Skip to content

PAT/PMT cadence

MISB ST 1402.2 requirement ST 1402-02 requires both the Program Association Table and every Program Map Table to occur more than four times per second throughout a program. Eight repetitions per second are recommended. Therefore, an interval equal to 250 ms is not compliant; the recommended interval is 125 ms.

Writing

ProgramTableScheduler wraps a TransportMuxer and preserves an exact rational schedule without cumulative floating-point drift:

from time import monotonic

from stanag4609 import ProgramTableScheduler

scheduler = ProgramTableScheduler(muxer)

while running:
    emission = scheduler.poll(at=monotonic())
    if emission is not None:
        transport_sink.write(b"".join(emission.packets))
        if not emission.interval_compliant:
            metrics.increment("st1402.psi_interval_violation")

Poll at least every 125 ms to meet the recommended cadence. The first poll emits immediately. A late poll emits one current PAT/PMT pair rather than a burst of stale repetitions. missed_repetitions, late_by, and interval_compliant make the resulting timing quality explicit. Continuity counters advance through the wrapped muxer exactly as they do for manual table emission.

Calling reset() reanchors scheduling and clears metrics but intentionally does not rewind the muxer's continuity counters. A custom interval is accepted only when positive and strictly below 250 ms.

LiveTransportTransformer owns this scheduler in timed mode. Pass the same monotonic clock to feed(..., at=...), emit_metadata(..., at=...), and poll_program_tables(at=...). Untimed calls intentionally continue to mirror source PAT/PMT cadence for compatibility.

Receiving and auditing

PSICadenceValidator accepts integer, finite float, or exact Fraction seconds from any one monotonic timeline. Use host monotonic time for a live receiver or PCR-derived ProgramClockReference.seconds for deterministic recording audits.

Call start(at=...) when the beginning of a program is known. This allows check(at=...) to diagnose a PAT that never arrived. Without an explicit start, the first observation or check establishes the monitoring baseline. Observing a current PAT discovers the PMTs that must recur; removed programs stop producing PMT diagnostics and newly announced programs receive independent acquisition deadlines.

Only current tables count. For a multi-section PAT, every section in the cycle must arrive before the table occurrence satisfies cadence. H.222.0 requires a program definition to fit one TS_program_map_section, with both PMT section numbers set to zero. check() is a snapshot and may be called by an idle timer to diagnose silence even when no transport events are arriving.

TransportDemuxer emits a PATEvent only after a complete current PAT cycle is available. Iterate event.sections when passing that cycle to this validator; event.programs is the combined program loop and event.table remains the section-zero compatibility view.

For a finite recording without an external arrival clock, use PCRBracketedPSICadenceValidator. Feed PATEvent, PMTEvent, and ProgramClockEvent objects in demux order:

from stanag4609 import PCRBracketedPSICadenceValidator, TransportDemuxer

demuxer = TransportDemuxer()
cadence = PCRBracketedPSICadenceValidator()

for event in demuxer.feed(transport_bytes):
    for issue in cadence.observe(event):
        print(issue.table, issue.minimum_interval, issue.current_source_offset)

The adapter never estimates packet time from file byte position or an assumed bit rate. Instead, each table occurrence receives the PCR interval that bounds its arrival. It reports only when the smallest possible separation between two occurrences is at least 250 ms. Thus, an uncertain interval is left unreported, an exact 250 ms minimum fails the strict “more than four times per second” rule, and clock discontinuities, regressions, or PCR-PID changes reset the proof. Pending occurrences are bounded per program for live use. This conservative receiver is integrated into FMVVerifier.