Test with a Pluto SDR
Overview
Use this guide to confirm your device is transmitting valid Hubble Satellite Network packets before you wait on a real satellite pass to tell you.
- What: A Pluto SDR — a software-defined radio (SDR) that plugs into your computer over USB — sits on your bench and receives your device's transmissions directly, so you can check the radio works within minutes instead of waiting for a pass. (For background on how packets travel from your device through a satellite pass to the cloud, see How Hubble Satellite Network works.)
- How: The
pyhubblenetworkCLI drives the SDR through a Hubble-provided Docker image that handles RF reception and decoding, then prints decoded packets to your terminal. - Result: If packets appear with your device ID and incrementing sequence numbers, the device is transmitting valid, decodable Hubble Satellite Network packets. Scanning is the happy path. Only if scanning finds nothing do you need to capture raw samples and send them to Hubble.
A local scan receives RF off the air — it does not send anything to the cloud. Packets seen here will not appear in your Hubble dashboard. Confirming the full end-to-end path still requires a real satellite pass. See the Satellite Quick Start.
What you need
| Requirement | Detail |
|---|---|
| ADALM-PLUTO | Analog Devices ADALM-PLUTO SDR, connected over USB. |
| Docker | Docker Desktop (macOS/Windows) or Docker Engine (Linux), with local access granted on your machine, running before you scan. |
| Python | Python 3.9 or later, for pyhubblenetwork CLI. |
| A transmitting device | Powered on and running a satellite transmit build. See Satellite Quick Start Step 4. |
| Device key | Optional, only needed to decrypt payloads locally. The base64 or hex key from device registration. |
Set up
1. Prepare the Pluto
Do this once, before your first scan. What's needed depends on your OS. The Pluto's USB networking defaults to RNDIS mode, which Windows and Linux support natively but macOS does not.
| OS | What to do |
|---|---|
| Linux | Nothing. Plug it in. |
| Windows | Install the ADI USB drivers before plugging the Pluto in. Do not change usb_ethernet_mode — Windows does not support NCM. |
| macOS | Switch the Pluto to NCM mode (below). |
Either way the Pluto ends up reachable at 192.168.2.1, which is what the scanner uses by default.
macOS: switch to NCM mode
-
Plug the Pluto into USB. It mounts as a drive named PlutoSDR.
-
Open
config.txton the PlutoSDR drive. Under[SYSTEM], set:usb_ethernet_mode = ncmNote: Spelling matters — an unrecognized value silently falls back to RNDIS with no error.
If config.txt has no usb_ethernet_mode line at all, the Pluto is running firmware older than v0.33, which predates NCM support. Update it using the ADI firmware update guide, then repeat.
-
Save and eject the drive. The drive reappears after a few seconds; that is not a reboot.
-
Power-cycle the Pluto. The setting is only applied at boot. (Alternatively, also set
reset = 1under[ACTIONS]in step 2 and the Pluto reboots itself on eject.) -
A new network interface appears and the Pluto answers at
192.168.2.1. You can send a test ping in terminal to confirm:ping 192.168.2.1
2. Install pyhubblenetwork
Open your terminal and install the Hubble CLI (source and full command reference on GitHub):
pip install pyhubblenetwork
For CLI-only use, pipx install pyhubblenetwork keeps it isolated from your other Python environments. If you already have it, upgrade:
pip install --upgrade pyhubblenetwork
3. Start Docker
Start Docker Desktop (or confirm the Docker daemon is running on Linux) and leave it running. The first scan pulls the SDR image, which takes a few minutes; later scans reuse the cached image.
Docker must have local network access to use the Pluto SDR. For example, on macOS you may confirm the application has access under Privacy & Security > Local Network.
Scan for satellite packets
With the Pluto connected and your device transmitting nearby:
hubblenetwork sat scan
The command verifies Docker, starts the SDR container, waits for the receiver to come up, and then streams decoded packets until you press Ctrl+C.
Useful variations:
# Stop after 60 seconds
hubblenetwork sat scan --timeout 60
# Stop after 5 packets
hubblenetwork sat scan -n 5
# Decrypt payloads locally with your device key (hex or base64, 16 or 32 bytes)
hubblenetwork sat scan --key "<device key>"
# JSON output, one object per line
hubblenetwork sat scan -o json
What success looks like
Packets print as a table as they arrive:
+--------------+--------+--------+----------------------------+----------+----------+--------------+----------------------+
| DEVICE_ID | SEQ | TYPE | TIME | RSSI_DB | CHANNEL | FREQ_OFFSET | PAYLOAD |
+--------------+--------+--------+----------------------------+----------+----------+--------------+----------------------+
| 1a2b3c4d | 41 | DATA | Tue Aug 11 10:14:02 2026 | -62.4 | 17 | -1240 | |
| 1a2b3c4d | 42 | DATA | Tue Aug 11 10:14:34 2026 | -61.8 | 22 | -1198 | |
+--------------+--------+--------+----------------------------+----------+----------+--------------+----------------------+
You're looking for:
- The device ID matching the device you registered.
- Sequence numbers incrementing across packets, which shows a live transmit loop rather than one lucky decode.
That confirms the device is transmitting decodable Hubble Satellite Network packets at the RF layer.
Decrypting payloads
Passing --key decrypts each payload locally with the same AES-CTR scheme used on the terrestrial network. The counter source (UNIX_TIME or DEVICE_UPTIME) is auto-detected from the packets and announced; force it with --counter-mode if needed:
hubblenetwork sat scan --key "<device key>" --counter-mode DEVICE_UPTIME
Packets the key cannot decrypt are hidden by default. To see them anyway, with a pass/fail column:
hubblenetwork sat scan --key "<device key>" --show-failed-decryption
Packets appearing here but failing to decrypt usually means a key or counter-mode mismatch between your firmware and your device registration, not an RF problem.
If scanning finds nothing
Work through these first:
| Check | Detail |
|---|---|
| Docker is running | The scan fails immediately if the daemon isn't up. |
| Pluto plugged in first | Connect the Pluto before starting the scan, and make sure nothing else is using it. |
| Device is transmitting | Confirm via firmware logs that the transmit loop is actually running. |
| Device is close by | Keep the device within a few feet of the Pluto, clear of large metal objects. |
| Antenna attached | Both the Pluto and the device need their antennas connected. |
| Latest CLI | pip install --upgrade pyhubblenetwork. |
If you've been through those and still see nothing, capture raw samples so Hubble can analyze the RF offline.
Capture samples for Hubble
Leave the device transmitting and the Pluto connected, then run both commands:
# 10 seconds of raw IQ samples, written to a NumPy .npy file
hubblenetwork sat record 10 --output capture.npy
# 10 seconds recorded and re-analyzed into a plain-text RF diagnostic
hubblenetwork sat signal-report 10 --output report.txt
recordcaptures the raw radio signal with no decoding. This is the file Hubble can reprocess.signal-reportrecords IQ and analyzes it offline into a readable link-health summary: per-symbol timing and drift, channel-hopping validation, amplitude and SNR, and chipset metrics. It reports signal quality only and contains no decoded payloads.
Both accept --output PATH (a timestamped filename is generated if you omit it). Capturing longer than 10 seconds produces a larger file and covers more transmit attempts; 10 to 30 seconds is usually enough.
signal-report contains no payload data. The raw .npy capture contains whatever your device transmitted over the air, in encrypted form.
Send them to Hubble
Open a support ticket from your Hubble Dashboard — account icon in the top right, then Support — and attach both files along with:
- Your device ID and the hardware/chipset you're testing.
- The SDK sample or firmware build you flashed.
- The counter mode the device was registered with.
- What you already tried from the checklist above.