# CLAUDE.md This file provides guidance to Claude Code when working with gr-apollo. ## Project Overview GNU Radio 3.10+ out-of-tree (OOT) module for decoding **Apollo Unified S-Band (USB)** telecommunications signals. Based on the 1965 NAA Telecommunication Systems Study Guide (Course A-624) and Virtual AGC project. **Target signals:** - Downlink: 2287.5 MHz (spacecraft → ground) - Uplink: 2106.40625 MHz (ground → spacecraft) - Coherent ratio: 240/221 (Tx = Rx × 240/221) ## Repository Structure ``` gr-apollo/ ├── src/apollo/ # GNU Radio Python blocks (src-layout) │ ├── pm_demod.py # PM demodulator (0.133 rad peak) │ ├── bpsk_subcarrier_demod.py # 1.024 MHz BPSK subcarrier │ ├── pcm_frame_sync.py # 32-bit sync, 128-word frames │ ├── pcm_demux.py # Frame demultiplexer │ └── voice_subcarrier_demod.py # 1.25 MHz FM voice ├── grc/ # GRC block YAML definitions ├── docs/ # Reference documentation ├── examples/ # Example flowgraphs ├── pyproject.toml # Python package configuration └── README.md ``` ## Development Commands ```bash # Install in development mode uv pip install -e . # Install GRC block definitions cp grc/*.yml ~/.local/share/gnuradio/grc/blocks/ # Test with gr-mcp (if available) # Use protocol analysis tools to generate decoder chain ``` ## Signal Architecture ### Downlink Signal Chain (2287.5 MHz) ``` RF Input → Carrier PLL → PM Demod → Subcarrier Separation │ ┌───────────────────────┼───────────────────────┐ ↓ ↓ ↓ 1.024 MHz BPSK 1.25 MHz FM Ranging (PCM telemetry) (Voice) (PRN code) ↓ ↓ Symbol Sync FM Discriminator ↓ ↓ 51.2 kbps NRZ 300-3000 Hz audio ↓ Frame Sync (32-bit) ↓ 128-word demux ``` ### Key Parameters | Parameter | Value | Notes | |-----------|-------|-------| | Downlink frequency | 2287.5 MHz | Coherent with uplink | | PM peak deviation | 0.133 rad (7.6°) | Phase modulation | | PCM subcarrier | 1.024 MHz | BPSK modulated | | PCM bit rate | 51.2 kbps (high) / 1.6 kbps (low) | NRZ, MSB first | | Voice subcarrier | 1.25 MHz | FM, ±29 kHz deviation | | Frame length | 128 words × 8 bits | 50 fps high rate | | Frame sync | 32-bit pattern | Complements on odd frames | | Master clock | 512 kHz | All timing derived from this | ### PCM Frame Structure ``` ┌─────────────────────────────────────────────────────────────┐ │ Frame Sync (32 bits = 4 words) │ │ [5-bit A][15-bit core][6-bit B][6-bit frame ID] │ ├─────────────────────────────────────────────────────────────┤ │ Data Words 5-128 (124 words) │ │ - High-level analog (0-5V, 8-bit) │ │ - Low-level analog (0-40mV, 8-bit with ×125 gain) │ │ - Digital parallel/serial inputs │ │ - AGC downlink data (channels 34, 35, 57) │ └─────────────────────────────────────────────────────────────┘ 50 frames = 1 subframe (1 second at high rate) ``` ### Subcarrier Oscillators (FM Mode) | SCO | Center Freq | Deviation | Use | |-----|-------------|-----------|-----| | 1 | 14,500 Hz | ±7.5% | Analog sensor | | 2 | 22,000 Hz | ±7.5% | Analog sensor | | 3 | 30,000 Hz | ±7.5% | Analog sensor | | 4 | 40,000 Hz | ±7.5% | Analog sensor | | 5 | 52,500 Hz | ±7.5% | Analog sensor | | 6 | 70,000 Hz | ±7.5% | Analog sensor | | 7 | 95,000 Hz | ±7.5% | Analog sensor | | 8 | 125,000 Hz | ±7.5% | Analog sensor | | 9 | 165,000 Hz | ±7.5% | Analog sensor | ## GNU Radio Blocks to Implement ### Phase 1: Core Demodulation | Block | Type | I/O | Description | |-------|------|-----|-------------| | `pm_demod` | `gr.sync_block` | complex→float | PM demodulator with carrier recovery | | `subcarrier_extract` | `gr.sync_block` | float→complex | Bandpass + downconvert subcarrier | | `bpsk_demod` | `gr.sync_block` | complex→byte | BPSK demodulation with symbol sync | ### Phase 2: PCM Processing | Block | Type | I/O | Description | |-------|------|-----|-------------| | `pcm_frame_sync` | `gr.basic_block` | byte→PDU | 32-bit sync detection, frame extraction | | `pcm_demux` | `gr.basic_block` | PDU→PDU | Demultiplex 128-word frames by word position | | `downlink_decoder` | `gr.basic_block` | PDU→dict | Interpret AGC telemetry lists | ### Phase 3: Voice & Analog | Block | Type | I/O | Description | |-------|------|-----|-------------| | `fm_voice_demod` | `gr.sync_block` | complex→float | 1.25 MHz FM subcarrier → audio | | `sco_demod` | `gr.sync_block` | float→float | FM SCO demodulator (configurable) | ## Virtual AGC Integration The Virtual AGC emulator communicates via TCP socket (port 19697+): ``` 4-byte packet format: Byte 0: [Channel bits 8-4][0x00] Byte 1: [0x40 | Channel bits 3-1][Value bits 14-12] Byte 2: [0x80 | Value bits 11-6] Byte 3: [0xC0 | Value bits 5-0] ``` Key telecom channels: - **Ch 45 (INLINK)**: Uplink data input - **Ch 57 (OUTLINK)**: Downlink data output - **Ch 34/35 (DNTM1/2)**: Telemetry word stream ## References - [Implementation Spec](../virtualagc/telecom_study_guide/IMPLEMENTATION_SPEC.md) - [Virtual AGC Project](https://www.ibiblio.org/apollo/) - [NASA Apollo USB Description](https://ntrs.nasa.gov/) - [NAA Course A-624 Study Guide](https://archive.org/details/apollo-telecommunications) ## Code Style - Python: Follow GNU Radio block patterns (numpy for DSP) - Use gr-mcp protocol analysis tools for decoder chain generation - Test blocks in Docker before integration ## Docs Site Deployment Starlight documentation site in `docs/` subdirectory. - **Production**: https://gr-apollo.warehack.ing - **Local dev**: Set `DOMAIN=gr-apollo.l.warehack.ing` in `docs/.env` - **Git**: `git@git.supported.systems:warehack.ing/gr-apollo.git` - **Server**: `ssh -A warehack-ing@warehack.ing`, repo at `~/gr-apollo` ### Environment Copy `.env.example` to `.env` and set: ```env COMPOSE_PROJECT=gr-apollo-docs DOMAIN=gr-apollo.warehack.ing ``` ### Commands ```bash # From docs/ directory: make up # Production — Caddy serves static dist/ make dev # Development — Astro dev server with HMR make down # Stop all containers make logs # Tail container logs ``` ### Deploy to Production ```bash # One-liner from local machine ssh -A warehack-ing@warehack.ing "cd ~/gr-apollo && git pull && cd docs && make up" ``` ### Notes - Uses Docker Compose profiles (`prod` / `dev`) - Playwright + Chromium installed in build stage for mermaid diagram rendering - caddy-docker-proxy on external `caddy` network handles TLS automatically