Skip to content

Commit f34db0d

Browse files
committed
deploy: cadc6ed
1 parent 6533d66 commit f34db0d

14 files changed

Lines changed: 2572 additions & 12 deletions

.gitignore

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
.idea
2+
**/.venv
3+
4+
# Build output
5+
metadata.json
6+
motors.db
7+
motors.db.gz
8+
9+
.env

LICENSE

Lines changed: 674 additions & 0 deletions
Large diffs are not rendered by default.

README.md

Lines changed: 230 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,230 @@
1+
# OpenRocket Motor Database
2+
3+
This repository acts as the dynamic backend for OpenRocket's thrust curve data.
4+
5+
## Deployment & API Endpoints
6+
7+
This repository utilizes **GitHub Pages** to act as a static Content Delivery Network (CDN) for the compiled motor data. This ensures high availability and fast downloads for OpenRocket users without requiring a dedicated backend server.
8+
9+
## Architecture
10+
11+
1. **Source:** John Coker's [ThrustCurve.org](https://www.thrustcurve.org/) API (thanks a lot for this incredible resource!).
12+
2. **Automation:** GitHub Actions runs weekly.
13+
3. **Process:**
14+
* Checks for new motors.
15+
* Downloads raw `.eng` and `.rse` files to `data/`.
16+
* Compiles them into a SQLite database (`motors.db`).
17+
* GZips and hashes the database.
18+
4. **Distribution:** The resulting `motors.db.gz` and `metadata.json` are published to GitHub Pages.
19+
20+
The deployment process follows a split-branch strategy:
21+
22+
1. **`main` branch:** Contains the source code, scripts, and the raw text cache (`.eng`/`.rse` files).
23+
2. **`gh-pages` branch:** Contains **only** the build artifacts (`motors.db.gz` and `metadata.json`).
24+
25+
Every time the [Update Workflow](.github/workflows/update-motors.yml) runs (scheduled weekly), it commits the new raw data to `main`, compiles the database, and force-pushes the binary artifacts to `gh-pages`.
26+
27+
### Public Endpoints
28+
29+
The OpenRocket client (and other interested 3rd parties) can access the live data at the following URLs:
30+
31+
| File | URL | Description |
32+
| :--- | :--- | :--- |
33+
| **Manifest** | `https://openrocket.github.io/motor-database/metadata.json` | Lightweight JSON file. Contains `database_version`, `generated_at`, `last_checked`, `sha256`, and the signature fields for verification. Checked by the client on startup. |
34+
| **Database** | `https://openrocket.github.io/motor-database/motors.db.gz` | GZipped SQLite database. Downloaded by the client *only* if the manifest version differs from the local cache. |
35+
36+
### Manifest Format
37+
The `metadata.json` structure is defined as follows:
38+
`database_version` is a sortable timestamp in `YYYYMMDDHHMMSS` format.
39+
```json
40+
{
41+
"schema_version": 2,
42+
"database_version": 20251225140000,
43+
"generated_at": "2025-12-25T14:00:00.000000",
44+
"last_checked": "2025-12-27T14:00:00.000000",
45+
"motor_count": 1033,
46+
"curve_count": 1320,
47+
"sha256": "a1b2c3d4e5f6...",
48+
"sha256_gz": "a1b2c3d4e5f6...",
49+
"sig": "base64-signature...",
50+
"download_url": "https://openrocket.github.io/motor-database/motors.db.gz"
51+
}
52+
```
53+
54+
Signature notes:
55+
- `sig` is an Ed25519 signature over `openrocket-motordb-v1\n{database_version}\n{sha256_gz}\n`.
56+
- `sha256_gz` is the SHA-256 of the gzipped database; `sha256` is kept for backward compatibility.
57+
- `key_id` is optional for key rotation.
58+
59+
## Database Schema
60+
61+
The SQLite schema lives in `schema/V1__initial_schema.sql` and is optimized for fast client lookups. Foreign keys are enabled with cascade deletes.
62+
63+
### Entity Relationship
64+
65+
```
66+
manufacturers motors thrust_curves thrust_data
67+
┌──────────────┐ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐
68+
│ id (PK) │◄─────│ mfr_id (FK) │ │ id (PK) │◄───────│ curve_id(FK) │
69+
│ name │ 1:N │ id (PK) │◄────│ motor_id(FK) │ 1:N │ id (PK) │
70+
│ abbrev │ │ tc_motor_id │ 1:N │ tc_simfile_id│ │ time_seconds │
71+
└──────────────┘ │ designation │ │ source │ │ force_newtons│
72+
│ impulse_class│ │ format │ └──────────────┘
73+
│ ... │ │ ... │
74+
└──────────────┘ └──────────────┘
75+
```
76+
77+
- **manufacturers****motors**: One manufacturer has many motors
78+
- **motors****thrust_curves**: One motor can have multiple thrust curves (different sources/measurements)
79+
- **thrust_curves****thrust_data**: One curve has many time/thrust data points
80+
81+
### Tables and Columns
82+
83+
**meta**
84+
85+
| Column | Type | Notes |
86+
| :--- | :--- | :--- |
87+
| key | TEXT | Primary key |
88+
| value | TEXT | Required |
89+
90+
Keys stored: `schema_version`, `database_version`, `generated_at`, `motor_count`, `curve_count`.
91+
92+
---
93+
94+
**manufacturers**
95+
96+
| Column | Type | Notes |
97+
| :--- | :--- | :--- |
98+
| id | INTEGER | Primary key, autoincrement |
99+
| name | TEXT | Required, unique (e.g. "AeroTech") |
100+
| abbrev | TEXT | Short name (e.g. "AT") |
101+
102+
---
103+
104+
**motors**
105+
106+
| Column | Type | Notes |
107+
| :--- | :--- | :--- |
108+
| id | INTEGER | Primary key, autoincrement |
109+
| manufacturer_id | INTEGER | FK to `manufacturers.id` |
110+
| tc_motor_id | TEXT | ThrustCurve.org motor ID |
111+
| designation | TEXT | Required (e.g. "H128W") |
112+
| common_name | TEXT | Display name (e.g. "H128") |
113+
| impulse_class | TEXT | Letter class: A, B, C, ... O |
114+
| diameter | REAL | Motor diameter in mm |
115+
| length | REAL | Motor length in mm |
116+
| total_impulse | REAL | Total impulse in Ns |
117+
| avg_thrust | REAL | Average thrust in N |
118+
| max_thrust | REAL | Maximum thrust in N |
119+
| burn_time | REAL | Burn time in seconds |
120+
| propellant_weight | REAL | Propellant weight in grams |
121+
| total_weight | REAL | Total weight in grams |
122+
| type | TEXT | "SU" (single-use), "reload", "hybrid" |
123+
| delays | TEXT | Available delays, e.g. "0,6,10,14" |
124+
| case_info | TEXT | Case info, e.g. "RMS 38/360" |
125+
| prop_info | TEXT | Propellant info, e.g. "White Lightning" |
126+
| sparky | INTEGER | 1 if sparky motor, 0 otherwise |
127+
| info_url | TEXT | URL to motor info page |
128+
| data_files | INTEGER | Number of data files on ThrustCurve |
129+
| updated_on | TEXT | Last update date from ThrustCurve |
130+
| description | TEXT | RASP file comments (header notes, newlines removed) |
131+
| source | TEXT | Data source (e.g. "thrustcurve.org", "manual") |
132+
133+
---
134+
135+
**thrust_curves**
136+
137+
Each motor can have multiple thrust curves from different sources (certification, manufacturer, user submissions).
138+
139+
| Column | Type | Notes |
140+
| :--- | :--- | :--- |
141+
| id | INTEGER | Primary key, autoincrement |
142+
| motor_id | INTEGER | FK to `motors.id`, cascade delete |
143+
| tc_simfile_id | TEXT | ThrustCurve.org simfile ID |
144+
| source | TEXT | "cert", "mfr", or "user" |
145+
| format | TEXT | "RASP" or "RSE" |
146+
| license | TEXT | "PD", "free", or other |
147+
| info_url | TEXT | URL to simfile info page |
148+
| data_url | TEXT | URL to download simfile |
149+
| total_impulse | REAL | Calculated total impulse (Ns) |
150+
| avg_thrust | REAL | Calculated average thrust (N) |
151+
| max_thrust | REAL | Calculated max thrust (N) |
152+
| burn_time | REAL | Calculated burn time (s) |
153+
154+
---
155+
156+
**thrust_data**
157+
158+
Time/thrust data points for each thrust curve.
159+
160+
| Column | Type | Notes |
161+
| :--- | :--- | :--- |
162+
| id | INTEGER | Primary key, autoincrement |
163+
| curve_id | INTEGER | FK to `thrust_curves.id`, cascade delete |
164+
| time_seconds | REAL | Time in seconds |
165+
| force_newtons | REAL | Thrust force in Newtons |
166+
167+
---
168+
169+
### Indices
170+
171+
| Index | Table | Columns | Purpose |
172+
| :--- | :--- | :--- | :--- |
173+
| idx_motor_mfr | motors | manufacturer_id | Filter by manufacturer |
174+
| idx_motor_diameter | motors | diameter | Filter by size |
175+
| idx_motor_impulse | motors | total_impulse | Filter by impulse |
176+
| idx_motor_impulse_class | motors | impulse_class | Filter by class (A-O) |
177+
| idx_motor_tc_id | motors | tc_motor_id | Lookup by ThrustCurve ID |
178+
| idx_curve_motor | thrust_curves | motor_id | Get curves for a motor |
179+
| idx_curve_simfile | thrust_curves | tc_simfile_id | Lookup by simfile ID |
180+
| idx_thrust_curve | thrust_data | curve_id | Get data for a curve |
181+
182+
## Manual Usage
183+
184+
1. `pip install -r scripts/requirements.txt`
185+
2. `python scripts/fetch_updates.py` (Downloads new files)
186+
3. `python scripts/build_database.py` (Generates DB)
187+
188+
## State Files
189+
190+
- `state/last_update.json`: timestamp of the most recent data/metadata change detected by `scripts/fetch_updates.py`.
191+
- `state/last_check.json`: timestamp of the most recent update check, even if no changes were found.
192+
- `state/last_build.json`: input hash + build outputs used by `scripts/build_database.py` to skip rebuilding when the schema/data inputs are unchanged.
193+
194+
## Signing
195+
196+
Signing is done in CI after the database build completes.
197+
198+
What is signed:
199+
- Canonical message: `openrocket-motordb-v1\n{database_version}\n{sha256_gz}\n`
200+
- `sha256_gz` is the SHA-256 of `motors.db.gz` (the compressed DB)
201+
202+
What gets added to `metadata.json` by the signing step:
203+
- `sha256_gz`: SHA-256 of `motors.db.gz` (currently matches `sha256`)
204+
- `sig`: base64-encoded Ed25519 signature of the canonical message
205+
- `key_id` (optional): identifier for key rotation
206+
207+
How CI handles it:
208+
- `.github/workflows/update-motors.yml` installs `cryptography`
209+
- It runs `python scripts/sign_database.py motors.db.gz metadata.json`
210+
- The private key is provided via secrets
211+
212+
Set the private key in:
213+
214+
- `MOTOR_DB_PRIVATE_KEY_BASE64` (Ed25519 private key, DER or PEM encoded, then base64)
215+
- `MOTOR_DB_KEY_ID` (optional, for key rotation)
216+
217+
Manual signing: `python scripts/sign_database.py motors.db.gz metadata.json`
218+
219+
## Unit Tests
220+
221+
1. `pip install -r scripts/requirements.txt`
222+
2. `pip install pytest cryptography`
223+
3. `pytest`
224+
225+
## Data Attribution & License
226+
The motor data in this repository is cached from [ThrustCurve.org](https://www.thrustcurve.org).
227+
228+
* **Source:** ThrustCurve.org (maintained by John Coker).
229+
* **Copyright:** The data files (`.eng`, `.rse`) retain their original internal copyright headers.
230+
* **Usage:** This data is intended for use within OpenRocket. If you wish to use this data for other projects, please use the [ThrustCurve.org API](https://www.thrustcurve.org/info/api.html) directly to ensure you are respecting the latest updates and restrictions.

metadata.json

Lines changed: 0 additions & 12 deletions
This file was deleted.

motors.db.gz

-783 KB
Binary file not shown.

resources/rasp_file_format.md

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
Resource: https://www.thrustcurve.org/info/raspformat.html, accessed on 2025-12-26 at 23:06 UTC.
2+
3+
4+
# RASP File Format
5+
6+
RASP was the original rocket flight simulator, started by the model rocket pioneer G. Harry Stine. This format, named
7+
"RASP" or "ENG", has become the standard for motor data interchange and most modern flight simulators can use these
8+
files directly or import them into their own proprietary formats.
9+
10+
RASP was a very simple program and had relatively strict requirements for the format and data values. Modern simulators
11+
are generally more forgiving, but it's still preferable to make your files comply to the original standard to allow them
12+
to work with as many programs as possible.
13+
14+
For the hard-core, the [source code](http://www.ibiblio.org/pub/archives/rec.models.rockets/RASP/MRASP/mac_raspinfo.c)
15+
for the RASP engine database listing program is the ultimate authority.
16+
17+
This page gives you enough information to create and edit these files by hand. There is also a tool you can use to trace
18+
the thrust curve from a graph image and produce RASP data files automatically. See the TCtracer page for more info.
19+
20+
## The Header Line
21+
22+
Blank lines and lines beginning with semicolons are ignored at the begnning of the entry. The first line interpreted is
23+
the "header line", which contains info on the motor itself. The header contains seven pieces of info, separated by
24+
spaces. All seven must be present for the entry to be read successfully.
25+
26+
Here's a sample fragment, with a few comments and the header line:
27+
28+
![raspheader.png](raspheader.png)
29+
30+
1. The common name of the motor; just the impulse class and average thrust.
31+
2. The casing diameter in millimeters (mm).
32+
3. The casing length, also in millimeters.
33+
4. The list of available delays, separated by dashes. If the motor has an ejection charge but no delay use "0" and if
34+
it has no ejection charge at all use "P" (plugged).
35+
5. The weight of all consumables in the motor. For solid motors this is simply the propellant itself, but for hybrids
36+
it is the fuel grain(s) plus the oxidizer (such as N2O). This weight is expressed in kilograms (Kg).
37+
6. The weight of the motor loaded and ready for flight, also in kilograms.
38+
7. The motor manufacturer abbreviated to a few letters. NAR maintains a list of manufacturer abbreviations on page 2
39+
of the [combined master list](https://www.nar.org/SandT/pdf/CombinedMotorsByImpulse.pdf).
40+
41+
## Data Points
42+
43+
Starting immediately after the header line, the remaining lines contain sample data points. Each sample specifies a time
44+
(seconds) and a thrust (Newtons) as two floating-point numbers, usually preceded by a few spaces for readability.
45+
46+
An implicit first point at 0,0 is assumed and should not be specified explicitly. The final point must have a thrust of
47+
zero and it indicates the motor's burn time. The points should be in increasing order of time and the thrusts should
48+
trace out the thrust curve as representatively as possible.
49+
50+
After the final data point, the entry may end or contain comments and blank lines, but nothing else.
51+
52+
RASP supported a maximum of 32 data points, including the final (zero) point. This site does not enforce that limitation
53+
since modern simulator programs don't either. However, files with more than 32 data points will not work with some
54+
older programs.
55+
56+
It is common to put a single line containing just a semicolon after the sample data. This is not interpreted by the
57+
software, but it does prevent two entries from running together if multiple entries are present in the same file. Here's
58+
the first example we created on the [contribute page](https://www.thrustcurve.org/info/contribute.html):
59+
60+
```
61+
; Rocketvision F32
62+
; from NAR data sheet updated 11/2000
63+
; created by John Coker 5/2006
64+
F32 24 124 5-10-15 .0377 .0695 RV
65+
0.01 50
66+
0.05 56
67+
0.10 48
68+
2.00 24
69+
2.20 19
70+
2.24 5
71+
2.72 0
72+
;
73+
```
74+
75+
## Common Problems
76+
77+
One common problem with RASP data floating around is that the thrust drops to zero before the end of the data. However,
78+
a sample with zero thrust represents the end of the data. Note that a point at zero time is not required. (It is a
79+
common mistake to create an initial point at zero time, zero thrust.) This site will detect this problem and reject an
80+
entry with zero thrust at other than the last point.
81+
82+
The burn always starts at zero time and thus the burn time is simply the time at the final (zero thrust) point. The
83+
final point must have zero thrust and this site will detect this problem and reject an entry without it.
84+
85+
The data points need not be spaced evenly apart, but they must be in order of time. This site will detect this problem
86+
and reject an entry where a data point occurs before the previous point (or where the first point occurs in negative
87+
time).
88+
89+
The original RASP would only read a motor descriptions from a single file (RASP.ENG). All motor entries were located in
90+
this file, separated by comment lines (beginning with a semicolon). When maintaining multiple motor entries in a single
91+
file, make sure that each entry is separated by at least one comment line. Since this site is database-oriented, each
92+
entry is stored in a separate record. This site will parse these multi-motor files and attempt to pick out the correct
93+
motor.
94+
95+
Finally, a perennial problem is with the manufacturer name. Originally, only a few manufacturers existed and each had a
96+
single-letter code (E=Estes, A=AeroTech, K=Kosdon, and so on). With the rapid increase in the number of manufacturers,
97+
this has become impractical. NAR maintains a list of manufacturer abbreviations in the combined master list, but
98+
unfortunately they aren't used consistently.
99+
100+
This site has collected the abbreviations from the NAR master list and those used by various simulator programs and
101+
listed them as aliases along with the other manufacturer info (See the Manufacturers page). When scanning simulator
102+
files, it checks against the full name, abbreviation and the set of aliases to match the manufacturer.

resources/raspheader.png

4.25 KB
Loading

0 commit comments

Comments
 (0)