Skip to main content

SoftLoRa

Google Summer of Code 2026

Decode LoRa packets from radio recordings and live SDR streams, in pure Python.

Point it at an IQ recording and it finds the packets, corrects the frequency and timing errors introduced by the radio link, and gives you back the payload. No GNU Radio or C++ needed — just numpy and scipy.

It was built for satellite downlinks, where signals are weak and Doppler-shifted, and is tested on synthetic data, over-the-air captures and real satellite passes.

Developed for Google Summer of Code 2026 with LibreCube.

Install

pip install softlora

Python 3.9+. The decoder needs only numpy and scipy.

Receiving live off an SDR additionally needs the SoapySDR bindings, which are a system package rather than a pip one — see Receive from an SDR.

Decode a recording

from softlora import LoRaDecoder

decoder = LoRaDecoder(sf=10, bw=125_000, fs=125_000, fc=437e6)

for packet in decoder.decode_file("recording.wav"):
    print(packet.payload_text, packet.crc_valid)

The four arguments describe the signal you are decoding:

Argument Meaning
sf Spreading factor, 7–12
bw LoRa bandwidth in Hz
fs Sample rate of your recording in Hz
fc Center frequency in Hz

.wav, .cfile, .dat and .bin files are supported. A recording may hold several packets, so you always get a list back.

Decode a live stream

Feed IQ chunks as they arrive from an SDR. Packets are returned as soon as they are complete, even when one spans two chunks:

decoder = LoRaDecoder(sf=10, bw=125_000, fs=250_000, fc=437e6)

while receiving:
    for packet in decoder.decode_stream(read_iq_from_sdr(8192)):
        print(packet.payload_text)

for packet in decoder.flush():      # decode whatever is left in the buffer
    print(packet)

Receive from an SDR

One ready-to-run receiver, in examples/, for three common radios -- set RADIO to the one you have:

python examples/sdr_live.py                   # RTL-SDR, Airspy or HackRF

It is a thin script over softlora.sdr.LiveReceiver, which owns the radio, the reader thread and the frequency correction:

from softlora.sdr import LiveReceiver, format_packet

with LiveReceiver(436.13125e6, sf=8, radio='airspy') as rx:
    for packet in rx:
        print(format_packet(packet))
436.13125 MHz  sf=8  bw=125k  fs=1000k (8x bw, decim 3)  offset=0k   Ctrl-C to stop

[16:04:38] t=  22.43s  snr=+11.7dB  cfo=     -8Hz
  len=15  cr=4/5  crc=on
  hex  736f66746c6f726120626561636f6e
  text 'softlora beacon'

It tunes off-channel to dodge the radio's DC spike, decimates to a rate the decoder likes, and folds each packet's residual frequency error back into the software shift — so the receiver calibrates itself out of its own crystal error while it runs. RECORD keeps the IQ for offline replay.

That capture is the bundled examples/arduino/lora_beacon sketch, which beacons one LoRa packet every 3 s from an STM32 + SX1262 at exactly these defaults — a transmitter to test against when you have only one radio.

softlora.sdr needs SoapySDR, which is not on PyPI:

sudo apt install soapysdr-tools python3-soapysdr
sudo apt install soapysdr-module-rtlsdr    # or -airspy, or -hackrf
python3 -m venv --system-site-packages .venv

Reading the result

Every decode path returns Packet objects. The fields you will usually want:

packet.payload_bytes   # the data
packet.payload_text    # the same data decoded as UTF-8
packet.crc_valid       # True when the payload passed its checksum
packet.snr_est         # signal-to-noise estimate in dB
packet.ok              # a packet was found and demodulated

ok and crc_valid answer different questions. ok=True, crc_valid=False means a packet arrived but was corrupted on the way. Filter on packet.crc_valid is True when you only want trustworthy payloads.

A LoRa packet

Each packet starts with a preamble of plain upchirps, then a sync word, then a 2.25-symbol start-of-frame delimiter (SFD) made of downchirps, and finally the header, payload and CRC. All four regions are visible below.

A real LoRa packet received from the Polytech Universe-3 (PU-3) satellite (SF8, 62.5 kHz bandwidth). The payload is truncated for display.

Going further

Topic Where
Runnable examples examples/
Packets without a header Quick start
Tuning the decoder (DecoderSettings) The decode pipeline
Rescuing weak packets with Chase decoding Chase guide
Live receive off RTL-SDR / Airspy / HackRF examples/README.md
Live SDR chain via GNU Radio examples/README.md
Doppler and carrier offset for satellites Ground-station use
Benchmarks across SF 7–12 Performance
How synchronization works Sync algorithm
Full API reference Documentation site

References

[1] M. Xhonneux, O. Afisiadis, D. Bol, and J. Louveaux, "A Low-Complexity LoRa Synchronization Algorithm Robust to Sampling Time Offsets," IEEE Internet of Things Journal, 2021. arXiv:1912.11344

[2] J. Tapparel, O. Afisiadis, P. Mayoraz, A. Balatsoukas-Stimming, and A. Burg, "An Open-Source LoRa Physical Layer Prototype on GNU Radio," SPAWC, 2020.

License

MIT — see LICENSE and CONTRIBUTORS.txt.

Metadata

Release files for softlora 0.5.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for softlora 0.5.0
File Size Uploaded Provenance Actions
softlora-0.5.0.tar.gz 99.9 kB Without provenance Details for softlora-0.5.0.tar.gz

Built distribution (wheel)

Table of built distributions (wheels) for softlora 0.5.0
File Interpreter ABI Platform Actions
softlora-0.5.0-py3-none-any.whl Python 3 none any Details for softlora-0.5.0-py3-none-any.whl

Total release size: 157.1 kB

Release files / softlora-0.5.0.tar.gz

Download URL softlora-0.5.0.tar.gz
Size 99.9 kB
Tags Source
SHA-256 checksum
How to use checksums
277b89ea5d74b2a0933d78a6e544f6c3a055b8ab7591d9d7a8b5393af122faf2
BLAKE2b-256 checksum
How to use checksums
3a948ce2f69423d43f829fc659b4cbec26adda5ffe888c32220f5d057299857c
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.11.16

Release files / softlora-0.5.0-py3-none-any.whl

Download URL softlora-0.5.0-py3-none-any.whl
Size 57.2 kB
Tags Python 3
SHA-256 checksum
How to use checksums
47f84b0e58d37b9b5bf6d324b2d024677d4e343d7da58cd312794c90436cc685
BLAKE2b-256 checksum
How to use checksums
1fddcb20cdd4399b94b134a2f1b980d62624b04358f5bb13bdcf3143a6515d7f
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.11.16

Security for softlora 0.5.0

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

No provenance found

None of the files in this release were uploaded with provenance metadata.

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page