gr-rylr998/README.md
Ryan Malloy c839d225a8 Initial release: complete LoRa TX/RX for RYLR998 modems
GNU Radio Out-of-Tree module providing:
- Complete TX chain: PHYEncode → FrameGen → CSSMod
- Complete RX chain: CSSDemod → FrameSync → PHYDecode
- NETWORKID extraction/encoding (0-255 range)
- All SF (7-12) and CR (4/5-4/8) combinations
- Loopback tested with 24/24 configurations passing

Key features:
- Fractional SFD (2.25 downchirp) handling
- Gray encode/decode with proper inverse operations
- gr-lora_sdr compatible decode modes
- GRC block definitions and example flowgraphs
- Comprehensive documentation

Discovered RYLR998 sync word mapping:
  sync_bin_1 = (NETWORKID >> 4) * 8
  sync_bin_2 = (NETWORKID & 0x0F) * 8
2026-02-05 13:38:07 -07:00

165 lines
4.4 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# gr-rylr998
GNU Radio Out-of-Tree Module for RYLR998 LoRa Modems
## Overview
gr-rylr998 provides complete TX/RX blocks for RYLR998 LoRa modems, implementing the full PHY layer compatible with the Semtech LoRa standard.
### Key Features
- **Complete TX/RX chains** for LoRa modulation/demodulation
- **NETWORKID support** - Extracts and encodes RYLR998 NETWORKID from sync word
- **gr-lora_sdr compatible** - Uses the same signal processing chain
- **Python-only** - No C++ compilation required (GNU Radio 3.10+)
- **Well-documented** - Educational comments throughout
## Installation
### From Source (Development)
```bash
cd gr-rylr998
pip install -e .
```
### With CMake (GNU Radio Integration)
```bash
mkdir build && cd build
cmake ..
make
sudo make install
```
## Quick Start
### Python API
```python
from rylr998 import PHYEncode, PHYDecode, FrameGen, FrameSync
import numpy as np
# === Transmit ===
encoder = PHYEncode(sf=9, cr=1, has_crc=True)
frame_gen = FrameGen(sf=9, networkid=18)
payload = b"Hello, LoRa!"
bins = encoder.encode(payload)
iq = frame_gen.generate_frame(bins)
# === Receive (loopback) ===
sync = FrameSync(sf=9)
result = sync.sync_from_samples(iq)
decoder = PHYDecode(sf=9)
# For loopback: use_grlora_gray=False, soft_decoding=True
# For real SDR captures: use defaults (True, False)
frame = decoder.decode(
result.data_symbols,
cfo_bin=int(result.cfo_bin),
use_grlora_gray=False,
soft_decoding=True
)
print(f"NETWORKID: {result.networkid}")
print(f"Payload: {frame.payload}")
print(f"CRC OK: {frame.crc_ok}")
```
### Command Line
```bash
# Run loopback test
python examples/loopback_test.py
# Test all SF/CR combinations
python examples/loopback_test.py --all
# Generate TX frame
python examples/bladerf_tx.py --payload "Test" --output frame.raw
# Decode from file
python examples/bladerf_rx.py --input capture.raw --verbose
```
## Block Inventory
| Block | Type | Description |
|-------|------|-------------|
| `css_demod` | Demod | FFT-based CSS demodulator |
| `css_mod` | Mod | Chirp generator |
| `frame_sync` | Sync | Preamble + NETWORKID detection |
| `frame_gen` | Framing | Preamble + sync word + SFD |
| `phy_decode` | PHY | Gray → deinterleave → Hamming → dewhiten |
| `phy_encode` | PHY | Whiten → Hamming → interleave → Gray |
| `rylr998_rx` | Hier | Complete RX chain |
| `rylr998_tx` | Hier | Complete TX chain |
## NETWORKID Mapping
The RYLR998 NETWORKID (0-255) maps directly to the LoRa sync word:
```python
sync_bin_1 = (NETWORKID >> 4) * 8 # High nibble × 8
sync_bin_2 = (NETWORKID & 0x0F) * 8 # Low nibble × 8
```
| NETWORKID | Sync Bins | Use |
|-----------|-----------|-----|
| 18 (0x12) | [8, 16] | Private networks |
| 52 (0x34) | [24, 32] | LoRaWAN public |
## Directory Structure
```
gr-rylr998/
├── python/rylr998/ # Python module
│ ├── __init__.py
│ ├── networkid.py # NETWORKID utilities
│ ├── css_demod.py # CSS demodulator
│ ├── css_mod.py # CSS modulator
│ ├── phy_decode.py # PHY RX chain
│ ├── phy_encode.py # PHY TX chain
│ ├── frame_sync.py # Frame synchronization
│ └── frame_gen.py # Frame generation
├── grc/ # GRC block definitions (.yml)
├── examples/ # Example scripts and flowgraphs
│ ├── loopback_test.py
│ ├── bladerf_rx.py
│ ├── bladerf_tx.py
│ ├── rylr998_loopback.grc
│ ├── rylr998_bladerf_rx.grc
│ └── rylr998_bladerf_tx.grc
├── docs/ # Documentation
│ ├── BLOCK_REFERENCE.md
│ ├── NETWORKID_MAPPING.md
│ └── GRC_FLOWGRAPHS.md
├── CMakeLists.txt # CMake build
└── pyproject.toml # Python packaging
```
## Documentation
| Document | Description |
|----------|-------------|
| [BLOCK_REFERENCE.md](docs/BLOCK_REFERENCE.md) | All blocks, parameters, troubleshooting |
| [NETWORKID_MAPPING.md](docs/NETWORKID_MAPPING.md) | RYLR998 NETWORKID ↔ sync word mapping |
| [GRC_FLOWGRAPHS.md](docs/GRC_FLOWGRAPHS.md) | GNU Radio Companion usage guide |
## Dependencies
- Python 3.10+
- NumPy
- GNU Radio 3.10+ (optional, for GRC integration)
## References
- [LoRa Patent EP2763321](https://patents.google.com/patent/EP2763321A1)
- [gr-lora_sdr](https://github.com/tapparelj/gr-lora_sdr)
- [Matt Knight, "Decoding LoRa" (DEF CON 24)](https://media.defcon.org/DEF%20CON%2024/DEF%20CON%2024%20presentations/)
## License
GPL-3.0-or-later