oljasz.com / field notes
Entry 012 of 016 / D Radio
Warsaw
Last entry 07.07.2026
No. 012SDR for SIGINT / Part 1 of 5

Listening to an Entire 868 MHz Band at Once: Building an SDR Hub

One RTL-SDR stream, fanned out to a wM-Bus meter decoder, a wideband burst hunter, and a 200-protocol device classifier - all running at the same time without ever stopping the listen. Architecture of a DIY SIGINT hub.

Filed
Reading
5 min
Section
D / Radio & DSP
Tags

I put an 868 MHz antenna on the roof and asked a simple question: what is actually flying around up there? Smart meters, LoRa nodes, weather stations, blind motors, car key fobs, who knows. The 868 MHz ISM band in Europe is a busy place.

The naive approach is to tune your SDR to one frequency, decode one thing, then retune. That is fine for a hobby afternoon. But I wanted to decode water meters, hunt for unknown signals, and classify named devices all at the same time, continuously, without ever stopping the listen. This is the first post in a SIGINT series about what I built and what I found.

01The architecture: capture once, fan out

An RTL-SDR at 2.0 MS/s produces a 2 MHz-wide window of raw I/Q samples. That single stream is enough to feed several independent consumers if you stop thinking of the SDR as "a radio tuned to one thing" and start thinking of it as "a firehose of complex samples."

The split I landed on:

  • The dongle lives on a Raspberry Pi at the antenna and exposes the raw I/Q over TCP (rtl_tcp).
  • A Mac does all the DSP. The Pi is a cheap antenna node; the heavy lifting happens where there are CPU cores to spare.
  • One client reads the stream and fans every chunk out to three workers.
Listing 1 / text
  Pi --- RTL-SDR --[ rtl_tcp, 2.0 MS/s ]--> Mac
                                             |
                          +------------------+------------------+
                          |                  |                  |
                     METERS thread      HUNTER thread      rtl_433 thread
                  (wM-Bus decode)    (FFT burst finder)   (200+ protocols)

Why offload to the Mac? Bandwidth. 2.0 MS/s of 8-bit I/Q is ~32 Mbit/s. That fits comfortably through an SSH tunnel. A HackRF at 20 MS/s would not - that has to be a local USB3 device. Know your link budget before you design the topology.

02The fan-out

The receive thread does almost nothing - read bytes, convert to complex, hand the same buffer to everyone:

Listing 2 / python
def rx_thread():
    s = socket.create_connection((RTL_HOST, RTL_PORT))
    s.recv(12)                       # rtl_tcp header
    cmd(2, FS); cmd(1, FC)           # set rate, center
    cmd(3, 1); cmd(4, 490)           # manual gain, 49 dB
    leftover = b""
    while True:
        d = leftover + s.recv(1 << 16)
        m = len(d) & ~1              # whole I/Q pairs only
        chunk, leftover = d[:m], d[m:]   # carry the odd byte
        a = np.frombuffer(chunk, np.uint8).astype(np.float32) - 127.5
        c = (a[0::2] + 1j * a[1::2]).astype(np.complex64)
        ingest_iq(c, chunk)          # -> hunter, meters, rtl_433

That leftover byte matters. TCP does not respect your I/Q pair boundaries, and np.frombuffer on an odd-length buffer throws. Carry the stray byte to the next read. I learned that one from a crash.

03The three workers

Meters digitally tune to the wM-Bus channel layout, resample to the rate rtl_wmbus expects, and pipe to rtl_wmbus | wmbusmeters. This is where the smart-meter readings come from - that is the next post, and it has a genuinely surprising twist.

The hunter runs a continuous FFT over the whole 2 MHz, finds every burst that pokes above the noise floor, fingerprints it (center, bandwidth, FSK vs OOK vs chirp, baud, deviation) and guesses the protocol by frequency. New or unusual signals show up live. Over a few hours it logged thousands of bursts: LoRa on 868.1 and 868.3, Z-Wave at 869.85, an io-home FSK signal at 869.0 (almost certainly someone's roller blinds), and a big pile of unidentified SRD traffic.

rtl_433 is the secret weapon. It has decoders for 200+ consumer protocols. I feed it the raw I/Q bytes directly - no extra DSP, since it wants the same unsigned 8-bit format the dongle produces. It identified a neighbour's Bresser 6-in-1 weather station by model and serial, complete with temperature and humidity, within seconds.

Cost of running rtl_433 on the live stream: I benchmarked it at about 1.7% of one core. It processed 60 seconds of I/Q in just over a second. There is no reason not to run it.

04Why a hub and not three scripts

The point of the fan-out is that the workers are blind to each other. The hunter does not know the meters exist. rtl_433 does not care about LoRa. Each one sees the same samples through its own lens, and together they paint a far more complete picture of the band than any single decoder would.

That is the core SIGINT idea: one capture, many interpretations, never stop listening.

A small web server exposes a live event feed so I can watch decodes, bursts, and device IDs scroll past in a browser. Locked to read-only - no frequency control from the web, on purpose.

In the next post: how a $30 SDR read my water meter, the one-line bug that made it impossible for a day, and the factory key the water utility apparently never changed.

This is part of a series on rooftop SIGINT with cheap SDR hardware. Everything here is passive reception of my own and ambient signals, which is legal in the EU. Decoding payloads and transmitting are different matters, covered later in the series.

Written by

Paweł Oljasz

Engineering Leader - infrastructure, security, AI, embedded/IoT and SDR. 15+ years in production; leads a team of senior specialists. This notebook is where the lab work gets written down.

Cite this entry
@misc{oljasz2026sdrwideband,
  author       = {Oljasz, Pawe{\l}},
  title        = {Listening to an Entire 868 {MHz} Band at Once: Building an {SDR} Hub},
  howpublished = {Lab notebook, entry 012},
  year         = {2026},
  month        = jun,
  url          = {https://oljasz.com/blog/sdr-wideband-hub}
}

Entry 012 / filed 29.06.2026 / stable URL, no tracking parameters.