PCM Telemetry Decommutation (flight-test-operations/planning/pcm-telemetry-decommutation)
Use when the task is decoding a serial PCM minor-frame telemetry
stream at the ground station decode side: finding the sync word in the
captured word stream, locking the minor frames, counting sync misses,
and splitting each locked frame back into the fixed,
supercommutated and subcommutated channel time series that feed data
reduction. This leaf implements the standard decommutation walk in
pure Python, stdlib only. It pairs with
flight-test-operations/planning/telemetry-data-acquisition (the design
leaf, which sizes the frame and assigns the channels and stops at the
transmitter) and with
flight-test-operations/planning/flight-test-data-reduction (the post
decomm processing leaf that calibrates, aligns and smooths these
recovered series).
Domain quick reference
- Wire format: a minor frame on the wire is the sync word followed by
the data words, then the idle words before the next sync word. The
frame period in words is 1 + data_words_per_frame + idle_words, and
frame_period_words(data_words_per_frame, idle_words) returns that
period. Example: 8 data words and 1 idle word give a 10 word
period.
- Frame sync acquisition: the sync word is a fixed bit pattern that
repeats at every frame boundary. sync_search finds its first
occurrence at or after a start index; acquire_lock reports
first_sync_index and scan_length (words examined up to and including
the sync word) for the first lock in the stream, or raises
ValueError when no sync word exists in the scanned region.
- Locked-frame walk: with the frame lock at first_sync_index, the next
sync word is expected one frame period later. At each expected sync
position, a word equal to the sync word locks the frame and the
following data_words_per_frame words are recorded; any other word is
a sync miss, and the walk resyncs by scanning forward from the
missed position, bounded to the next two frame periods.
- Fixed channel: one word slot sampled every frame, so it yields one
value per locked frame.
- Supercommutation: a channel occupies multiple word slots per frame
(a list of slot indices) and yields one sample per slot, so it
yields len(slots) values per locked frame, in slot order.
- Subcommutation: a channel occupies one word slot that carries M
sub-values, cycled across frames. A designated subframe id word,
masked to its low bits, selects which sub-value each frame carries;
the sample joins the per-subframe value list for that id.
- Subframe id: read from the frame data word at sid_word_index and
masked with sid_mask, so the recovered trace is the id itself for
every locked frame.
- All channel values are recovered as transmitted: no calibration,
alignment or smoothing happens here (those belong to
flight-test-data-reduction).
Workflow
- State the format: data_words_per_frame and idle_words, and confirm
the period with frame_period_words(data_words_per_frame,
idle_words).
- Acquire the frame lock: acquire_lock(stream, sync_word,
data_words_per_frame, idle_words, max_scan) returns
first_sync_index and scan_length; a ValueError means the stream
holds no sync word in the scanned region and cannot be decoded.
- Walk the frames: decode_frames(stream, sync_word,
data_words_per_frame, idle_words) returns frames_locked,
sync_misses and frame_data, the per-frame data word lists of every
locked frame. Corrupted sync words drop the affected frame and add
one to sync_misses.
- Demultiplex with demultiplex(frame_data, sid_word_index, sid_mask,
supercommutated, subcommutated): pass the supercommutated map
{channel: [word slots]} and the subcommutated map {channel:
{"word_index": i, "subframes": M}}. Recover super[channel] (values
in frame order then slot order), sub[channel][subframe_id] (values
per subframe id) and subframe_ids.
- For a one-call decode, run decommutation_summary(stream, sync_word,
data_words_per_frame, idle_words, format) with the format dict
{"sid_word_index", "sid_mask", "supercommutated", "subcommutated"};
it returns frames_locked, sync_misses, super, sub and subframe_ids
together.
- Hand the recovered per-channel series to flight-test-data-reduction
for its post-decomm processing (its correction, alignment and
smoothing steps).
- Confirm the deterministic checks with the contract test
scripts/test_pcm_telemetry_decommutation.py.
Worked example
16-bit words, sync word 0xEB90, 8 data words per frame, 1 idle word,
so the frame period is 10 words. Frame k (k = 0..39) carries: word0 =
k & 3 (subframe id), word1 = 1000 + k (channel A, fixed), word2 =
2000 + (k & 3) * 100 + (k // 4) (channel S, subcommutated, 4
subframes), word3 = 3000 + k (channel A2, supercommutated slot 1),
word4 = 4000 + k, word5 = 5000 + k (channel A2, supercommutated slot
2), word6 = 6000 + k, word7 = 7000 + k. Idle word 0x0000. The wire
stream is the 40 frames concatenated (400 words).
Real module outputs on this fixture:
- frame_period_words(8, 1) = 10.
- acquire_lock: first_sync_index = 0, scan_length = 1 (clean, aligned
stream).
- decode_frames: frames_locked = 40, sync_misses = 0, and every
frame_data[k] equals the transmitted data words of frame k
elementwise.
- demultiplex with sid_word_index 0, sid_mask 0x0003,
supercommutated {"A": [1], "A2": [3, 5]}, subcommutated {"S":
{"word_index": 2, "subframes": 4}}:
- super A = [1000, 1001, ..., 1039] (40 values).
- super A2 = [3000, 5000, 3001, 5001, ..., 3039, 5039] (80 values,
frame order then slot order).
- sub S: subframe 0 = [2000..2009], subframe 1 = [2100..2109],
subframe 2 = [2200..2209], subframe 3 = [2300..2309] (10 values
each).
- subframe_ids = [0, 1, 2, 3, 0, 1, ...] (40 ids, cycling).
Corruption check on the same 40-frame stream with frame 20 sync word
replaced by 0xFFFF: frames_locked = 39, sync_misses = 1; channel A
recovers 39 values (the 1020 sample is dropped with frame 20);
channel A2 recovers 78 values (3020 and 5020 dropped); subframe 0 of
channel S recovers 9 values (the frame 20 sample, value 2005, is
dropped).
Pitfalls
- Demultiplexing without the frame layout: supercommutated channels
interleave in frame order then slot order (A2 reads [3000, 5000,
3001, 5001, ...] for slots [3, 5]), while subcommutated channels key
on the subframe id carried in the sid word.
- Reading the subframe id with the wrong mask or word index: the ids
cycle through 0..3 under sid_mask 0x0003 at sid_word_index 0, and an
out-of-range sid_word_index raises ValueError.
- Ignoring sync corruption in the recovery: a corrupted sync word drops
the whole frame from every channel (39 locked with 1 miss), so
channel A recovers 39 values and subframe 0 of channel S only 9 -
downstream analysis must expect the missing samples.
- Locking onto an unaligned stream without the scan bound: three junk
words prepended shift first_sync_index to 3 with the same recovered
frames, and max_scan bounds the sync search.
- Feeding malformed format or subcommutation dicts: empty frame lists,
ragged frame lengths, supercommutated slots outside the frame,
subframes below 1, and subcommutated entries missing their
word_index or subframes keys all raise ValueError.
- Recomputing the frame period from the data words alone: the period is
data plus idle words (8 + 1 = 10 here), and the layout must match the
transmitter-side design owned by telemetry-data-acquisition.
Verification
- Confirm frame_period_words(8, 1) = 10, (8, 0) = 9, (0, 0) = 1, and
that negative data_words_per_frame or idle_words raises ValueError.
- Confirm sync_search finds the first sync word at or after start and
returns None when the sync word is absent; empty streams and
negative sync words raise ValueError.
- Confirm acquire_lock on the clean stream reports first_sync_index 0;
three junk words prepended shift it to 3 with the same recovered
frames.
- Confirm on a clean stream every transmitted frame is locked
(sync_misses == 0) and the recovered channel values equal the
transmitted ramp values elementwise (deterministic identity).
- Confirm a frame whose sync word is corrupted is not locked and
increments sync_misses by one, with its samples absent from every
recovered channel.
- Confirm demultiplex raises ValueError on an empty frame list, an
out-of-range sid_word_index, a ragged frame length, a supercommutated
slot outside the frame, subframes below 1, and subcommutated entries
missing their word_index or subframes keys.
- Confirm decommutation_summary returns exactly the keys frames_locked,
sync_misses, super, sub, subframe_ids and matches the decode then
demultiplex result.
- Run the contract test offline: python3
scripts/test_pcm_telemetry_decommutation.py (27 tests,
deterministic).
Related leaves
- flight-test-operations/planning/telemetry-data-acquisition: the
design side, sizes the PCM frame, assigns the supercommutated and
subcommutated channels and stops at the transmitter.
- flight-test-operations/planning/flight-test-data-reduction: the post
decomm processing side, calibrates, aligns and smooths the series
this leaf recovers.
- flight-test-operations/planning/flight-test-instrumentation: the
sensor and recorder chain upstream of the telemetry stream.
Behavior contract (gate 3)
Run the deterministic contract test (stdlib unittest, offline):
python3 scripts/test_pcm_telemetry_decommutation.py
The test covers the frame period arithmetic, sync word search, frame
lock acquisition on clean and unaligned streams with max_scan bounds,
the locked-frame walk on clean and corrupted streams (40 frames
locked with zero misses versus 39 locked with one miss), fixed and
supercommutated channel recovery including the interleaved multi slot
channel, subcommutated recovery keyed by the subframe id, the
subframe id trace, the elementwise ramp identity, the corruption
dropped-sample checks, and ValueError rejection of empty streams, no
sync word, negative frame words, out-of-range slots and malformed
format and subcommutation dicts.
Compliance
- Standards referenced, not reproduced: FAR-25 sets the flight test
and certification context; PCM decommutation practice (frame sync
acquisition, minor frame locking, supercommutation and
subcommutation demultiplexing) is common telemetry methodology,
summary-only per standards-map.yaml.
- compliance: STANDARDS-REF, gated: false.
1---2name: pcm-telemetry-decommutation3description: Use when you must decommutate a serial PCM telemetry stream: acquire frame sync by locating the sync word in the captured word stream, walk the locked minor frames and count sync misses, and demultiplex the recovered telemetry channels into time series, with fixed channels read from their word slot every frame, supercommutated channels read from their multiple word slots per frame, and subcommutated channels keyed by the subframe id into the per-subframe value lists. Produce the locked frame count, the sync miss report, the subframe id trace, and the recovered per-channel time series that feed data reduction. Trigger: pcm telemetry decommutation, pcm frame sync, supercommutated demux, subcommutated demux, minor frame decode.4license: Apache-2.05---67# PCM Telemetry Decommutation (flight-test-operations/planning/pcm-telemetry-decommutation)89Use when the task is decoding a serial PCM minor-frame telemetry10stream at the ground station decode side: finding the sync word in the11captured word stream, locking the minor frames, counting sync misses,12and splitting each locked frame back into the fixed,13supercommutated and subcommutated channel time series that feed data14reduction. This leaf implements the standard decommutation walk in15pure Python, stdlib only. It pairs with16flight-test-operations/planning/telemetry-data-acquisition (the design17leaf, which sizes the frame and assigns the channels and stops at the18transmitter) and with19flight-test-operations/planning/flight-test-data-reduction (the post20decomm processing leaf that calibrates, aligns and smooths these21recovered series).2223## Domain quick reference2425- Wire format: a minor frame on the wire is the sync word followed by26 the data words, then the idle words before the next sync word. The27 frame period in words is 1 + data_words_per_frame + idle_words, and28 frame_period_words(data_words_per_frame, idle_words) returns that29 period. Example: 8 data words and 1 idle word give a 10 word30 period.31- Frame sync acquisition: the sync word is a fixed bit pattern that32 repeats at every frame boundary. sync_search finds its first33 occurrence at or after a start index; acquire_lock reports34 first_sync_index and scan_length (words examined up to and including35 the sync word) for the first lock in the stream, or raises36 ValueError when no sync word exists in the scanned region.37- Locked-frame walk: with the frame lock at first_sync_index, the next38 sync word is expected one frame period later. At each expected sync39 position, a word equal to the sync word locks the frame and the40 following data_words_per_frame words are recorded; any other word is41 a sync miss, and the walk resyncs by scanning forward from the42 missed position, bounded to the next two frame periods.43- Fixed channel: one word slot sampled every frame, so it yields one44 value per locked frame.45- Supercommutation: a channel occupies multiple word slots per frame46 (a list of slot indices) and yields one sample per slot, so it47 yields len(slots) values per locked frame, in slot order.48- Subcommutation: a channel occupies one word slot that carries M49 sub-values, cycled across frames. A designated subframe id word,50 masked to its low bits, selects which sub-value each frame carries;51 the sample joins the per-subframe value list for that id.52- Subframe id: read from the frame data word at sid_word_index and53 masked with sid_mask, so the recovered trace is the id itself for54 every locked frame.55- All channel values are recovered as transmitted: no calibration,56 alignment or smoothing happens here (those belong to57 flight-test-data-reduction).5859## Workflow60611. State the format: data_words_per_frame and idle_words, and confirm62 the period with frame_period_words(data_words_per_frame,63 idle_words).642. Acquire the frame lock: acquire_lock(stream, sync_word,65 data_words_per_frame, idle_words, max_scan) returns66 first_sync_index and scan_length; a ValueError means the stream67 holds no sync word in the scanned region and cannot be decoded.683. Walk the frames: decode_frames(stream, sync_word,69 data_words_per_frame, idle_words) returns frames_locked,70 sync_misses and frame_data, the per-frame data word lists of every71 locked frame. Corrupted sync words drop the affected frame and add72 one to sync_misses.734. Demultiplex with demultiplex(frame_data, sid_word_index, sid_mask,74 supercommutated, subcommutated): pass the supercommutated map75 {channel: [word slots]} and the subcommutated map {channel:76 {"word_index": i, "subframes": M}}. Recover super[channel] (values77 in frame order then slot order), sub[channel][subframe_id] (values78 per subframe id) and subframe_ids.795. For a one-call decode, run decommutation_summary(stream, sync_word,80 data_words_per_frame, idle_words, format) with the format dict81 {"sid_word_index", "sid_mask", "supercommutated", "subcommutated"};82 it returns frames_locked, sync_misses, super, sub and subframe_ids83 together.846. Hand the recovered per-channel series to flight-test-data-reduction85 for its post-decomm processing (its correction, alignment and86 smoothing steps).877. Confirm the deterministic checks with the contract test88 scripts/test_pcm_telemetry_decommutation.py.8990## Worked example919216-bit words, sync word 0xEB90, 8 data words per frame, 1 idle word,93so the frame period is 10 words. Frame k (k = 0..39) carries: word0 =94k & 3 (subframe id), word1 = 1000 + k (channel A, fixed), word2 =952000 + (k & 3) * 100 + (k // 4) (channel S, subcommutated, 496subframes), word3 = 3000 + k (channel A2, supercommutated slot 1),97word4 = 4000 + k, word5 = 5000 + k (channel A2, supercommutated slot982), word6 = 6000 + k, word7 = 7000 + k. Idle word 0x0000. The wire99stream is the 40 frames concatenated (400 words).100101Real module outputs on this fixture:102103- frame_period_words(8, 1) = 10.104- acquire_lock: first_sync_index = 0, scan_length = 1 (clean, aligned105 stream).106- decode_frames: frames_locked = 40, sync_misses = 0, and every107 frame_data[k] equals the transmitted data words of frame k108 elementwise.109- demultiplex with sid_word_index 0, sid_mask 0x0003,110 supercommutated {"A": [1], "A2": [3, 5]}, subcommutated {"S":111 {"word_index": 2, "subframes": 4}}:112 - super A = [1000, 1001, ..., 1039] (40 values).113 - super A2 = [3000, 5000, 3001, 5001, ..., 3039, 5039] (80 values,114 frame order then slot order).115 - sub S: subframe 0 = [2000..2009], subframe 1 = [2100..2109],116 subframe 2 = [2200..2209], subframe 3 = [2300..2309] (10 values117 each).118 - subframe_ids = [0, 1, 2, 3, 0, 1, ...] (40 ids, cycling).119120Corruption check on the same 40-frame stream with frame 20 sync word121replaced by 0xFFFF: frames_locked = 39, sync_misses = 1; channel A122recovers 39 values (the 1020 sample is dropped with frame 20);123channel A2 recovers 78 values (3020 and 5020 dropped); subframe 0 of124channel S recovers 9 values (the frame 20 sample, value 2005, is125dropped).126127## Pitfalls128129- Demultiplexing without the frame layout: supercommutated channels130 interleave in frame order then slot order (A2 reads [3000, 5000,131 3001, 5001, ...] for slots [3, 5]), while subcommutated channels key132 on the subframe id carried in the sid word.133- Reading the subframe id with the wrong mask or word index: the ids134 cycle through 0..3 under sid_mask 0x0003 at sid_word_index 0, and an135 out-of-range sid_word_index raises ValueError.136- Ignoring sync corruption in the recovery: a corrupted sync word drops137 the whole frame from every channel (39 locked with 1 miss), so138 channel A recovers 39 values and subframe 0 of channel S only 9 -139 downstream analysis must expect the missing samples.140- Locking onto an unaligned stream without the scan bound: three junk141 words prepended shift first_sync_index to 3 with the same recovered142 frames, and max_scan bounds the sync search.143- Feeding malformed format or subcommutation dicts: empty frame lists,144 ragged frame lengths, supercommutated slots outside the frame,145 subframes below 1, and subcommutated entries missing their146 word_index or subframes keys all raise ValueError.147- Recomputing the frame period from the data words alone: the period is148 data plus idle words (8 + 1 = 10 here), and the layout must match the149 transmitter-side design owned by telemetry-data-acquisition.150151## Verification152153- Confirm frame_period_words(8, 1) = 10, (8, 0) = 9, (0, 0) = 1, and154 that negative data_words_per_frame or idle_words raises ValueError.155- Confirm sync_search finds the first sync word at or after start and156 returns None when the sync word is absent; empty streams and157 negative sync words raise ValueError.158- Confirm acquire_lock on the clean stream reports first_sync_index 0;159 three junk words prepended shift it to 3 with the same recovered160 frames.161- Confirm on a clean stream every transmitted frame is locked162 (sync_misses == 0) and the recovered channel values equal the163 transmitted ramp values elementwise (deterministic identity).164- Confirm a frame whose sync word is corrupted is not locked and165 increments sync_misses by one, with its samples absent from every166 recovered channel.167- Confirm demultiplex raises ValueError on an empty frame list, an168 out-of-range sid_word_index, a ragged frame length, a supercommutated169 slot outside the frame, subframes below 1, and subcommutated entries170 missing their word_index or subframes keys.171- Confirm decommutation_summary returns exactly the keys frames_locked,172 sync_misses, super, sub, subframe_ids and matches the decode then173 demultiplex result.174- Run the contract test offline: python3175 scripts/test_pcm_telemetry_decommutation.py (27 tests,176 deterministic).177178## Related leaves179180- flight-test-operations/planning/telemetry-data-acquisition: the181 design side, sizes the PCM frame, assigns the supercommutated and182 subcommutated channels and stops at the transmitter.183- flight-test-operations/planning/flight-test-data-reduction: the post184 decomm processing side, calibrates, aligns and smooths the series185 this leaf recovers.186- flight-test-operations/planning/flight-test-instrumentation: the187 sensor and recorder chain upstream of the telemetry stream.188189## Behavior contract (gate 3)190191Run the deterministic contract test (stdlib unittest, offline):192193 python3 scripts/test_pcm_telemetry_decommutation.py194195The test covers the frame period arithmetic, sync word search, frame196lock acquisition on clean and unaligned streams with max_scan bounds,197the locked-frame walk on clean and corrupted streams (40 frames198locked with zero misses versus 39 locked with one miss), fixed and199supercommutated channel recovery including the interleaved multi slot200channel, subcommutated recovery keyed by the subframe id, the201subframe id trace, the elementwise ramp identity, the corruption202dropped-sample checks, and ValueError rejection of empty streams, no203sync word, negative frame words, out-of-range slots and malformed204format and subcommutation dicts.205206## Compliance207208- Standards referenced, not reproduced: FAR-25 sets the flight test209 and certification context; PCM decommutation practice (frame sync210 acquisition, minor frame locking, supercommutation and211 subcommutation demultiplexing) is common telemetry methodology,212 summary-only per standards-map.yaml.213- compliance: STANDARDS-REF, gated: false.