Point a COSMOS grid B210 at 1090 MHz and decode the ADS-B messages that airliners broadcast — call sign, altitude, and live position — using an Ansible playbook that grabs, compiles, and runs the readsb decoder against the SDR via SoapyUHD.
Aircraft continuously broadcast ADS-B (Automatic Dependent Surveillance–Broadcast) on 1090 MHz: their ICAO address, flight number, barometric altitude, and GPS position, in Mode-S extended-squitter frames. Any SDR that tunes 1090 MHz with a few MHz of bandwidth can receive them, and open-source decoders turn the raw IQ into a live aircraft list.
This tutorial uses a USRP B210 on the COSMOS grid as the receiver and wiedehopf/readsb — a widely used Mode-S/ADS-B decoder — as the software. The whole flow (install build dependencies, clone, compile with SoapySDR support, run against the B210, summarize decoded aircraft) is driven by a small Ansible playbook, so it is one command to reproduce. It is a passive, receive-only experiment: nothing transmits.
The takeaway is a repeatable "from source to decoded aircraft" pipeline you can adapt to other Mode-S / SoapySDR work, and a concrete example of driving an SDR experiment on COSMOS with Ansible instead of by hand.
After completing this tutorial you will be able to:
readsb from source with SoapySDR support and run it against a USRP B210.| Difficulty | Beginner |
| Estimated time | 30 min |
| Domain / sandbox | grid |
| Topic group | SDR & GNU Radio |
| Last verified | 2026-07-12 |
Background knowledge
readsb is assumed; a quick skim of SDR hardware helps.Account & access
Devices / nodes
| Resource | Role | Qty | Notes |
|---|---|---|---|
| A B210-equipped grid node | ADS-B receiver | 1 | Window-row nodes (e.g. node18-19, node18-2, node20-x) have better reception than nodes deep in the grid |
Disk images
| Image | Load onto | Provides |
|---|---|---|
ubuntu2404-uhd4.9-gr3.10.ndz |
the B210 node | Ubuntu 24.04 + UHD 4.9 + SoapyUHD + GNU Radio, USRP FPGA images pre-baked |
Software components
| Component | Version | Source |
|---|---|---|
| UHD | 4.9 | preinstalled in the image (/usr/local) |
| SoapyUHD | matches UHD 4.9 | preinstalled in the image |
| readsb | dev branch |
built from source by the playbook |
| Ansible | distro | on the grid console |
Spectrum / RF / special
Receive-only at 1090 MHz (Mode-S / ADS-B downlink). No transmit, no license implications. An antenna suitable for ~1 GHz on the B210 RX2/TX-RX port improves range; reception is best from nodes near the windows.
A single node with a USRP B210 attached over USB 3.0. The node's control interface is used only to drive it; the RF path is the B210's antenna port receiving over the air. No data-plane or inter-node connectivity is required — this is a one-node passive receiver.
[ grid B210 node ] --USB3--> [ USRP B210 ] --ant--> 1090 MHz ADS-B (aircraft)
The build/run steps are packaged as an Ansible role in the COSMOS tutorials bundle. First image and boot the node, then fetch the bundle on the console.
ssh <username>@console.grid.cosmos-lab.org
omf tell -a offh -t node18-19
omf load -i ubuntu2404-uhd4.9-gr3.10.ndz -t node18-19
omf tell -a on -t node18-19
omf stat -t node18-19
cd ~/tutorials
The bundle ships an ansible.cfg, a dynamic inventory (cosmos-inventory.py, which lists POWERON nodes from omf stat, narrowed by OMF_NODES), and the sdr/adsb.yml playbook with its sdr_common and adsb roles.The playbook targets exactly one node — the first host in the inventory — so scope the inventory to your B210 node with OMF_NODES, then run it. It performs three visible stages: grab → compile → run.
Passwordless ssh root@<node> works from the console. Verify UHD sees the B210 (the FPGA images ship in the image; type=b200 filters out any networked X310s the node can also reach):
ssh root@node18-19 'uhd_find_devices --args "type=b200" 2>&1 | grep -A3 "Device Address"'
--------------------------------------------------
-- UHD Device 0
--------------------------------------------------
type: b200
product: B210
serial: 3....
If instead you see No UHD Devices Found, run uhd_images_downloader once (see Troubleshooting).
cd ~/tutorials
export OMF_NODES="node18-19"
ansible-playbook sdr/adsb.yml
The adsb role does the whole pipeline. Grab — install build dependencies (including libzstd-dev; note it does not install soapysdr-module-uhd from apt, which would pull an older UHD 4.6 that conflicts with the image's UHD 4.9) and clone the decoder:
apt-get install -y git build-essential pkg-config libzstd-dev libsoapysdr-dev libncurses-dev zlib1g-dev libusb-1.0-0-dev
git clone --depth 1 https://github.com/wiedehopf/readsb /root/readsb
Compile — build with SoapySDR support:
make SOAPYSDR=yes -j4 # in /root/readsb, produces ./readsb
Run — decode for 60 s, selecting the B210 through SoapyUHD and pointing UHD at the pre-baked FPGA images:
UHD_IMAGES_DIR=/usr/share/uhd/4.9.0/images timeout 60 /root/readsb \
--device-type=soapysdr --soapy-device="driver=uhd,type=b200" \
--freq=1090000000 --gain=50 \
--write-json /run/readsb --write-json-every 1 --quiet
readsb exits with code 124 when timeout stops it — that is expected, not an error.
The role summarizes the aircraft JSON at the end of the run:
python3 -c "import json; d=json.load(open('/run/readsb/aircraft.json'));
ac=d.get('aircraft',[]); wp=[a for a in ac if a.get('lat') is not None];
print('AIRCRAFT HEARD: %d WITH POSITION: %d' % (len(ac), len(wp)));
[print('%-8s %-8s %8s %10s %11s %6s %7s' % (a.get('hex'),(a.get('flight') or '').strip(),
a.get('alt_baro'),a.get('lat'),a.get('lon'),a.get('messages'),a.get('rssi')))
for a in sorted(ac,key=lambda x:-(x.get('messages') or 0))[:10]]"
AIRCRAFT HEARD: 7 WITH POSITION: 4
ICAO FLIGHT ALT_ft LAT LON MSGS RSSI
a1b2c3 UAL1705 35000 40.7128 -74.0060 412 -18.3
a4d5e6 AAL1820 37000 40.8500 -73.9000 287 -21.1
...
Real flights (here United 1705 and American 1820) with call sign, altitude, and live latitude/longitude confirm end-to-end reception and decode. (ICAO addresses and exact coordinates above are illustrative.)
Success = the aircraft table is non-empty and at least one aircraft reports a position (lat/lon). A steadily rising MSGS count across repeated runs, and RSSI values in roughly the −15 to −30 dB range, indicate healthy reception. If AIRCRAFT HEARD is 0, see Troubleshooting — most often it is antenna/placement or the wrong SDR being selected.
omf tell -a offh -t node18-19
Nothing needs to be saved — the build lives on the node's ephemeral image. Re-imaging returns the node to a clean state.
| Symptom | Likely cause | Fix |
|---|---|---|
No UHD Devices Found |
UHD FPGA/firmware images missing | Run uhd_images_downloader once; it writes to /usr/share/uhd/4.9.0/images/ (already handled by the sdr_common role) |
| readsb grabs a networked X310 instead of the B210 | driver=uhd matched the first UHD device on the network |
Keep type=b200 in --soapy-device to pin the local B210 |
make fails on zstd.h |
libzstd-dev not installed |
Install libzstd-dev (the role does this) |
| SoapySDR/UHD version conflict at runtime | soapysdr-module-uhd from apt (UHD 4.6) shadows the image's UHD 4.9 SoapyUHD in /usr/local/lib |
Do not apt install soapysdr-module-uhd; use the SoapyUHD that ships in the image |
AIRCRAFT HEARD: 0 |
Weak signal / node deep in the grid | Use a window-row node (node18-x–node20-x), attach a ~1 GHz antenna, raise --gain toward 60–70 |
| readsb exits 124 | timeout stopped it after the run window |
Expected — not an error |
Author(s): COSMOS team · Last verified: 2026-07-12 · Tested image/release: ubuntu2404-uhd4.9-gr3.10.ndz · Tags: sdr, uhd, soapysdr, adsb, ansible