Protocol Specification
The apollo.protocol module provides three categories of utility functions:
- Sync word functions — generate, parse, and convert the 32-bit PCM frame sync pattern
- AGC I/O packet functions — encode and decode the 4-byte Virtual AGC socket protocol
- A/D conversion functions — translate between 8-bit ADC codes and voltages
from apollo.protocol import ( generate_sync_word, parse_sync_word, sync_word_to_bytes, sync_word_to_bits, bits_to_sync_word, form_io_packet, parse_io_packet, adc_to_voltage, voltage_to_adc,)Sync Word Functions
Section titled “Sync Word Functions”The PCM frame sync word is a 32-bit pattern (4 words) at the start of every telemetry frame. Its bit layout:
Bit 31 27 26 12 11 6 5 0 ┌──────────────┬──────────────┬───────────┬───────────┐ │ A field (5) │ Core (15) │ B field(6)│Frame ID(6)│ └──────────────┴──────────────┴───────────┴───────────┘ MSB LSBThe 15-bit core is bitwise-complemented on odd-numbered frames, providing a built-in frame parity indicator.
generate_sync_word
Section titled “generate_sync_word”Generate a 32-bit PCM frame sync word as an integer.
from apollo.protocol import generate_sync_word
word = generate_sync_word(frame_id=1, odd=False)# Returns: 0xACEB4001 (with default field values)Signature
Section titled “Signature”def generate_sync_word( frame_id: int, odd: bool = False, a_bits: int = DEFAULT_SYNC_A, # 0b10101 = 21 core: int = DEFAULT_SYNC_CORE, # 0b111001101011100 = 29404 b_bits: int = DEFAULT_SYNC_B, # 0b110100 = 52) -> intParameters
Section titled “Parameters”| Parameter | Type | Default | Description |
|---|---|---|---|
frame_id | int | — (required) | Frame number within the subframe. Range: 1-50 |
odd | bool | False | If True, the 15-bit core is bitwise complemented |
a_bits | int | 0b10101 (21) | 5-bit patchboard-selectable A field. Only lower 5 bits used |
core | int | 0b111001101011100 (29404) | 15-bit fixed core pattern (even-frame value). Only lower 15 bits used |
b_bits | int | 0b110100 (52) | 6-bit patchboard-selectable B field. Only lower 6 bits used |
Returns
Section titled “Returns”A 32-bit integer: (a << 27) | (core << 12) | (b << 6) | frame_id
When odd=True, the core value is replaced with (~core) & 0x7FFF before assembly.
Raises
Section titled “Raises”ValueErrorifframe_idis not in range 1-50.
parse_sync_word
Section titled “parse_sync_word”Parse a 32-bit sync word integer into its component fields.
from apollo.protocol import parse_sync_word
fields = parse_sync_word(0xACEB4001)# Returns: {'a_bits': 21, 'core': 29404, 'b_bits': 52, 'frame_id': 1, 'word': 0xACEB4001}Signature
Section titled “Signature”def parse_sync_word(word: int) -> dictParameters
Section titled “Parameters”| Parameter | Type | Description |
|---|---|---|
word | int | 32-bit sync word value |
Returns
Section titled “Returns”| Key | Type | Bit Range | Description |
|---|---|---|---|
a_bits | int | 31-27 | 5-bit A field |
core | int | 26-12 | 15-bit core (as-is, not un-complemented) |
b_bits | int | 11-6 | 6-bit B field |
frame_id | int | 5-0 | 6-bit frame ID |
word | int | — | Original 32-bit word (pass-through) |
sync_word_to_bytes
Section titled “sync_word_to_bytes”Convert a 32-bit sync word to 4 bytes, MSB first (matching NRZ serial output order).
from apollo.protocol import sync_word_to_bytes
raw = sync_word_to_bytes(0xACEB4001)# Returns: b'\xac\xeb\x40\x01'Signature
Section titled “Signature”def sync_word_to_bytes(word: int) -> bytesParameters
Section titled “Parameters”| Parameter | Type | Description |
|---|---|---|
word | int | 32-bit sync word value |
Returns
Section titled “Returns”4 bytes in big-endian (MSB first) order.
sync_word_to_bits
Section titled “sync_word_to_bits”Convert a 32-bit sync word to a list of 32 individual bit values, MSB first.
from apollo.protocol import sync_word_to_bits
bits = sync_word_to_bits(0xACEB4001)# Returns: [1, 0, 1, 0, 1, 1, 0, 0, 1, 1, 1, 0, 1, 0, 1, 1, ...] (32 elements)Signature
Section titled “Signature”def sync_word_to_bits(word: int) -> list[int]Parameters
Section titled “Parameters”| Parameter | Type | Description |
|---|---|---|
word | int | 32-bit sync word value |
Returns
Section titled “Returns”List of 32 integers (each 0 or 1), bit 31 first.
bits_to_sync_word
Section titled “bits_to_sync_word”Convert a list of 32 bit values back to a 32-bit integer. Inverse of sync_word_to_bits.
from apollo.protocol import bits_to_sync_word
word = bits_to_sync_word([1, 0, 1, 0, ...]) # 32 bitsSignature
Section titled “Signature”def bits_to_sync_word(bits: list[int]) -> intParameters
Section titled “Parameters”| Parameter | Type | Description |
|---|---|---|
bits | list[int] | Exactly 32 bit values (0 or 1), MSB first |
Returns
Section titled “Returns”32-bit integer.
Raises
Section titled “Raises”ValueErroriflen(bits) != 32.
Virtual AGC I/O Packet Protocol
Section titled “Virtual AGC I/O Packet Protocol”The Apollo Guidance Computer emulator (yaAGC) communicates over TCP using a 4-byte packet format. Each packet carries one I/O channel update: a 9-bit channel number and a 15-bit data value.
These functions are direct ports of FormIoPacket() and ParseIoPacket() from yaAGC/SocketAPI.c in the Virtual AGC project.
Packet Bit Layout
Section titled “Packet Bit Layout” Byte 0 Byte 1 Byte 2 Byte 3 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 ┌─┬─┬─────────┐ ┌─┬─┬─────┬─────┐ ┌─┬─┬───────────┐ ┌─┬─┬───────────┐ │0│0│Ch[8:3] │ │0│1│Ch[2:0]│V[14:12]│ │1│0│ V[11:6] │ │1│1│ V[5:0] │ └─┴─┴─────────┘ └─┴─┴─────┴─────┘ └─┴─┴───────────┘ └─┴─┴───────────┘ sig=0x00 sig=0x40 sig=0x80 sig=0xC0| Byte | Signature (bits 7-6) | Data Bits | Content |
|---|---|---|---|
| 0 | 00 | bits 5-0 | Channel bits 8-3 |
| 1 | 01 | bits 5-3: channel 2-0, bits 2-0: value 14-12 | Channel low bits + value high bits |
| 2 | 10 | bits 5-0 | Value bits 11-6 |
| 3 | 11 | bits 5-0 | Value bits 5-0 |
The 2-bit signature prefix on each byte enables packet resynchronization after data loss.
form_io_packet
Section titled “form_io_packet”Encode a channel/value pair into a 4-byte Virtual AGC I/O packet.
from apollo.protocol import form_io_packet
packet = form_io_packet(channel=0o45, value=0x1234)# Returns: b'\x04\x49\x88\xf4' (4 bytes)Signature
Section titled “Signature”def form_io_packet(channel: int, value: int, u_bit: bool = False) -> bytesParameters
Section titled “Parameters”| Parameter | Type | Default | Range | Description |
|---|---|---|---|---|
channel | int | — (required) | 0-511 (9 bits) | I/O channel number. Values beyond 9 bits are masked |
value | int | — (required) | 0-32767 (15 bits) | Data value. Values beyond 15 bits are masked |
u_bit | bool | False | — | If True, sets bit 5 in byte 3 data field, marking this as a mask update rather than data. This is a yaAGC extension |
Returns
Section titled “Returns”4 bytes following the packet format above.
Encoding Details
Section titled “Encoding Details”b0 = (channel >> 3) & 0x3Fb1 = 0x40 | ((channel & 0x07) << 3) | ((value >> 12) & 0x07)b2 = 0x80 | ((value >> 6) & 0x3F)b3 = 0xC0 | (value & 0x3F)if u_bit: b3 |= 0x20parse_io_packet
Section titled “parse_io_packet”Decode a 4-byte Virtual AGC I/O packet into channel, value, and u_bit.
from apollo.protocol import parse_io_packet
channel, value, u_bit = parse_io_packet(b'\x04\x49\x88\xf4')# Returns: (37, 4660, False)Signature
Section titled “Signature”def parse_io_packet(packet: bytes) -> tuple[int, int, bool]Parameters
Section titled “Parameters”| Parameter | Type | Description |
|---|---|---|
packet | bytes | Exactly 4 bytes |
Returns
Section titled “Returns”A tuple of:
| Index | Type | Description |
|---|---|---|
| 0 | int | Channel number (0-511) |
| 1 | int | Data value (0-32767) |
| 2 | bool | u_bit flag (always False in current implementation — standard data packets) |
Raises
Section titled “Raises”ValueErroriflen(packet) != 4ValueErrorif any byte has an invalid signature prefix (bits 7-6 must follow the00,01,10,11sequence)
Decoding Details
Section titled “Decoding Details”channel = ((b0 & 0x3F) << 3) | ((b1 >> 3) & 0x07)value = ((b1 & 0x07) << 12) | ((b2 & 0x3F) << 6) | (b3 & 0x3F)AGC Telecom Channel Reference
Section titled “AGC Telecom Channel Reference”Complete table of AGC I/O channels relevant to the telecommunications system:
| Octal | Decimal | Constant | Direction | Purpose |
|---|---|---|---|---|
| 010 | 8 | AGC_CH_OUT0 | Output | Relay rows |
| 011 | 9 | AGC_CH_DSALMOUT | Output | DSKY alarm indicators |
| 013 | 11 | AGC_CH_CHAN13 | Output | Radar activity |
| 030 | 24 | AGC_CH_CHAN30 | Input | Status and alarm bits |
| 033 | 27 | AGC_CH_CHAN33 | Input | AGC warning input |
| 034 | 28 | AGC_CH_DNTM1 | Output | Downlink telemetry word 1 (high 7 bits) |
| 035 | 29 | AGC_CH_DNTM2 | Output | Downlink telemetry word 2 (low 8 bits) |
| 045 | 37 | AGC_CH_INLINK | Input | Uplink data from ground (triggers UPRUPT) |
| 057 | 47 | AGC_CH_OUTLINK | Output | Digital downlink data |
The AGC_TELECOM_CHANNELS frozenset contains channels 034, 035, 045, and 057 (the four primary telecom channels). The AGCBridgeClient uses this set as its default channel filter.
A/D Conversion Functions
Section titled “A/D Conversion Functions”The Apollo PCM encoder uses an 8-bit analog-to-digital converter with a non-standard code mapping. Per IMPL_SPEC section 5.3:
| Input Voltage | ADC Code | Binary |
|---|---|---|
| Below range | 0 | 00000000 |
| 0V | 1 | 00000001 |
| 4.98V (full scale) | 254 | 11111110 |
| >5V (overflow) | 255 | 11111111 |
Step size: 4.98V / 253 = 19.7 mV per LSB.
For low-level analog inputs (0-40 mV range), a x125 gain amplifier is applied before the ADC. The conversion functions handle this gain transparently.
adc_to_voltage
Section titled “adc_to_voltage”Convert an 8-bit ADC code to a voltage.
from apollo.protocol import adc_to_voltage
v = adc_to_voltage(128)# Returns: 2.4980... (approximately 2.5V, midscale)
v_low = adc_to_voltage(128, low_level=True)# Returns: 0.01998... (approximately 20 mV, after removing x125 gain)Signature
Section titled “Signature”def adc_to_voltage(code: int, low_level: bool = False) -> floatParameters
Section titled “Parameters”| Parameter | Type | Default | Description |
|---|---|---|---|
code | int | — (required) | 8-bit ADC value (0-255) |
low_level | bool | False | If True, divide result by 125 to recover actual input voltage for low-level (0-40 mV) channels |
Returns
Section titled “Returns”Voltage in volts (float).
Edge Cases
Section titled “Edge Cases”| Code | Returns | Reason |
|---|---|---|
| 0 | 0.0 | Below range |
| ≥ 255 | 5.0 | Overflow |
| 1-254 | (code - 1) * 4.98 / 253 | Normal conversion |
voltage_to_adc
Section titled “voltage_to_adc”Convert a voltage to an 8-bit ADC code. Inverse of adc_to_voltage.
from apollo.protocol import voltage_to_adc
code = voltage_to_adc(2.5)# Returns: 128
code = voltage_to_adc(0.020, low_level=True)# Returns: 128 (0.020V * 125 = 2.5V internal)Signature
Section titled “Signature”def voltage_to_adc(voltage: float, low_level: bool = False) -> intParameters
Section titled “Parameters”| Parameter | Type | Default | Description |
|---|---|---|---|
voltage | float | — (required) | Input voltage in volts |
low_level | bool | False | If True, apply x125 gain before conversion (for 0-40 mV inputs) |
Returns
Section titled “Returns”8-bit ADC code (int), clamped to range 1-254.
Edge Cases
Section titled “Edge Cases”| Voltage | Returns | Reason |
|---|---|---|
| ≤ 0.0 | 1 | Zero code |
| ≥ 4.98 | 254 | Full-scale code |
| Between | round(voltage * 253 / 4.98) + 1 | Normal conversion, clamped to 1-254 |
Conversion Formula
Section titled “Conversion Formula”If low_level: voltage = voltage * 125code = round(voltage * 253 / 4.98) + 1code = clamp(code, 1, 254)Usage Patterns
Section titled “Usage Patterns”Round-Trip Sync Word Test
Section titled “Round-Trip Sync Word Test”from apollo.protocol import generate_sync_word, parse_sync_word, sync_word_to_bits, bits_to_sync_word
# Generate even frame 1word = generate_sync_word(frame_id=1, odd=False)fields = parse_sync_word(word)assert fields["frame_id"] == 1
# Round-trip through bitsbits = sync_word_to_bits(word)assert len(bits) == 32recovered = bits_to_sync_word(bits)assert recovered == wordAGC Packet Round-Trip
Section titled “AGC Packet Round-Trip”from apollo.protocol import form_io_packet, parse_io_packet
# Encode an uplink command to INLINK channelpacket = form_io_packet(channel=0o45, value=0x5A00)channel, value, u_bit = parse_io_packet(packet)assert channel == 0o45 # 37 decimalassert value == 0x5A00ADC Voltage Scaling
Section titled “ADC Voltage Scaling”from apollo.protocol import adc_to_voltage, voltage_to_adc
# Standard high-level channelfor code in [1, 64, 128, 192, 254]: v = adc_to_voltage(code) back = voltage_to_adc(v) assert abs(back - code) <= 1 # rounding tolerance
# Low-level channel (0-40 mV)v = adc_to_voltage(128, low_level=True) # ~20 mVcode = voltage_to_adc(v, low_level=True)assert code == 128