Sharing a project I built this summer (GSoC 2026 with LibreCube) that should
interest colleagues here: a software-defined LoRa ground station for
the TinyGS network that runs entirely in Python. ( No more ESP based Boards)
The architecture is the same idea as SatNOGS: any Linux box + an SDR + an
antenna. An SDR streams IQ into a pure-Python LoRa decoder (softlora), and
a small station client (tinygs) publishes decoded packets to TinyGS over
MQTT-TLS.
It’s still in active testing, if anyone runs an omnidirectional VHF/UHF setup
and wants to try it on a LoRa satellite pass, I’d love feedback. My email is Hamza.mohammed.hasan[at]gmail[dot]com.
This is incredible timing! I was just wondering about setting up a TinyGS SDR ground station using my Linux environment and an RTL-SDR V4 instead of buying an additional hardware node, and then I found your post.
Thank you for sharing this Pure-Python LoRa decoder project (softlora). I’m very excited to check out your setup guide and test it out on upcoming satellite passes.
Thanks for the poke! Thanks to you, I noticed this amazing thread right away.
My current little hardware node is already working hard and keeping me satisfied, but it’s great timing because I was just wondering about setting up an SDR-based station using my Linux environment and RTL-SDR V4 instead of buying an additional node.
I wanted to check if this behavior is expected when receiving ambient noise / before locking onto an actual satellite pass, or if I should fine-tune specific parameters (like DC offset, gain, or synchronization thresholds) to get clean decodes.
Any advice or feedback would be greatly appreciated!
Ah, I might not have been clear—I’m actually right in the middle of troubleshooting!
I’m trying to get the softlora decoder running, but I keep getting checksum mismatch and header decode failures (as shown in my logs above). I’m still figuring out if it’s just ambient noise or if my setup parameters need tweaking.
So no active decoding yet, just a workstation trying to catch its first packet!
OK - with the sdr it’s easy to find out, if the signal is ok, just record a few packets from a strong satellite with positive snr (Tianqi) and then check the recording with inspectrum, or as a different method, run a Heltec lora V3 board parallel on the same antenna (I think it’s worth investing $18, to avoid puzzling around for weeks) - then you see the signal quality and you can directly compare the performance.
Thank you for the practical suggestions! Using inspectrum or comparing with a hardware board sounds like a solid approach to verify the RF path.
That said, @studhamza (Hamza), I’m really curious to hear your thoughts on this as well! When testing softlora with an SDR stream, are these persistent checksum mismatch or header decode failed errors expected when no valid sync word / strong packet is locked yet? Or is there a specific tuning trick in softlora we should look out for?
I dont have much experience with soft-decoding lora, but the behaviour you are describing lets speculate, that the preamble is detected correctly and during header-decode things go off rails.
According to my experience, even though the Lora signals are very robust, they are extremly sensitive to phase-issues, which can be caused by a jittery time-base or a poor SAW-filter. Unfortunately these problems are difficult to measure, but this comes into my mind, when I read the description of your problem. One more reason to connect a lora-hardware in parallel, which can be used to verify the integrity of the signal at least at the SDR-input (compared to other measuring equipment, the lora-board is an extremly powerfull and cheap measuring tool). In addition, with the lora board you have your own LoRa signal generator for testing and you can start with a strong, clean signal and work your way up to weaker and more noisy signals.
Thanks for the detailed insights! That makes a lot of sense regarding phase sensitivity.
Just as a quick update before calling it a night: I actually tried switching over to the 400MHz band as well, but I got very similar header parse/checksum errors there too.
I’ll sleep on it and see what Hamza thinks when he has a moment. Thanks again for all the help today!
As you can see from the console log, it’s actually catching the signal and trying to parse it, but failing on checksum mismatch/header decode. It seems the signal is physically there, but the TLE/timing sync isn’t quite aligning for a full decode. Real-world RF never gets boring!
Just came home from school. Actually this behavior was not intended, its a design flaw. I assume the decoder locks onto non existing packets.
Explanation
The decoder’s sync stage declares a preamble when three consecutive dechirped windows peak in the same FFT bin. That has a small but non-zero false-alarm rate on pure noise. Each false lock then synchronizes a non existing packet and tries to parse an explicit header, which fails its checksum, producing exactly the header decode failed warnings.
For IID circular Gaussian noise, the dechirped FFT bins |Y[k]| are IID Rayleigh, so the argmax bin is uniformly distributed over {0..N-1}. Every window picks a random “peak” bin. The detection condition “3 windows peak within ±1 bin” therefore happens by chance. For a window whose peak is bin b, the next two windows must land in {b−1, b, b+1}:
Thanks for the detailed technical breakdown! My math/theory is a bit rusty, but I’m just relieved to know that my setup and config weren’t the culprit here.
I’ll halt the receiver for now and patiently wait for your updated version. Really appreciate you diving into this so quickly—keep up the great work!
Thank you for your advice. I wanted to try running the example as you suggested, but I’ve hit a bit of a wall. Since I’m still struggling to fully understand how to navigate this and haven’t been able to use the AI assistant effectively to sort it out, I’m afraid I’ve reached my limit for now and have to step back.