minscs — minimalist spacecraft simulator
Five spacecraft subsystems, each an independent FreeRTOS binary, talking over a simulated multi-drop CAN bus, plus a ground-segment EGSE you can drive over TCP. Nodes share no memory: the only thing that crosses a subsystem boundary is an 8-byte CAN frame — except for bulk science, which crosses a second bus, a point-to-point SpaceWire link from the payload straight into the DHS’s mass memory.
┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐
│ DHS │ │ TTC │ │ ADCS │ │ EPS │ │ PAYLOAD │
│ 0x1 │ │ 0x2 │ │ 0x3 │ │ 0x4 │ │ 0x5 │
└──┬───┬──┘ └──┬──┬───┘ └────┬────┘ └────┬────┘ └──┬───┬──┘
│ └─────────┴──┼────┬───────┴─────────────┴───────────┘ │
│ │ │ UDP :20000 (CAN backplane) │
│ │ ┌─┴─────────┐ │
│ │ │ can_hub │ routing table, learned │
│ │ └───────────┘ at runtime │
└────────────────┼──────────────────────────────────────────┘
UDP :20100 │ SpaceWire: RMAP bulk science, one way
│
TCP :21000 │ ── spacecraft ──
════════════════════╪══════════════════════════════════════
│ ──── ground ────
┌─────┴─────┐ TCP :21001 ┌──────────┐
│ egse │ ──── stream ──▶│ egse_tm │ decode TM
│ (relay) │ ◀─── TC ───────│ egse_tc │ build TC
└───────────┘ └──────────┘The spacecraft/ground boundary is a TCP stream carrying CCSDS Space Packets with ECSS PUS-C secondary headers — TM downlink and TC uplink. CAN never leaves the spacecraft.
1. Quickstart
Build the project using cmake and then run the complete platform using the ./run_sim.sh script:
$ cmake -B build && cmake --build build -j # fetches FreeRTOS-Kernel V11.3.0
$ ./run_sim.sh # Ctrl+C stops the platform
$ ./build/bin/egse_tm # watch decoded telemetry, in another terminal
$ ./build/bin/egse_tc DHS PING # send a command, in a thirdOr using docker:
$ docker compose up --build2. The sub-systems
| Subsystem | CAN | APID | Binary | Role | Reference |
|---|---|---|---|---|---|
| DHS | 0x1 | 0x001 | dhs_node | Data handling: builds the PUS TM packets, decodes uplinked TC, owns the mass memory unit, the on-board clock and the event log, and holds and distributes the on-board database | dhs.md |
| TTC | 0x2 | 0x002 | ttc_node | Transfer-frame/link layer: moves whole PUS packets between the DHS and the EGSE, buffers them while the link is down | ttc.md |
| ADCS | 0x3 | 0x003 | adcs_node | Attitude determination and control: TRIAD, B-dot detumble, PID pointing, mode logic, FDIR — plus the simulated plant it flies against | adcs.md, dyn_engine.md |
| EPS | 0x4 | 0x004 | eps_node | Power and thermal: load rails (the instrument one switched per payload instrument), a coulomb-counting fuel gauge, the low-power alert — plus the simulated plant it draws from — and the owner of all three cold-redundant pairs | eps.md, dyn_engine.md |
| PAYLOAD | 0x5 | 0x005 | payload_node | Three instruments (GNSS, camera, storage), each on its own EPS-switched power channel, a virtual flash and the segmented file readout | payload.md |
The DHS is the CAN↔PUS boundary and TTC is the link layer: TTC takes whole packets from
the DHS (segmented over the bus, since a packet is far larger than an 8-byte frame), buffers
telemetry while the link is down, and dials out to the EGSE so the spacecraft never waits on an
accept(). RF, modulation, channel coding and CCSDS transfer frames are TTC’s charter, kept here
as a pass-through stub. On the ground, the EGSE is a dumb relay
— all decoding and command building lives in the client tools.
3. Repository layout
| Path | What |
|---|---|
include/shared_can.h | CAN frame, addressing, transport. Included by the hub and nodes; no FreeRTOS. |
include/pus.h | CCSDS Space Packet + PUS-C TM/TC encode/decode/frame. Shared by the DHS, TTC (framing only), the EGSE and the tools; no FreeRTOS. |
include/datapool.h | The on-board parameter catalog and pool API, and the on-board database catalog (DP_CFG_CATALOG). Shared by the nodes and the tools, so a mnemonic means the same thing on both ends; no FreeRTOS. |
include/events.h | The on-board event catalog: one X-macro row per occurrence, generating both the identifiers the DHS raises and the names egse_tm renders. Same arrangement as datapool.h, for the same reason; no FreeRTOS. |
include/obdb.h | Receiving the on-board database: the subsystem side of the DHS’s distribution. Nodes only. |
include/version.h | The platform version, written down once for all seven processes. CMake parses it back out of the header. |
include/spacewire.h | The SpaceWire mission data link: datagram, character level, FCT flow control, the initialisation state machine, logical addressing and 50-51C protocol identification. Shared by both ends and by the ground tools; no FreeRTOS. |
include/rmap.h | RMAP (ECSS-E-ST-50-52C) write commands and replies, header-only, with the standard’s CRC-8. Note this is not the CRC the rest of the platform uses; see spacewire.md. |
include/aocs_icd.h | The ADCS sensor/actuator interface: the contiguous image, its three segments, the identifier map, frames and units. The only header shared between nodes/adcs_node.c and sim/. FreeRTOS-free. |
include/eps_icd.h | The EPS sensor/actuator interface, on exactly the same pattern: the box’s own shunts, ADC, thermistors and ephemeris, the battery’s ground truth, and the load rails the application commands. The only header shared between nodes/eps_node.c and sim/eps_plant.c. FreeRTOS-free. |
config/FreeRTOSConfig.h | FreeRTOS kernel config, all five nodes. |
config/minscs.conf | The mission parameter database: one INI file initialises every node. |
common/ | config.c (INI loader), can_drv.c (virtual CAN driver — the Posix port workarounds live here), log.c (leveled, mutex-guarded logging), obt.c (on-board time), datapool.c (parameter table, dynamic arena, non-volatile mirror), obdb.c (receiving a configuration block). Nodes only. |
nodes/ | One file per subsystem, one binary each. |
common/spw_drv.c | The node-side SpaceWire driver, carrying the same three Posix port hazards as can_drv.c. Linked into the DHS and the payload alone, through ${ARGN}: no other subsystem has a SpaceWire port. |
sim/ | The plants: the ADCS’s External Dynamics Engine, the EPS’s Power Plant, and the shared universe (orbit, sun, shadow) both fly in. Not flight software — a sibling of hub/ and egse/ in status — but linked into adcs_node and eps_node, whose interfaces are those boards’ own sensor harnesses. See dyn_engine.md. |
hub/can_hub.c | The backplane. Plain POSIX C — deliberately no FreeRTOS. |
egse/egse.c | Ground packet relay: fans the PUS stream to tools, forwards TC. Plain POSIX C, select()-based, no decoding. |
tools/ | egse_tm.c (TM viewer), egse_tc.c (TC sender), egse_link.h (shared connect helper). Plain POSIX C. |
tests/ | The test suite: one .c per area, a Unity main in each, plus smoke_sim.sh which runs the whole platform. Links no kernel. See testing.md. |
mdb/ | The XTCE mission database. See mdb.md. |
yamcs/ | Yamcs server and instance configuration. See yamcs.md. |
containers/ | The image the platform runs in: a multi-stage Dockerfile and its ignore file. The build context is the repo root; docker-compose.yml stays there because that is where docker compose up looks. Not part of the CMake build. |