Skip to content

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 third

Or using docker:

$ docker compose up --build

2. The sub-systems

SubsystemCANAPIDBinaryRoleReference
DHS0x10x001dhs_nodeData 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 databasedhs.md
TTC0x20x002ttc_nodeTransfer-frame/link layer: moves whole PUS packets between the DHS and the EGSE, buffers them while the link is downttc.md
ADCS0x30x003adcs_nodeAttitude determination and control: TRIAD, B-dot detumble, PID pointing, mode logic, FDIR — plus the simulated plant it flies againstadcs.md, dyn_engine.md
EPS0x40x004eps_nodePower 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 pairseps.md, dyn_engine.md
PAYLOAD0x50x005payload_nodeThree instruments (GNSS, camera, storage), each on its own EPS-switched power channel, a virtual flash and the segmented file readoutpayload.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

PathWhat
include/shared_can.hCAN frame, addressing, transport. Included by the hub and nodes; no FreeRTOS.
include/pus.hCCSDS 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.hThe 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.hThe 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.hReceiving the on-board database: the subsystem side of the DHS’s distribution. Nodes only.
include/version.hThe platform version, written down once for all seven processes. CMake parses it back out of the header.
include/spacewire.hThe 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.hRMAP (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.hThe 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.hThe 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.hFreeRTOS kernel config, all five nodes.
config/minscs.confThe 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.cThe 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.cThe backplane. Plain POSIX C — deliberately no FreeRTOS.
egse/egse.cGround 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.
Last updated on