Skip to main content
For the complete documentation index, see llms.txt.

Hubble Network developer documentation: integrate Bluetooth devices with terrestrial and satellite connectivity.

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 pyhubblenetwork CLI 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​

RequirementDetail
ADALM-PLUTOAnalog Devices ADALM-PLUTO SDR, connected over USB.
DockerDocker Desktop (macOS/Windows) or Docker Engine (Linux), with local access granted on your machine, running before you scan.
PythonPython 3.9 or later, for pyhubblenetwork CLI.
A transmitting devicePowered on and running a satellite transmit build. See Satellite Quick Start Step 4.
Device keyOptional, 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.

OSWhat to do
LinuxNothing. Plug it in.
WindowsInstall the ADI USB drivers before plugging the Pluto in. Do not change usb_ethernet_mode — Windows does not support NCM.
macOSSwitch 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​

  1. Plug the Pluto into USB. It mounts as a drive named PlutoSDR.

  2. Open config.txt on the PlutoSDR drive. Under [SYSTEM], set:

    usb_ethernet_mode = ncm

    Note: 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.

  1. Save and eject the drive. The drive reappears after a few seconds; that is not a reboot.

  2. Power-cycle the Pluto. The setting is only applied at boot. (Alternatively, also set reset = 1 under [ACTIONS] in step 2 and the Pluto reboots itself on eject.)

  3. 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:

CheckDetail
Docker is runningThe scan fails immediately if the daemon isn't up.
Pluto plugged in firstConnect the Pluto before starting the scan, and make sure nothing else is using it.
Device is transmittingConfirm via firmware logs that the transmit loop is actually running.
Device is close byKeep the device within a few feet of the Pluto, clear of large metal objects.
Antenna attachedBoth the Pluto and the device need their antennas connected.
Latest CLIpip 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
  • record captures the raw radio signal with no decoding. This is the file Hubble can reprocess.
  • signal-report records 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.