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
165 lines
4.4 KiB
Markdown
165 lines
4.4 KiB
Markdown
# 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
|