The XTCE mission database
mdb/minscs.xtce is the minscs packet catalog in a portable, standard
format, so a real mission control system can decode telemetry and build commands without
reimplementing tools/egse_tm.c and tools/egse_tc.c.
See yamcs.md for pointing a Yamcs install at it and pus.md for the wire
format it describes.
The standard: XTCE
XTCE — XML Telemetric and Command Exchange, CCSDS 660.0-B — is the standard for exchanging
telemetry and telecommand database definitions between ground systems, native to
Yamcs and understood by most commercial MCS. It fits minscs directly: the
ground link already speaks CCSDS Space Packets with PUS-C secondary headers (pus.md),
and XTCE was designed for exactly that — a header container all packets inherit, plus parameter
extraction by bit offset with calibration and enumerations.
minscs.xtce targets XTCE 1.2 (http://www.omg.org/spec/XTCE/20180204).
Which simulator this database describes
Because the file is synchronised by hand, nothing stops an MDB from one release being pointed at a
spacecraft running another — and the failure is quiet: a container whose Packet_Length no longer
matches simply stops matching, and the packet lands in the ground system undecoded rather than
wrong. So the database states which build it was written for, and the spacecraft states which
build it’s running.
In the file: the <Header version="…"> attribute names the minscs release, the number in
include/version.h. (The XTCE standard version is not stated there — the
20180204 namespace on the SpaceSystem element already fixes it.)
On the wire: TM_DHS_Platform_HK ends with dhs_sw_major / dhs_sw_minor / dhs_sw_patch,
three octets the DHS fills from include/version.h — three plain numbers rather than one packed
word, precisely so the comparison is done by eye. The same number is also a data-pool parameter,
SYS_SW_VERSION (0x010A), readable on demand instead of waiting for a housekeeping cycle; there
it’s packed, major<<16 | minor<<8 | patch, so 0.0.1 reads as 1.
build/bin/egse_tm | grep 'DHS .* HK' # ... | ssmm files=0 | sw=0.0.1
grep '<Header version' mdb/minscs.xtce # <Header version="0.0.1" ...
build/bin/egse_tc DHS PARAM GET SYS_SW_VERSIONIt’s read-only, and a modelled OBC switch doesn’t change it: the reboot models a different computer running the same build. Every binary also prints the version in its startup banner, so a mismatched build tree shows up before anything is commanded.
When they disagree, trust neither decode. Bump <Header version> in the same change that
bumps include/version.h.
What’s inside
Shared framing — one abstract CCSDS_TM_Packet container carries the 6-octet CCSDS primary
header and the 13-octet PUS-C TM secondary header (service, subtype, message counter, destination,
CUC time). Every telemetry packet inherits it and restricts on APID + Service + Subtype. The
on-board time is an AbsoluteTimeParameterType declaring the UNIX epoch with
offset="-378691200" — deliberately not the 1958-01-01 a CCSDS CUC field suggests, which is
a silent 37-second error rather than a shortcut. See
The CUC epoch is UTC, not TAI before touching those types.
Telemetry (ContainerSet) — one container per distinct layout in prvDecodeTm:
| Container | APID | svc/sub |
|---|---|---|
TM_DHS_Platform_HK | DHS (1) | 3/25 |
TM_ADCS_HK | ADCS (3) | 3/25 |
TM_EPS_HK | EPS (4) | 3/25 |
TM_Payload_GNSS_HK / TM_Payload_Flash_Status | PAYLOAD (5) | 3/25 |
TM_TTC_Link_HK | TTC (2) | 3/25 |
TM_Event_Report | any | 5/1..4 |
TM_Verif_Accept_Success / _Accept_Failure | any | 1/1, 1/2 |
TM_Verif_Start_Success / _Start_Failure | any | 1/3, 1/4 |
TM_Verif_Complete_Success / _Complete_Failure | any | 1/7, 1/8 |
TM_Large_Data_First | any | 13/1 |
TM_Sched_Detail_Report | DHS (1) | 11/17 |
TM_Param_Value_Report | DHS (1) | 20/2 |
TM_File_Attr_Report | DHS (1) | 23/4 |
TM_Repo_Summary | DHS (1) | 23/13 |
Physical values are scaled integers on the wire (×100, or ×10 for altitude); the XTCE
calibrators recover SI units (%, V, A, deg/s, deg, km, degC). Enumerations cover
platform mode, OBC id, on/off, event severity, parameter value type and derivation operator, and
the time-reference status nibble.
TM_Param_Value_Report’s length isn’t fixed: a chunk-control octet, a record count, and that many
eight-octet records. Fixed-width records are what make it modellable — XTCE 1.2 repeats an
entry a dynamically counted number of times
(<RepeatEntry><Count><DynamicValue parameterRef="pv_count"/>) but can’t repeat one whose members
vary in width. pv_value stays raw 32 bits because its interpretation follows the pv_type octet
beside it, which no calibration can express.
Telecommands (MetaCommandSet) — an abstract PUS_TC_Base builds the CCSDS primary + PUS-C
TC secondary header; each concrete command assigns its service/subtype and appends the app-data
opcode and arguments. All twenty-nine:
| svc/sub | MetaCommands |
|---|---|
| 3/27, 9/128, 17/1 | TC_Request_HK, TC_Set_Time, TC_PING |
| 8/1 (routed to CAN) | TC_Set_Mode, TC_Capture, TC_Flash_Status, TC_Dump_Events, TC_Downlink_File, TC_Payload_Power (opcode CMD_PL_PWR), TC_Param_Dump (CMD_PARAM_DUMP), TC_Config_Push (CMD_CFG_PUSH) |
| 8/2 (high-priority, to EPS) | TC_OBC_Switch, TC_ACS_Switch, TC_TTC_Switch |
| 11/1..5, 11/16 | TC_Sched_Enable, _Disable, _Reset, _Insert, _Delete, _Detail_Req |
| 20/1, 20/3, 20/133, 20/134 | TC_Param_Report_Req, TC_Param_Set, TC_Param_Define, TC_Param_Delete |
| 23/1, 23/2, 23/3, 23/12, 23/14 | TC_File_Create, _Delete, _Attr, _List, _Copy |
The on-board engine accepts up to thirty parameters per request; the MetaCommands model the
single-parameter form, what an operator sends by hand, with the leading count pinned by a
FixedValueEntry. egse_tc builds the multi-parameter form directly. Subtypes 133/134 are
mission-specific because ECSS-E-ST-70-41C standardises only 20,1 / 20,2 / 20,3 and reserves
subtypes 1..127 — the same convention the mission already uses for the set-clock command at 9,128.
Discrimination notes
APID carries provenance — several subsystems share service 3/25, so each container’s restriction criteria include
APID, not just service/subtype.Same-APID collisions use
Packet_Length. PAYLOAD (GNSS / flash) is the only case left: the two layouts share APID+service+subtype and are disambiguated by the CCSDS packet-length field, which mirrors theapp_lenguards inprvDecodeTm(22/20); the0x12tag byte is still modelled as the first parameter of the tagged layout, so it doesn’t rest on length alone.This is a constraint on the flight software, not just on this file. The ADCS and EPS used to relay one container per CAN frame, told apart by a ladder of lengths, which made every report’s octet count a ground-visible interface. The DHS now builds one report per subsystem from its parameter pool, so
TM_ADCS_HK(47) andTM_EPS_HK(44) are one container each at a length nothing else reaches. A layout identifiable only by length is one wire change from being wrong; where that’s unavoidable, compare the tag as well.TM_Param_Value_Reportrestricts on APID + service + subtype alone, deliberately: its length varies with the record count, so there’s noPacket_Lengthto compare against — and no other layout shares 1 / 20 / 2.Bit order in the configuration-readiness flags.
TM_DHS_Platform_HKcarries a one-octet mask whose bit 0 is TTC and bit 3 the payload; a container reads MSB first, so theEntryListruns the other way — four unused high bits, then payload, EPS, ADCS, TTC, as four one-bit booleans so a ground system renders four indicators. The command-schedule block follows the mask.
What is deliberately not modelled
- The on-board database distribution (
CMD_CFG_BEGIN/_ITEM/_END) never crosses the ground link, so it gets no containers: a mission database describes the space-to-ground wire, not the internal bus. What the ground sees is the effect — the epoch and readiness flags inTM_DHS_Platform_HK, theCFG_*events, and the parameters through service 20. - The on-board execution time option is a Yamcs-level concept registered from Java, not a
mission-database construct, which is what keeps this file standard XTCE
(
yamcs.md). A wrapped command is aTC[11,4]on the wire, soTC_Sched_Insertdescribes it, not the MetaCommand the operator picked. - The telecommand embedded in a
TC_Sched_Insertis opaque octets (ArgTcBlock) of lengthtc_length, because XTCE can’t say “any other member of this MetaCommandSet appears here”; a ground system encodes the inner command itself, asegse_tc’sprvDispatchScheddoes. LikewiseTM_Sched_Detail_Reportreports each activity’s identity (APID, service, subtype, length) but not its bytes, since the flight software doesn’t echo them. - No ADCS or EPS report tag.
ADCS_RPT_*andEPS_RPT_*still sort those subsystems’ CAN frames, but none reaches the ground now that the DHS builds their reports rather than relaying them. Only a relayed layout’s tag belongs in a mission database, which here leavesFlashTagalone. - Big-endian — every multi-byte field is MSB-first, matching
pusPutPrimaryand the(d[0]<<8)|d[1]decoders.
Uplink caveats (Yamcs fills these)
Three fields in PUS_TC_Base_Container are zero placeholders because they depend on the final
encoded packet, and the ground system computes them at build time: the 14-bit sequence count
(per-APID rolling counter), the 16-bit packet length, and the PEC trailer — a 2-octet
CRC-16-CCITT (poly 0x1021, seed 0xFFFF) that isn’t in the command container and must be added by
a Yamcs command post-processor or in the link. The same CRC is on every downlink packet, modelled as
the trailing PEC_CRC parameter, and Yamcs can verify it on receipt.
Ack flags are fixed to 0x9 (ACCEPT | COMPLETE), matching prvSendTc; the TC source id is the
ground APID 0x00A.
The CUC epoch is UTC, not TAI
OBT and ArgOBT declare the UNIX epoch with an offset="-378691200", not the 1958-01-01
a CCSDS CUC field suggests. A 1958 epoch means TAI to Yamcs, and this clock isn’t TAI:
cucFromUnix() is plain Unix seconds plus 378691200, and obt.c knows nothing of leap seconds.
With the 1958 declaration a TC_Set_Time for 18:00:00Z encoded as on-board 18:00:37Z, and every
scheduled activity was misplaced by the same amount, silently, growing by a second at each leap.
Two details that are easy to get wrong when touching this:
- It must be the named token
UNIX, not the literal date1970-01-01T00:00:00. Yamcs’s own instant is already “millis since 1970 TAI”, so a literal 1970 epoch is used raw and reproduces the identical 37-second error; only the token selects the UTC conversion. Both were tried. - The
offsetattribute on<Encoding>is honoured, and is what carries the 378691200 s from the Unix epoch to the CCSDS one.
Check it after any change to those types, with the simulator not even running:
curl -s -X POST localhost:8090/api/processors/minscs/realtime/commands/minscs/TC_Set_Time \
-H 'Content-Type: application/json' \
-d '{"dryRun":true,"args":{"obt":"2026-08-03T18:00:00.000Z"}}'Base64-decode binary; the six app-data octets must be 810335A00000 (coarse 2164471200). If
they read 810335C50000 the TAI regression is back.
Command verification (command-history status)
Each DHS-handled MetaCommand carries a <VerifierSet> so Yamcs drives command-history status
from the DHS’s service-1 reports: an AcceptedVerifier on TM_Verif_Accept_Success (1,1) and,
where a completion follows, a CompleteVerifier on TM_Verif_Complete_Success (1,7). These are
container verifiers — a stage passes when a matching report arrives inside its check window
(acceptance 3 s, completion 8 s, the latter chosen to outlast the DHS verif_timeout_ms so a
genuine 1,8 timeout also fails the stage). A rejected or timed-out command produces no success
report, so the stage’s window expires and the command is marked failed; the _Failure containers
(1,2 / 1,8) still decode as telemetry with their ver_failure_code reason.
Three deliberate gaps. There’s no StartedVerifier anywhere — 1,3/1,4 are emitted only for
telecommands the schedule releases, which aren’t the MetaCommand instance Yamcs is tracking; the
two TM_Verif_Start_* containers exist so those reports decode, not so a stage waits on them.
TC_OBC_Switch, TC_ACS_Switch and TC_TTC_Switch (8/2) bypass the DHS and generate no report,
so they carry no verifiers. TC_Downlink_File gets acceptance only, since the payload’s
file-stored ack is consumed by the DHS downlink path rather than producing a 1,7.
Why a container verifier cannot correlate a report to a specific command instance — and what the
decomposed request id (ver_req_pid / ver_req_seqflags / ver_req_seqcount) is there for — is in
yamcs.md.
The mission data link
Service 23 and the SpaceWire link are modelled like anything else: TM_File_Attr_Report (23,4) and
TM_Repo_Summary (23,13), the five TC_File_* metacommands, and SpwLinkState / XferState /
FileRepo / FileName in the type sets. Two things to know when editing:
- The
TC_File_*metacommands carry noFixedValueEntryopcode octet. Unlike the service-8 telecommands, service 23 executes wholly on the DHS and never becomes a CAN command, so the body is the arguments and nothing else. Services 20 and 11 are built the same way. TC_File_Copy’s completion window is 30 s, not the usual 8. APAYLOAD -> SSMMcopy completes only on the payload’s CAN ack, which can be queued behind a readout already in progress.
TM_DHS_Platform_HK grew from Packet_Length 65 to 77 when the link’s six parameters joined the
DHS’s own housekeeping, and to 80 with the three software-version octets — why every container
in the file states its length in a comment beside it.
Validate and load
# Well-formedness + schema validation against the XTCE 1.2 XSD
curl -fsSLO https://raw.githubusercontent.com/yamcs/yamcs/master/yamcs-xtce/SpaceSystemV1.2.xsd
xmllint --noout --schema SpaceSystemV1.2.xsd mdb/minscs.xtce # -> "mdb/minscs.xtce validates"The XTCE loader stanza, the TM/TC data links and the end-to-end check are in
yamcs.md.
Keeping it in sync
This file is hand-authored and must track the C sources. When a packet changes, update:
| Change | Source of truth | XTCE section |
|---|---|---|
| APID / service / subtype constant | include/pus.h | container RestrictionCriteria |
| Command opcode | include/shared_can.h (CMD_*) | FixedValueEntry in the TC container |
| TM byte layout | tools/egse_tm.c (prvDecodeTm) | TM EntryList + ParameterSet |
| TC byte layout | tools/egse_tc.c (prvEncodeVerb, prvDispatchSched, prvDispatchFile) | TC CommandContainer EntryList |
| Verification failure code | include/pus.h (PUS_VERIF_ERR_*) | VerifFailCode enumeration |
| Parameter value type / derivation operator | include/datapool.h (PUS_PARAM_TYPE_*, DP_OP_*) | ParamType / ParamOp enumerations |
| A distributable subsystem parameter | include/datapool.h (DP_CFG_CATALOG) | nothing — see below |
| The simulator release | include/version.h | <Header version="…"> |
Deliberately not here: the on-board parameter catalog (DP_PID_CATALOG). Its identifiers are
data carried inside TM_Param_Value_Report, not packet layouts, and the pool is editable in
flight — a ground-defined parameter has no XTCE definition at all. A mission control system keeps
its own list, or reads one back with TC_Param_Dump.