This project is for controlling our Air brakes system with the goal of making our rocket "hit" its target apogee. We have a Raspberry Pi 4 as the brains of our system which runs our code. It connects to a servo motor to control the extension of our air brakes and an IMU (basically an altimeter, accelerometer, and gyroscope). The code follows the finite state machine design pattern, using the AirbrakesContext
to manage interactions between the states, hardware, logging, and data processing.
https://github.com/user-attachments/assets/0c72a9eb-0b15-4fbf-9e62-f6a69e5fadaa
A video of our air brakes extending and retracting
As per the finite state machine design pattern, we have a context class which links everything together. Every loop, the context:
%%{init: {'theme': 'dark'}}%%
flowchart TD
%% Define styles for different shapes
classDef circular fill:#44007e,stroke:#fff,stroke-width:2px,rx:50%,ry:50%; %% Ovals for classes
classDef bubble fill:#164b6c,stroke:#fff,stroke-width:2px,rx:10px,ry:10px; %% Bubbles for methods
classDef outputSquare fill:#044728,stroke:#fff,stroke-width:2px; %% Squares for outputs
classDef textStyle fill:none,color:#fff,font-weight:bold,font-size:16px; %% Text style for bold white text
classDef mainColor fill:#ac6600,stroke:#fff,stroke-width:2px,rx:50%,ry:50%; %% Color for main.py
%% Main structure with main.py at the top
subgraph mainFlow[Main Flow]
direction TB
mainpy((main.py)):::mainColor --> Airbrakes((Airbrakes Context)):::circular
end
Airbrakes --> Update[update]:::bubble
%% IMU Data Packet Flow
IMUDataPacket --> Update
%% States as individual nodes
State((State)):::circular
CoastState((Coast)):::circular
Standbystate((Standby)):::circular
FreefallState((Freefall)):::circular
LandedState((Landed)):::circular
MotorBurnState((Motor Burn)):::circular
%% Connections between States and Airbrakes
State((State)):::circular --> Airbrakes:::circular
State --> Update
CoastState((Coast)):::circular --> State
Standbystate((Standby)):::circular --> State
FreefallState((Freefall)):::circular --> State
LandedState((Landed)):::circular --> State
MotorBurnState((Motor Burn)):::circular --> State
%% Connections with Labels
Airbrakes ---|Child Process| Logger((Logger)):::circular
Airbrakes ---|Child Process| IMU((IMU)):::circular
IMU((IMU)):::circular ---|Fetch Packets| IMUDataPacket[(IMU Data Packet)]:::outputSquare
%% IMU Data Processing
IMUDataPacket --> IMUDataProcessor[IMU Data Processor]:::circular
%%IMUDataProcessor --> ProcessedData[(Processed Data Packet)]:::outputSquare
IMUDataProcessor --> Speed[(Speed)]:::outputSquare
IMUDataProcessor --> Altitude[(Altitude)]:::outputSquare
Speed --> ProcessedData[(Processed Data Packet)]:::outputSquare
Altitude --> ProcessedData[(Processed Data Packet)]:::outputSquare
ProcessedData[(Processed Data Packet)]:::outputSquare --> Update
%% Logging Mechanism
Logger --> LogFunction[log]:::bubble
Update --> LogData[(Logged Data Packet)]:::outputSquare
LogData --> LogFunction
%% Airbrake Control Methods with Parentheses Displayed
Update --> ExtendAirbrakes[extend_airbrakes]:::bubble
Update --> RetractAirbrakes[retract_airbrakes]:::bubble
%% Servo Connections
RetractAirbrakes --> Servo((Servo)):::circular
ExtendAirbrakes --> Servo
Airbrakes --> Servo
Type | Color | Examples |
---|---|---|
Entry point | #ac6600 |
Main.py |
Classes | #44007e |
Airbrakes Context, State, Logger, IMU, IMU Data Processor, Servo, Coast, Standby, Freefall, Landed, Motor Burn |
Methods | #164b6c |
update(), log(), extend_airbrakes(), retract_airbrakes() |
Outputs | #044728 |
IMU Data Packet, Processed Data Packet, Logged Data Packet |
This is our interest launch flight data, altitude over time. The different colors of the line are different states the rocket goes through:
We have put great effort into keeping the file structure of this project organized and concise. Try to be intentional on where you place new files or directories.
AirbrakesV2/
āāā airbrakes/
| āāā hardware/
ā ā āāā [files related to the connection of the pi with hardware ...]
| āāā mock/
ā ā āāā [files related to the connection of mock (or simulated) hardware ...]
| āāā data_handling/
ā ā āāā [files related to the processing of data ...]
ā āāā [files which control the airbrakes at a high level ...]
āāā tests/ [used for testing all the code]
ā āāā ...
āāā logs/ [log files made by the logger]
ā āāā ...
āāā scripts/ [small files to test individual components like the servo]
ā āāā ...
āāā main.py [main file used to run on the rocket]
āāā constants.py [file for constants used in the project]
āāā pyproject.toml [configuration file for the project]
āāā README.md
This project uses Python 3.12. Using an older version may not work since we use newer language features
git clone https://github.com/NCSU-High-Powered-Rocketry-Club/AirbrakesV2.git
cd AirbrakesV2
python -m venv .venv
# For Linux
source .venv/bin/activate
# For Windows
.\.venv\Scripts\activate
pip install .[dev]
There are libraries that only fully work when running on the Pi (gpiozero, mscl), so if you're having trouble importing them locally, program the best you can and test your changes on the pi.
Testing our code can be difficult, so we've developed a way to run mock launches based on previous flight data--the rocket pretends, in real-time, that it's flying through a previous launch.
To run a mock launch, make sure to first specify the path to the CSV file for the previous launch's data in constants.py
and then run:
python3 main.py -m
If you want to run a mock launch, but with the real servo running, run:
python3 main.py -m -r
There are some additional options you can use when running a mock launch. To view them all, run:
python3 main.py --help
Our CI pipeline uses pytest to run tests. You can run the tests locally to ensure that your changes are working as expected.
To run the tests, run this command from the project root directory:
pytest
If you make a change to the code, please make sure to update or add the necessary tests.
Our CI also tries to maintain code quality by running a linter. We use Ruff.
To run the linter, and fix any issues it finds, run:
ruff check . --fix --unsafe-fixes
To format the code, run:
ruff format .
In order to connect to the Pi, you need first to set up a mobile hotspot with the name HPRC
, password tacholycos
, and 2.4 GHz
band. Next, turn on the Pi and it will automatically connect to your hotspot. Once it's connected, find the Pi's IP Address, and in your terminal run:
ssh pi@[IP.ADDRESS]
# Its password is raspberry
cd AirbrakesV2/
Every time the pi boots up, you must run this in order for the servo to work. We have already added this command to run on startup, but you may want to confirm that it is running, e.g. by using htop
.
sudo pigpiod
During development, you may want to run individual scripts to test components. For example, to test the servo, run:
# Make sure you are in the root directory,
python3 -m scripts.run_servo
Feel free to submit issues or pull requests. For major changes, please open an issue first to discuss what you would like to change.
This project is licensed under the MIT License. You are free to copy, distribute, and modify the software, provided that the original license notice is included in all copies or substantial portions of the software. See LICENSE for more.