[docs](trx-wxsat): add README with architecture and API documentation
https://claude.ai/code/session_01Cm1JpWMDZanjwKg3r2S3VR Signed-off-by: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,149 @@
|
|||||||
|
# trx-wxsat
|
||||||
|
|
||||||
|
Weather satellite image decoders for NOAA APT and Meteor-M LRPT signals.
|
||||||
|
|
||||||
|
## Supported Satellites
|
||||||
|
|
||||||
|
| Satellite | Format | Frequency | Modulation |
|
||||||
|
|----------------|--------|---------------|------------------------|
|
||||||
|
| NOAA-15 | APT | 137.620 MHz | FM/AM subcarrier |
|
||||||
|
| NOAA-18 | APT | 137.9125 MHz | FM/AM subcarrier |
|
||||||
|
| NOAA-19 | APT | 137.100 MHz | FM/AM subcarrier |
|
||||||
|
| Meteor-M N2-3 | LRPT | 137.900 MHz | QPSK, 72 kbps, CCSDS |
|
||||||
|
| Meteor-M N2-4 | LRPT | 137.100 MHz | QPSK, 72 kbps, CCSDS |
|
||||||
|
|
||||||
|
## Architecture
|
||||||
|
|
||||||
|
```
|
||||||
|
trx-wxsat/src/
|
||||||
|
├── lib.rs # Module declarations, shared helpers
|
||||||
|
├── image_enc.rs # Shared PNG encoding (grayscale + RGB)
|
||||||
|
├── noaa/
|
||||||
|
│ ├── mod.rs # AptDecoder, AptImage (public API)
|
||||||
|
│ ├── apt.rs # AM demodulator (Hilbert/FFT), line sync tracker
|
||||||
|
│ ├── image_enc.rs # APT-specific dual-channel image assembly
|
||||||
|
│ └── telemetry.rs # Wedge-based calibration, satellite ID, histogram EQ
|
||||||
|
└── lrpt/
|
||||||
|
├── mod.rs # LrptDecoder, LrptImage (public API)
|
||||||
|
├── demod.rs # QPSK demodulator (Costas loop + Gardner TED)
|
||||||
|
├── cadu.rs # CCSDS CADU frame synchronisation (ASM search)
|
||||||
|
└── mcu.rs # Per-APID channel assembly, RGB composite
|
||||||
|
```
|
||||||
|
|
||||||
|
## Signal Flow
|
||||||
|
|
||||||
|
### NOAA APT
|
||||||
|
|
||||||
|
```
|
||||||
|
FM-demodulated audio (any sample rate)
|
||||||
|
│ AptDemod: FFT Hilbert transform, bandpass @ 2400 Hz ±1040 Hz
|
||||||
|
▼
|
||||||
|
AM envelope resampled to 4160 Hz
|
||||||
|
│ SyncTracker: 1040 Hz sync-A marker correlation
|
||||||
|
▼
|
||||||
|
Aligned 2080-sample lines → [SyncA 39][SpaceA 47][ImageA 909][TelA 45][SyncB 39][SpaceB 47][ImageB 909][TelB 45]
|
||||||
|
│ Telemetry extraction, wedge-based radiometric calibration, histogram EQ
|
||||||
|
▼
|
||||||
|
PNG image (1818 × N pixels, dual-channel side-by-side)
|
||||||
|
```
|
||||||
|
|
||||||
|
**Key DSP details:**
|
||||||
|
|
||||||
|
- AM envelope extraction uses an FFT-based Hilbert transform (rustfft) with
|
||||||
|
bandpass filtering around the 2400 Hz subcarrier
|
||||||
|
- Sync detection uses cosine correlation against a 7-cycle 1040 Hz reference
|
||||||
|
pattern, normalised by RMS; threshold 0.15 for acquisition, 0.075 for tracking
|
||||||
|
- Telemetry frames span 128 lines; wedges 1-8 provide known reference levels
|
||||||
|
for piecewise-linear radiometric calibration; wedge 9 encodes the sensor
|
||||||
|
channel ID
|
||||||
|
- Satellite identification is heuristic, based on the detected channel pairing
|
||||||
|
(e.g. VIS + TIR4 maps to NOAA-18)
|
||||||
|
- Per-line normalisation clips to the 2nd-98th percentile before scaling
|
||||||
|
|
||||||
|
### Meteor-M LRPT
|
||||||
|
|
||||||
|
```
|
||||||
|
Baseband samples (any sample rate)
|
||||||
|
│ QpskDemod: Costas loop carrier recovery + Gardner TED symbol timing
|
||||||
|
▼
|
||||||
|
Soft symbols (±1.0, I/Q interleaved) @ 72 ksym/s
|
||||||
|
│ CaduFramer: hard-decision, ASM (0x1ACFFC1D) search, frame lock
|
||||||
|
▼
|
||||||
|
1024-byte CADUs (CCSDS transfer frames)
|
||||||
|
│ ChannelAssembler: VCID → APID routing, MCU extraction
|
||||||
|
▼
|
||||||
|
Per-APID pixel rows (1568 px wide)
|
||||||
|
│ RGB composite (APIDs 64/65/66) or grayscale fallback
|
||||||
|
▼
|
||||||
|
PNG image (1568 × N pixels)
|
||||||
|
```
|
||||||
|
|
||||||
|
**LRPT channel mapping:**
|
||||||
|
|
||||||
|
| APID | Channel | Band |
|
||||||
|
|------|---------|-------------------|
|
||||||
|
| 64 | 1 | Visible (0.5-0.7 µm) |
|
||||||
|
| 65 | 2 | Visible/NIR (0.7-1.1 µm) |
|
||||||
|
| 66 | 3 | Near-IR (1.6-1.8 µm) |
|
||||||
|
| 67 | 4 | Mid-IR (3.5-4.1 µm) |
|
||||||
|
| 68 | 5 | Thermal IR (10.5-11.5 µm) |
|
||||||
|
| 69 | 6 | Thermal IR (11.5-12.5 µm) |
|
||||||
|
|
||||||
|
**Key DSP details:**
|
||||||
|
|
||||||
|
- Costas loop parameters: bandwidth ~0.01 of symbol rate, damping factor 0.707
|
||||||
|
- Gardner TED operates on interpolated mid-sample points for timing error
|
||||||
|
estimation
|
||||||
|
- Frame synchronisation searches for the 4-byte Attached Sync Marker
|
||||||
|
(`0x1ACFFC1D`) and maintains lock/unlock state tracking
|
||||||
|
- Spacecraft ID extraction from VCDU header: ID 57 = Meteor-M N2-3,
|
||||||
|
ID 58 = Meteor-M N2-4
|
||||||
|
- RGB compositing uses channels 1/2/3 when available; falls back to the
|
||||||
|
highest-populated single channel as grayscale
|
||||||
|
|
||||||
|
## Public API
|
||||||
|
|
||||||
|
Both decoders share the same streaming interface:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
// NOAA APT
|
||||||
|
let mut apt = AptDecoder::new(sample_rate);
|
||||||
|
apt.process_samples(&audio_batch); // returns new line count
|
||||||
|
apt.line_count(); // total lines so far
|
||||||
|
let image: Option<AptImage> = apt.finalize(); // PNG + telemetry
|
||||||
|
apt.reset(); // prepare for next pass
|
||||||
|
|
||||||
|
// Meteor-M LRPT
|
||||||
|
let mut lrpt = LrptDecoder::new(sample_rate);
|
||||||
|
lrpt.process_samples(&baseband_batch); // returns new MCU row count
|
||||||
|
lrpt.mcu_count(); // total MCU rows so far
|
||||||
|
let image: Option<LrptImage> = lrpt.finalize(); // PNG + metadata
|
||||||
|
lrpt.reset(); // prepare for next pass
|
||||||
|
```
|
||||||
|
|
||||||
|
### Output types
|
||||||
|
|
||||||
|
**`AptImage`**: PNG bytes, line count, first-line timestamp, identified
|
||||||
|
satellite (`NOAA-15`/`18`/`19`), sensor channels A and B
|
||||||
|
(`Visible1`, `NearIr2`, `ThermalIr4`, etc.)
|
||||||
|
|
||||||
|
**`LrptImage`**: PNG bytes, MCU row count, identified satellite
|
||||||
|
(`Meteor-M N2-3`/`N2-4`), comma-separated active APID list
|
||||||
|
|
||||||
|
## Dependencies
|
||||||
|
|
||||||
|
| Crate | Purpose |
|
||||||
|
|---------------|----------------------------------|
|
||||||
|
| `trx-core` | Shared core types |
|
||||||
|
| `rustfft` | FFT for Hilbert AM demodulation |
|
||||||
|
| `num-complex` | Complex arithmetic |
|
||||||
|
| `image` | PNG encoding (png feature only) |
|
||||||
|
|
||||||
|
## Integration
|
||||||
|
|
||||||
|
The crate plugs into `trx-server` as a decoder task. The server feeds PCM
|
||||||
|
audio from the SDR backend into `process_samples()`, auto-finalises on
|
||||||
|
timeout (no new lines/MCUs for a configurable period), and publishes
|
||||||
|
decoded images as `DecodedMessage::WxsatImage` / `DecodedMessage::LrptImage`
|
||||||
|
for client consumption. Images are saved to `~/.cache/trx-rs/wxsat/` and
|
||||||
|
`~/.cache/trx-rs/lrpt/`.
|
||||||
Reference in New Issue
Block a user