softlora 0.3.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- softlora-0.3.0/CONTRIBUTORS.txt +4 -0
- softlora-0.3.0/LICENSE +19 -0
- softlora-0.3.0/MANIFEST.in +5 -0
- softlora-0.3.0/PKG-INFO +157 -0
- softlora-0.3.0/README.md +120 -0
- softlora-0.3.0/docs/api/chase.md +3 -0
- softlora-0.3.0/docs/api/chirp.md +3 -0
- softlora-0.3.0/docs/api/coding.md +3 -0
- softlora-0.3.0/docs/api/decoder.md +29 -0
- softlora-0.3.0/docs/api/io.md +3 -0
- softlora-0.3.0/docs/api/packet.md +3 -0
- softlora-0.3.0/docs/api/sync.md +3 -0
- softlora-0.3.0/docs/api/utils.md +3 -0
- softlora-0.3.0/docs/changelog.md +152 -0
- softlora-0.3.0/docs/guide/chase.md +254 -0
- softlora-0.3.0/docs/guide/formats.md +68 -0
- softlora-0.3.0/docs/guide/packet-semantics.md +60 -0
- softlora-0.3.0/docs/guide/performance.md +97 -0
- softlora-0.3.0/docs/guide/pipeline.md +267 -0
- softlora-0.3.0/docs/guide/satellites.md +78 -0
- softlora-0.3.0/docs/guide/streaming.md +96 -0
- softlora-0.3.0/docs/guide/sync-algorithm.md +157 -0
- softlora-0.3.0/docs/index.md +66 -0
- softlora-0.3.0/docs/installation.md +48 -0
- softlora-0.3.0/docs/quickstart.md +86 -0
- softlora-0.3.0/examples/README.md +60 -0
- softlora-0.3.0/examples/decode_file.py +36 -0
- softlora-0.3.0/examples/decode_ota_capture.py +15 -0
- softlora-0.3.0/examples/decoder_settings.py +61 -0
- softlora-0.3.0/examples/implicit_header.py +29 -0
- softlora-0.3.0/examples/sdr_live.py +141 -0
- softlora-0.3.0/examples/streaming.py +74 -0
- softlora-0.3.0/examples/tutorial_decode_iq.py +42 -0
- softlora-0.3.0/pyproject.toml +57 -0
- softlora-0.3.0/setup.cfg +4 -0
- softlora-0.3.0/softlora/__init__.py +22 -0
- softlora-0.3.0/softlora/chase.py +338 -0
- softlora-0.3.0/softlora/chirp.py +22 -0
- softlora-0.3.0/softlora/coding.py +474 -0
- softlora-0.3.0/softlora/decoder.py +1551 -0
- softlora-0.3.0/softlora/gr_frame_sync.py +619 -0
- softlora-0.3.0/softlora/io.py +244 -0
- softlora-0.3.0/softlora/packet.py +244 -0
- softlora-0.3.0/softlora/py.typed +0 -0
- softlora-0.3.0/softlora/sync.py +372 -0
- softlora-0.3.0/softlora/utils.py +166 -0
- softlora-0.3.0/softlora.egg-info/PKG-INFO +157 -0
- softlora-0.3.0/softlora.egg-info/SOURCES.txt +57 -0
- softlora-0.3.0/softlora.egg-info/dependency_links.txt +1 -0
- softlora-0.3.0/softlora.egg-info/requires.txt +12 -0
- softlora-0.3.0/softlora.egg-info/top_level.txt +1 -0
- softlora-0.3.0/tests/__init__.py +0 -0
- softlora-0.3.0/tests/test_chase.py +183 -0
- softlora-0.3.0/tests/test_decoder.py +61 -0
- softlora-0.3.0/tests/test_io.py +115 -0
- softlora-0.3.0/tests/test_packet.py +98 -0
- softlora-0.3.0/tests/test_stream.py +249 -0
- softlora-0.3.0/tests/test_sync_paper.py +238 -0
- softlora-0.3.0/tests/tx_helpers.py +144 -0
softlora-0.3.0/LICENSE
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
4
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
5
|
+
in the Software without restriction, including without limitation the rights
|
|
6
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
7
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
8
|
+
furnished to do so, subject to the following conditions:
|
|
9
|
+
|
|
10
|
+
The above copyright notice and this permission notice shall be included in all
|
|
11
|
+
copies or substantial portions of the Software.
|
|
12
|
+
|
|
13
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
14
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
15
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
16
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
17
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
18
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
19
|
+
SOFTWARE.
|
softlora-0.3.0/PKG-INFO
ADDED
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: softlora
|
|
3
|
+
Version: 0.3.0
|
|
4
|
+
Summary: LoRa satellite receiver - sync, demodulate, and decode
|
|
5
|
+
Author-email: LibreCube <info@librecube.org>, Hamza Hassan <hamza.mohammed.hasan@gmail.com>
|
|
6
|
+
License: MIT License
|
|
7
|
+
Project-URL: Homepage, https://gitlab.com/librecube/prototypes/gsoc-lora-sat-receiver
|
|
8
|
+
Project-URL: Repository, https://gitlab.com/librecube/prototypes/gsoc-lora-sat-receiver
|
|
9
|
+
Project-URL: Bug Tracker, https://gitlab.com/librecube/prototypes/gsoc-lora-sat-receiver/-/issues
|
|
10
|
+
Project-URL: Documentation, https://librecube.gitlab.io/prototypes/gsoc-lora-sat-receiver/
|
|
11
|
+
Keywords: lora,satellite,sdr,chirp-spread-spectrum,gnuradio
|
|
12
|
+
Classifier: Development Status :: 3 - Alpha
|
|
13
|
+
Classifier: Intended Audience :: Science/Research
|
|
14
|
+
Classifier: Programming Language :: Python :: 3
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
20
|
+
Classifier: Typing :: Typed
|
|
21
|
+
Classifier: Topic :: Communications :: Ham Radio
|
|
22
|
+
Classifier: Topic :: Scientific/Engineering :: Information Analysis
|
|
23
|
+
Requires-Python: >=3.9
|
|
24
|
+
Description-Content-Type: text/markdown
|
|
25
|
+
License-File: LICENSE
|
|
26
|
+
Requires-Dist: numpy
|
|
27
|
+
Requires-Dist: scipy
|
|
28
|
+
Provides-Extra: docs
|
|
29
|
+
Requires-Dist: mkdocs; extra == "docs"
|
|
30
|
+
Requires-Dist: mkdocs-material; extra == "docs"
|
|
31
|
+
Requires-Dist: mkdocstrings[python]; extra == "docs"
|
|
32
|
+
Provides-Extra: dev
|
|
33
|
+
Requires-Dist: build; extra == "dev"
|
|
34
|
+
Requires-Dist: twine; extra == "dev"
|
|
35
|
+
Requires-Dist: pytest; extra == "dev"
|
|
36
|
+
Dynamic: license-file
|
|
37
|
+
|
|
38
|
+
# SoftLoRa
|
|
39
|
+
|
|
40
|
+
[]()
|
|
41
|
+
|
|
42
|
+
Decode LoRa packets from radio recordings and live SDR streams, in pure Python.
|
|
43
|
+
|
|
44
|
+
Point it at an IQ recording and it finds the packets, corrects the frequency and
|
|
45
|
+
timing errors introduced by the radio link, and gives you back the payload.
|
|
46
|
+
No GNU Radio or C++ needed — just `numpy` and `scipy`.
|
|
47
|
+
|
|
48
|
+
It was built for satellite downlinks, where signals are weak and Doppler-shifted,
|
|
49
|
+
and is tested on synthetic data, over-the-air captures and real satellite passes.
|
|
50
|
+
|
|
51
|
+
Developed for Google Summer of Code 2026 with [LibreCube](https://librecube.org/).
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
## Install
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
pip install softlora
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
Python 3.9+.
|
|
61
|
+
|
|
62
|
+
## Decode a recording
|
|
63
|
+
|
|
64
|
+
```python
|
|
65
|
+
from softlora import LoRaDecoder
|
|
66
|
+
|
|
67
|
+
decoder = LoRaDecoder(sf=10, bw=125_000, fs=125_000, fc=437e6)
|
|
68
|
+
|
|
69
|
+
for packet in decoder.decode_file("recording.wav"):
|
|
70
|
+
print(packet.payload_text, packet.crc_valid)
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
The four arguments describe the signal you are decoding:
|
|
74
|
+
|
|
75
|
+
| Argument | Meaning |
|
|
76
|
+
|---|---|
|
|
77
|
+
| `sf` | Spreading factor, 7–12 |
|
|
78
|
+
| `bw` | LoRa bandwidth in Hz |
|
|
79
|
+
| `fs` | Sample rate of *your recording* in Hz |
|
|
80
|
+
| `fc` | Center frequency in Hz |
|
|
81
|
+
|
|
82
|
+
`.wav`, `.cfile`, `.dat` and `.bin` files are supported. A recording may hold
|
|
83
|
+
several packets, so you always get a list back.
|
|
84
|
+
|
|
85
|
+
## Decode a live stream
|
|
86
|
+
|
|
87
|
+
Feed IQ chunks as they arrive from an SDR. Packets are returned as soon as they
|
|
88
|
+
are complete, even when one spans two chunks:
|
|
89
|
+
|
|
90
|
+
```python
|
|
91
|
+
decoder = LoRaDecoder(sf=10, bw=125_000, fs=250_000, fc=437e6)
|
|
92
|
+
|
|
93
|
+
while receiving:
|
|
94
|
+
for packet in decoder.decode_stream(read_iq_from_sdr(8192)):
|
|
95
|
+
print(packet.payload_text)
|
|
96
|
+
|
|
97
|
+
for packet in decoder.flush(): # decode whatever is left in the buffer
|
|
98
|
+
print(packet)
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
## Reading the result
|
|
102
|
+
|
|
103
|
+
Every decode path returns [`Packet`](softlora/packet.py) objects. The
|
|
104
|
+
fields you will usually want:
|
|
105
|
+
|
|
106
|
+
```python
|
|
107
|
+
packet.payload_bytes # the data
|
|
108
|
+
packet.payload_text # the same data decoded as UTF-8
|
|
109
|
+
packet.crc_valid # True when the payload passed its checksum
|
|
110
|
+
packet.snr_est # signal-to-noise estimate in dB
|
|
111
|
+
packet.ok # a packet was found and demodulated
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
`ok` and `crc_valid` answer different questions. `ok=True, crc_valid=False`
|
|
115
|
+
means a packet arrived but was corrupted on the way. Filter on
|
|
116
|
+
`packet.crc_valid is True` when you only want trustworthy payloads.
|
|
117
|
+
|
|
118
|
+
## A LoRa packet
|
|
119
|
+
|
|
120
|
+
Each packet starts with a preamble of plain upchirps, then a sync word, then a
|
|
121
|
+
2.25-symbol start-of-frame delimiter (SFD) made of downchirps, and finally the
|
|
122
|
+
header, payload and CRC. All four regions are visible below.
|
|
123
|
+
|
|
124
|
+
<div align="center">
|
|
125
|
+
|
|
126
|
+
| <img src="docs/images/lora_packet.png" width="600"> |
|
|
127
|
+
|:---:|
|
|
128
|
+
| *A real LoRa packet received from the Polytech Universe-3 (PU-3) satellite (SF8, 62.5 kHz bandwidth). The payload is truncated for display.* |
|
|
129
|
+
|
|
130
|
+
</div>
|
|
131
|
+
|
|
132
|
+
## Going further
|
|
133
|
+
|
|
134
|
+
| Topic | Where |
|
|
135
|
+
|---|---|
|
|
136
|
+
| Runnable examples | [`examples/`](examples/README.md) |
|
|
137
|
+
| Packets without a header | [Quick start](docs/quickstart.md) |
|
|
138
|
+
| Tuning the decoder (`DecoderSettings`) | [The decode pipeline](docs/guide/pipeline.md) |
|
|
139
|
+
| Rescuing weak packets with Chase decoding | [Chase guide](docs/guide/chase.md) |
|
|
140
|
+
| Live SDR chain via GNU Radio | [`examples/README.md`](examples/README.md#live-sdr-via-gnu-radio) |
|
|
141
|
+
| Doppler and carrier offset for satellites | [Ground-station use](docs/guide/satellites.md) |
|
|
142
|
+
| Benchmarks across SF 7–12 | [Performance](docs/guide/performance.md) |
|
|
143
|
+
| How synchronization works | [Sync algorithm](docs/guide/sync-algorithm.md) |
|
|
144
|
+
| Full API reference | [Documentation site](https://gsoc-lora-sat-receiver-7e5f4f.gitlab.io/) |
|
|
145
|
+
|
|
146
|
+
## References
|
|
147
|
+
|
|
148
|
+
[1] M. Xhonneux, O. Afisiadis, D. Bol, and J. Louveaux, "A Low-Complexity LoRa
|
|
149
|
+
Synchronization Algorithm Robust to Sampling Time Offsets," *IEEE Internet of
|
|
150
|
+
Things Journal*, 2021. [arXiv:1912.11344](https://arxiv.org/abs/1912.11344)
|
|
151
|
+
|
|
152
|
+
[2] J. Tapparel, O. Afisiadis, P. Mayoraz, A. Balatsoukas-Stimming, and A. Burg,
|
|
153
|
+
"An Open-Source LoRa Physical Layer Prototype on GNU Radio," *SPAWC*, 2020.
|
|
154
|
+
|
|
155
|
+
## License
|
|
156
|
+
|
|
157
|
+
MIT — see [LICENSE](LICENSE) and [CONTRIBUTORS.txt](CONTRIBUTORS.txt).
|
softlora-0.3.0/README.md
ADDED
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
# SoftLoRa
|
|
2
|
+
|
|
3
|
+
[]()
|
|
4
|
+
|
|
5
|
+
Decode LoRa packets from radio recordings and live SDR streams, in pure Python.
|
|
6
|
+
|
|
7
|
+
Point it at an IQ recording and it finds the packets, corrects the frequency and
|
|
8
|
+
timing errors introduced by the radio link, and gives you back the payload.
|
|
9
|
+
No GNU Radio or C++ needed — just `numpy` and `scipy`.
|
|
10
|
+
|
|
11
|
+
It was built for satellite downlinks, where signals are weak and Doppler-shifted,
|
|
12
|
+
and is tested on synthetic data, over-the-air captures and real satellite passes.
|
|
13
|
+
|
|
14
|
+
Developed for Google Summer of Code 2026 with [LibreCube](https://librecube.org/).
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
## Install
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
pip install softlora
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
Python 3.9+.
|
|
24
|
+
|
|
25
|
+
## Decode a recording
|
|
26
|
+
|
|
27
|
+
```python
|
|
28
|
+
from softlora import LoRaDecoder
|
|
29
|
+
|
|
30
|
+
decoder = LoRaDecoder(sf=10, bw=125_000, fs=125_000, fc=437e6)
|
|
31
|
+
|
|
32
|
+
for packet in decoder.decode_file("recording.wav"):
|
|
33
|
+
print(packet.payload_text, packet.crc_valid)
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
The four arguments describe the signal you are decoding:
|
|
37
|
+
|
|
38
|
+
| Argument | Meaning |
|
|
39
|
+
|---|---|
|
|
40
|
+
| `sf` | Spreading factor, 7–12 |
|
|
41
|
+
| `bw` | LoRa bandwidth in Hz |
|
|
42
|
+
| `fs` | Sample rate of *your recording* in Hz |
|
|
43
|
+
| `fc` | Center frequency in Hz |
|
|
44
|
+
|
|
45
|
+
`.wav`, `.cfile`, `.dat` and `.bin` files are supported. A recording may hold
|
|
46
|
+
several packets, so you always get a list back.
|
|
47
|
+
|
|
48
|
+
## Decode a live stream
|
|
49
|
+
|
|
50
|
+
Feed IQ chunks as they arrive from an SDR. Packets are returned as soon as they
|
|
51
|
+
are complete, even when one spans two chunks:
|
|
52
|
+
|
|
53
|
+
```python
|
|
54
|
+
decoder = LoRaDecoder(sf=10, bw=125_000, fs=250_000, fc=437e6)
|
|
55
|
+
|
|
56
|
+
while receiving:
|
|
57
|
+
for packet in decoder.decode_stream(read_iq_from_sdr(8192)):
|
|
58
|
+
print(packet.payload_text)
|
|
59
|
+
|
|
60
|
+
for packet in decoder.flush(): # decode whatever is left in the buffer
|
|
61
|
+
print(packet)
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
## Reading the result
|
|
65
|
+
|
|
66
|
+
Every decode path returns [`Packet`](softlora/packet.py) objects. The
|
|
67
|
+
fields you will usually want:
|
|
68
|
+
|
|
69
|
+
```python
|
|
70
|
+
packet.payload_bytes # the data
|
|
71
|
+
packet.payload_text # the same data decoded as UTF-8
|
|
72
|
+
packet.crc_valid # True when the payload passed its checksum
|
|
73
|
+
packet.snr_est # signal-to-noise estimate in dB
|
|
74
|
+
packet.ok # a packet was found and demodulated
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
`ok` and `crc_valid` answer different questions. `ok=True, crc_valid=False`
|
|
78
|
+
means a packet arrived but was corrupted on the way. Filter on
|
|
79
|
+
`packet.crc_valid is True` when you only want trustworthy payloads.
|
|
80
|
+
|
|
81
|
+
## A LoRa packet
|
|
82
|
+
|
|
83
|
+
Each packet starts with a preamble of plain upchirps, then a sync word, then a
|
|
84
|
+
2.25-symbol start-of-frame delimiter (SFD) made of downchirps, and finally the
|
|
85
|
+
header, payload and CRC. All four regions are visible below.
|
|
86
|
+
|
|
87
|
+
<div align="center">
|
|
88
|
+
|
|
89
|
+
| <img src="docs/images/lora_packet.png" width="600"> |
|
|
90
|
+
|:---:|
|
|
91
|
+
| *A real LoRa packet received from the Polytech Universe-3 (PU-3) satellite (SF8, 62.5 kHz bandwidth). The payload is truncated for display.* |
|
|
92
|
+
|
|
93
|
+
</div>
|
|
94
|
+
|
|
95
|
+
## Going further
|
|
96
|
+
|
|
97
|
+
| Topic | Where |
|
|
98
|
+
|---|---|
|
|
99
|
+
| Runnable examples | [`examples/`](examples/README.md) |
|
|
100
|
+
| Packets without a header | [Quick start](docs/quickstart.md) |
|
|
101
|
+
| Tuning the decoder (`DecoderSettings`) | [The decode pipeline](docs/guide/pipeline.md) |
|
|
102
|
+
| Rescuing weak packets with Chase decoding | [Chase guide](docs/guide/chase.md) |
|
|
103
|
+
| Live SDR chain via GNU Radio | [`examples/README.md`](examples/README.md#live-sdr-via-gnu-radio) |
|
|
104
|
+
| Doppler and carrier offset for satellites | [Ground-station use](docs/guide/satellites.md) |
|
|
105
|
+
| Benchmarks across SF 7–12 | [Performance](docs/guide/performance.md) |
|
|
106
|
+
| How synchronization works | [Sync algorithm](docs/guide/sync-algorithm.md) |
|
|
107
|
+
| Full API reference | [Documentation site](https://gsoc-lora-sat-receiver-7e5f4f.gitlab.io/) |
|
|
108
|
+
|
|
109
|
+
## References
|
|
110
|
+
|
|
111
|
+
[1] M. Xhonneux, O. Afisiadis, D. Bol, and J. Louveaux, "A Low-Complexity LoRa
|
|
112
|
+
Synchronization Algorithm Robust to Sampling Time Offsets," *IEEE Internet of
|
|
113
|
+
Things Journal*, 2021. [arXiv:1912.11344](https://arxiv.org/abs/1912.11344)
|
|
114
|
+
|
|
115
|
+
[2] J. Tapparel, O. Afisiadis, P. Mayoraz, A. Balatsoukas-Stimming, and A. Burg,
|
|
116
|
+
"An Open-Source LoRa Physical Layer Prototype on GNU Radio," *SPAWC*, 2020.
|
|
117
|
+
|
|
118
|
+
## License
|
|
119
|
+
|
|
120
|
+
MIT — see [LICENSE](LICENSE) and [CONTRIBUTORS.txt](CONTRIBUTORS.txt).
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# LoRaDecoder
|
|
2
|
+
|
|
3
|
+
The main entry point: constructed once with all decode parameters, then used
|
|
4
|
+
to decode files, in-memory buffers, or live streams.
|
|
5
|
+
|
|
6
|
+
::: softlora.decoder.LoRaDecoder
|
|
7
|
+
options:
|
|
8
|
+
show_docstring_parameters: true
|
|
9
|
+
members:
|
|
10
|
+
- decode
|
|
11
|
+
- decode_file
|
|
12
|
+
- decode_iq
|
|
13
|
+
- decode_stream
|
|
14
|
+
- estimate_carrier_offset
|
|
15
|
+
- flush
|
|
16
|
+
- reset
|
|
17
|
+
- sync
|
|
18
|
+
- demodulate
|
|
19
|
+
|
|
20
|
+
# DecoderSettings
|
|
21
|
+
|
|
22
|
+
Advanced/internal decoder parameters, passed to `LoRaDecoder` via
|
|
23
|
+
`settings=`. It isolates everything that is not a radio/tunable parameter:
|
|
24
|
+
the sync algorithm, the decode mode, Chase tuning, the streaming gates and
|
|
25
|
+
the gr-lora-sdr SFO search.
|
|
26
|
+
|
|
27
|
+
::: softlora.decoder.DecoderSettings
|
|
28
|
+
options:
|
|
29
|
+
show_docstring_parameters: true
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## Unreleased
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
|
|
7
|
+
- **README packet spectrogram** — `scripts/plot_pktA_spectrogram.py` renders the
|
|
8
|
+
PU-3 packet from the packet-A sample recording as a single labelled
|
|
9
|
+
spectrogram strip (preamble, sync, SFD down-chirps, header + payload + CRC),
|
|
10
|
+
used as README Figure 1.
|
|
11
|
+
- **`gr-lora-sdr` streaming** — `decode_stream` / `flush` now work with
|
|
12
|
+
`sync_algorithm='gr-lora-sdr'`: a new `_GrFrameStream` scanner runs the
|
|
13
|
+
native-rate dechirp run detection incrementally over arbitrary-sized chunks
|
|
14
|
+
(bounded memory) and decodes each candidate as soon as its window is
|
|
15
|
+
buffered. `decode_file` / `decode_iq` reuse the same scanner, so both sync
|
|
16
|
+
algorithms now stream.
|
|
17
|
+
|
|
18
|
+
### Changed
|
|
19
|
+
|
|
20
|
+
- **`save_packets` prettier output** — besides `packet_XXXX.bin` payloads and
|
|
21
|
+
the append-only `metadata.jsonl`, `save_packets` now writes a readable
|
|
22
|
+
`packets.txt` summary with each packet's `Packet.__str__` detail table.
|
|
23
|
+
|
|
24
|
+
### Removed
|
|
25
|
+
|
|
26
|
+
- **Chase diagnosis fields dropped from `Packet`** — `chase_used`,
|
|
27
|
+
`chase_attempts` and `chase_stage` are no longer exposed on the result type;
|
|
28
|
+
Chase remains a decoder mode and its outcome is read from `ok` / `crc_valid`
|
|
29
|
+
as before. The `chase_flip_frac` / `chase_attempts_avg` metrics were removed
|
|
30
|
+
from `perf/awgn_chase_vs_hard.py` accordingly.
|
|
31
|
+
|
|
32
|
+
## v0.3.0 (2026-08-23)
|
|
33
|
+
|
|
34
|
+
### Added
|
|
35
|
+
|
|
36
|
+
- **Automatic carrier offset estimation** — `decode_file` / `decode_iq` /
|
|
37
|
+
`decode` accept `carrier_offset_hz='auto'`, which scans the recording for
|
|
38
|
+
preamble runs and resolves the offset from the preamble upchirp and the
|
|
39
|
+
first SFD downchirp (the timing term cancels, so the estimate is
|
|
40
|
+
unambiguous over `±fs/2`). See `LoRaDecoder.estimate_carrier_offset`.
|
|
41
|
+
- **`DecoderSettings.sfo_ppm`** — optional clock-offset override for the
|
|
42
|
+
`gr-lora-sdr` sync: a fixed ppm value, `None` (derive from residual CFO), or
|
|
43
|
+
`'auto'` (default) to sweep `sfo_ppm_search` and accept the first frame
|
|
44
|
+
whose CRC validates.
|
|
45
|
+
- **`DecoderSettings.max_os_factor`** — the `gr-lora-sdr` sync band-limits and
|
|
46
|
+
decimates high-oversampled input down to at most this `fs/bw` before running
|
|
47
|
+
the native-rate `frame_sync` port.
|
|
48
|
+
- **Header checksum verification** — explicit headers are now validated with
|
|
49
|
+
the LoRa 5-bit checksum (`verify_header_checksum`) before their fields are
|
|
50
|
+
trusted.
|
|
51
|
+
- **Sub-sample timing refinement** — when the hard decode of a fully buffered
|
|
52
|
+
packet fails, the xhonneux sync probes `timing_search` sub-sample
|
|
53
|
+
alignments and the surrounding symbol positions before giving up.
|
|
54
|
+
|
|
55
|
+
### Changed
|
|
56
|
+
|
|
57
|
+
- **`sync.py` now implements Algorithm 1 of Xhonneux et al. exactly**:
|
|
58
|
+
- Fractional CFO from the **last two** upchirp pairs (Eq. 14) instead of an
|
|
59
|
+
average over all preamble pairs.
|
|
60
|
+
- Stage 2 uses the **first** post-detection window for `s_tilde_up` and the
|
|
61
|
+
paper's `M_tilde = N - s_tilde_up`.
|
|
62
|
+
- Stage 3 demodulates the **single** final upchirp (U7) and **single** first
|
|
63
|
+
SFD downchirp (D1) instead of averaged windows.
|
|
64
|
+
- Integer offsets via Eq. 17/18, with the integer STO unwrapped into
|
|
65
|
+
`[-N/2, N/2)` so a slightly negative STO no longer pushes the payload a
|
|
66
|
+
full symbol early.
|
|
67
|
+
- **`gr-lora-sdr` sync no longer returns failed candidates** — `decode_file`
|
|
68
|
+
/ `decode_iq` with `sync_algorithm='gr-lora-sdr'` now drop runs whose frame
|
|
69
|
+
never synchronized (false-positive preamble detections on noise), matching
|
|
70
|
+
the `xhonneux` path which already filtered them.
|
|
71
|
+
- **`Packet.__str__` renders a detail table** — `print(packet)` shows an
|
|
72
|
+
aligned key/value table with the full payload hex (wrapped at 32 bytes per
|
|
73
|
+
line), full payload text, positions, SNR, frequency offset and CRC status.
|
|
74
|
+
`__repr__` keeps the compact one-line summary for lists and logs.
|
|
75
|
+
- `calc_payload_sym_num` now uses the SF-dependent explicit-header spare
|
|
76
|
+
nibbles (`sf - 7`) instead of a fixed 3, fixing symbol-count undercounts
|
|
77
|
+
for SF < 10 (gr-lora_sdr packets at SF 7-9 previously dewhitened one
|
|
78
|
+
nibble short).
|
|
79
|
+
- `apply_time_shift` fractional delays are zero-padded so the FFT wrap cannot
|
|
80
|
+
alias into the signal.
|
|
81
|
+
- **Short preambles** — stage 3 now demodulates the final preamble upchirp
|
|
82
|
+
from `l + min(4, N_preamble_up - N_detect)` instead of the paper's fixed
|
|
83
|
+
U7 (`l+4`), so preambles with only 6 upchirps (6 up + 2 sync + 2.25 SFD)
|
|
84
|
+
decode correctly when configured with `N_preamble_up=6`.
|
|
85
|
+
- **Header checksum fix** — the `c0` checksum bit used `bit0(n1)` where
|
|
86
|
+
gr-lora_sdr's algorithm uses `bit3(n2)`; valid headers were being rejected.
|
|
87
|
+
- **`decode_file` small-chunk performance** — the sub-sample timing probe is
|
|
88
|
+
only run once a packet is fully buffered, removing the per-chunk repeat
|
|
89
|
+
cost while a packet is still streaming in.
|
|
90
|
+
- **Packaging** — license and authorship follow the LibreCube convention
|
|
91
|
+
(MIT, `CONTRIBUTORS.txt`); wheel and source distribution rebuilt for 0.3.0.
|
|
92
|
+
|
|
93
|
+
### Fixed
|
|
94
|
+
|
|
95
|
+
- Explicit-header checksum validation (see Changed).
|
|
96
|
+
|
|
97
|
+
### Tests
|
|
98
|
+
|
|
99
|
+
- Reference OTA packet-start positions updated for the unwrapped-STO sync.
|
|
100
|
+
- `tests/test_sync_paper.py` — estimator identities (Eq. 19 = Eq. 20 at
|
|
101
|
+
`M_hat=0`), Eq. 17/18 round-trips, noiseless fractional CFO/STO recovery,
|
|
102
|
+
and end-to-end decode with injected integer/fractional CFO and STO.
|
|
103
|
+
- `scripts/gr_ref.py`, `scripts/gr_packet_test.py` — generate packets with
|
|
104
|
+
gr-lora_sdr's TX (SF 7-10, CR 1/2/4, with/without CRC, with/without offsets)
|
|
105
|
+
and verify the payload against the transmitted ground truth and gr-lora_sdr's
|
|
106
|
+
RX chain.
|
|
107
|
+
- `scripts/ota_test.py` — decode the OTA capture `dataset/lora_capture2.dat`
|
|
108
|
+
(4× "Hi from Shayan") at the reference positions and cross-check the payload
|
|
109
|
+
with gr-lora_sdr's RX.
|
|
110
|
+
- `docs/guide/sync-algorithm.md` — equation-by-equation mapping of the
|
|
111
|
+
algorithm, the preamble-length generalization, and the `N/2` disambiguation.
|
|
112
|
+
|
|
113
|
+
## v0.2.0 (2026-08-06)
|
|
114
|
+
|
|
115
|
+
Breaking: the decoder now returns `Packet` objects on every path, and the
|
|
116
|
+
streaming API replaces the old `detect_packets`.
|
|
117
|
+
|
|
118
|
+
### Added
|
|
119
|
+
|
|
120
|
+
- **`Packet` result type** — stable schema with `ok` / `error` / `crc_valid`,
|
|
121
|
+
`snr_est`, `freq_offset_hz`, sample positions and diagnostics; `to_dict()`
|
|
122
|
+
for JSON serialization.
|
|
123
|
+
- **`LoRaDecoder.decode_stream` / `flush` / `reset`** — live chunked decoding;
|
|
124
|
+
packets may straddle chunk boundaries and memory stays bounded.
|
|
125
|
+
- **`decode_file` streams from disk** — new `iter_iq_chunks` reader
|
|
126
|
+
(raw float32/int16 + stereo WAV) keeps memory bounded for multi-GB files.
|
|
127
|
+
- **`estimate_snr`** — preamble-based SNR estimator (previously documented
|
|
128
|
+
but never implemented).
|
|
129
|
+
- **`load_iq` / `save_packets` / `sample_rate`** I/O helpers.
|
|
130
|
+
- Stateful linear-phase FIR decimator, preamble gate, and pending/truncation
|
|
131
|
+
handling in the streaming scan.
|
|
132
|
+
|
|
133
|
+
### Removed
|
|
134
|
+
|
|
135
|
+
- `detect_packets` (replaced by `decode_iq`) and the `multi_packet` module.
|
|
136
|
+
|
|
137
|
+
### Changed
|
|
138
|
+
|
|
139
|
+
- `full_decode` returns a `Packet` instead of a dict.
|
|
140
|
+
- Constructor gains implicit-header, chase and streaming tuning parameters.
|
|
141
|
+
- `decode_file` / `decode_iq` always return `list[Packet]`.
|
|
142
|
+
|
|
143
|
+
### Tests
|
|
144
|
+
|
|
145
|
+
- Streaming equivalence across chunk sizes, packet splits across chunk
|
|
146
|
+
boundaries, synthetic implicit-header round-trips at SF 7–12, SNR estimator,
|
|
147
|
+
and I/O helpers (46 tests).
|
|
148
|
+
|
|
149
|
+
## v0.1.0 (initial)
|
|
150
|
+
|
|
151
|
+
- Pure-Python LoRa receiver with the 3-stage Xhonneux et al. synchronizer.
|
|
152
|
+
- GNU Radio TX/RX flowgraphs and AWGN performance sweeps.
|