Purpose. Step-by-step checklist for the first three days in orbit (LEOP — Launch and Early Orbit Phase). Written from the ground-operator’s seat: what to do, in what order, and what “good” looks like at each checkpoint.
Applies to: UniSat-1 or any derivative mission sharing the firmware stack in this repo.
The point of this section is to make sure the ground station is ready before the launch vehicle lifts off. Every hour spent here saves two hours of panic later.
# From a clean checkout, on the operations laptop:
git clone https://github.com/root3315/unisat.git
cd unisat
./scripts/verify.sh # ctest + pytest + SITL beacon demo
Pass criterion: final line ✓ UniSat green. Ready to submit.
ground-station/cli/ax25_listen.py
through a piped demodulator.ax25_send.py at low power into
a dummy load; confirm ax25_listen prints the same JSON bytes.simulation/orbit_simulator.py.ops_log.md — each pass gets a row.Two operators minimum per pass for the first 48 h. Sheet with shifts + callsigns + phone numbers. One operator runs the radio, the other monitors telemetry + tweets status for the team.
When: first pass over the ground station after deployment. Deployment usually happens ~30 min after separation from the launch vehicle. Satellite initially tumbles; the initial signal will be weak and fade-modulated.
Action:
python -m cli.ax25_listen --port 52100 --count 0 | tee logs/pass_001.json
Pass criterion: at least one beacon frame decoded with
fcs_valid: true within the predicted pass window.
Fail handling:
Open the decoded JSON and verify:
| Field | Expected first-pass value |
|---|---|
src.callsign |
Your mission callsign (e.g. UN8SAT) |
fcs_valid |
true |
info_hex (bytes 0-3) |
Small uptime, < 3600 s |
info_hex (byte 4) |
OBC mode = 0 (IDLE) or 1 (DETUMBLE) |
info_hex (bytes 5-6) |
Battery voltage 3.3-4.1 V converted (raw mV little-endian) |
If uptime is already hours, the satellite has been alive longer than you expected — check deployment timestamp vs TLE.
Post-deployment every CubeSat tumbles; the ADCS B-dot controller spends the first 1-12 h damping the rotation rate. Monitoring this is the single most important early-mission task.
Beacon bytes 32-33 carry omega in 0.01 deg/s units (magnitude).
Expected trajectory:
| Time since deploy | Typical omega |
|---|---|
| 0 h | 5-15 °/s (post-separation tumble) |
| 1 h | 3-8 °/s (B-dot engaged) |
| 6 h | < 2 °/s |
| 12 h | < 0.5 °/s |
Plot omega vs time — you want a monotonic decay.
Fail handling:
0x04 (FDIR report) to pull fault
codes from the dispatcher.Once omega < 0.5 °/s, beacon bytes 16-31 carry the quaternion. Watch for:
|q| == 1.0 ± 0.001 — normalised, good.Solar panels and antennas stay stowed until:
This is the first uplink command the mission will send. Every
byte is authenticated with HMAC-SHA256, counter-fresh. See
docs/security/ax25_threat_model.md
for the wire format.
python scripts/send_command.py \
--counter $(cat state/last_counter.txt | awk '{print $1+1}') \
--cmd DEPLOY_PANELS \
--callsign <YOUR_CALL> \
--key-file state/mission_key.bin
The ground-side script:
[counter BE][CCSDS cmd packet].state/last_counter.txt.Pass criterion: next beacon shows mode = 2 (NOMINAL) and
solar power > 1 W.
Fail handling:
rejected_replay or
rejected_bad_tag if the frame was refused.From Day 3 the satellite should beacon every 30 s. You get 4-6 passes per day over a mid-latitude ground station. Routine:
ax25_listen 5 min before each predicted AOS.cp logs/pass_*.json archive/$(date +%Y-%m-%d)/
For every pass, check the beacon’s error counter (byte 45):
| error_count | Meaning | Action |
|---|---|---|
| 0 | Clean. | Continue. |
| 1-5 | Transient I²C glitches. | Monitor. |
| 6-20 | Sensor flapping. | Log, plan downlink of full error log next pass. |
| 21-255 | Serious fault. | Escalate to Phase 6. |
Saturated errors (byte=0xFF) mean the telemetry counter maxed out — indicative, not diagnostic. Downlink the full FDIR log.
Once you have two weeks of clean telemetry, you can point the payload. Specifics depend on the mission:
ACTIVATE_CAMERA + TAKE_IMAGE
with TLE-derived ground-point scheduler.See the payload’s own README.md in payloads/<type>/ for the
command set.
The satellite enters SAFE_MODE autonomously if any of:
In SAFE_MODE the only active subsystems are:
RESET_FDIR with an authenticated command; this
clears the fault log (.noinit SRAM) and re-enters
NOMINAL if no fault is currently tripping.SOFT_REBOOT — OBC restarts, re-runs selftest.
Counter window is preserved through the A/B key store.HARD_REBOOT — same but includes a deeper init
(re-calibrate sensors, re-acquire TLE, etc.).Each of these commands is defined in the command dispatcher
and tested in firmware/tests/test_command_dispatcher.c.
ops_log.md templateUse one log file per mission week. Fill in every pass.
# Week of 2026-MM-DD
## Pass 2026-MM-DDThh:mm UTC (AOS) — operator @call
- Max elevation: XX°
- Frames received: N
- fcs_valid: true on all
- mode: 2 (NOMINAL)
- omega: 0.3 °/s
- SOC: 87 %
- errors: 0
- Commands sent: none
- Anomalies: none
The authenticated-uplink counter persists locally in
state/last_counter.txt. Never decrement it. If you lose
the file, the first uplink after re-initialisation will fail
with rejected_replay; recover by re-syncing to the
satellite’s current highest counter (downlinked in the
extended housekeeping beacon).
Fill this in before launch. Include amateur-radio elmers, IARU reps, and your national radio regulator emergency contact. Hard-coded names get stale; keep this appendix a living document.
This runbook is a template. Tailor it to your ground station, callsign, and mission-specific commands before launch day.