A Python script that uses an RTL-SDR USB dongle to scan 802.11ah (WiFi HaLow) channels in the sub-GHz spectrum and determine which channel has the least noise/interference.
- 🔍 Scans all 802.11ah HaLow channels in the US 902-928 MHz band
- 📊 Supports multiple channel bandwidths: 1, 2, 4, and 8 MHz
- 📡 Uses RTL-SDR for spectrum analysis
- 🎯 Identifies the cleanest channel with lowest noise floor
- 📈 Provides detailed power spectrum measurements
- ⚡ Fast scanning with averaging for accuracy
- RTL-SDR USB dongle (RTL2832U-based)
- Must support sub-GHz frequencies (some RTL-SDR dongles don't go below 24 MHz)
- Consider using an upconverter if your RTL-SDR doesn't support sub-GHz
- Recommended: RTL-SDR Blog V3 or similar with direct sampling mode
- Python 3.7+
- pyrtlsdr library
- numpy
-
Install RTL-SDR drivers
On Ubuntu/Debian:
sudo apt-get update sudo apt-get install rtl-sdr librtlsdr-dev
On macOS:
brew install librtlsdr
-
Install Python dependencies
pip install -r requirements.txt
-
Configure RTL-SDR access (Linux only)
Create udev rules to allow non-root access:
sudo bash -c 'cat > /etc/udev/rules.d/20-rtlsdr.rules << EOF SUBSYSTEM=="usb", ATTRS{idVendor}=="0bda", ATTRS{idProduct}=="2838", MODE="0666" EOF' sudo udevadm control --reload-rules sudo udevadm trigger
-
Test RTL-SDR connection
rtl_test
Scan all channels with 2 MHz bandwidth:
python halow_scanner.pyScan with 4 MHz bandwidth:
python halow_scanner.py -b 4Scan all available bandwidths:
python halow_scanner.py --all-bandwidthsLess verbose output:
python halow_scanner.py -b 2 -qCustom sample rate (2.4 MHz):
python halow_scanner.py -s 2.4 -h, --help Show help message
-b, --bandwidth Channel bandwidth in MHz: 1, 2, 4, or 8 (default: 2)
-r, --region Regulatory region: US (default: US)
-s, --sample-rate SDR sample rate in MHz (default: 2.4)
-q, --quiet Less verbose output
--all-bandwidths Scan all available bandwidths
============================================================
Scanning 802.11ah HaLow Channels (US region)
Bandwidth: 2 MHz
============================================================
Scanning channel 1 @ 906 MHz (BW: 2 MHz)...
Noise floor: -45.23 dB
Avg power: -42.15 dB
Scanning channel 2 @ 908 MHz (BW: 2 MHz)...
Noise floor: -48.67 dB
Avg power: -44.32 dB
[...]
============================================================
SCAN RESULTS (Sorted by cleanest channel)
============================================================
Rank Ch Freq(MHz) BW(MHz) Noise(dB) AvgPwr(dB)
------------------------------------------------------------
★1 6 916.0 2.0 -51.23 -47.45
2 5 914.0 2.0 -49.87 -46.12
3 2 908.0 2.0 -48.67 -44.32
[...]
============================================================
★ CLEANEST CHANNEL: Channel 6 @ 916.0 MHz (Noise: -51.23 dB)
============================================================
| Channel | Center Freq (MHz) | Available Bandwidths |
|---|---|---|
| 1 | 906 | 1, 2 MHz |
| 2 | 908 | 1, 2 MHz |
| 3 | 910 | 1, 2 MHz |
| 4 | 912 | 1, 2, 4 MHz |
| 5 | 914 | 1, 2, 4 MHz |
| 6 | 916 | 1, 2, 4 MHz |
| 7 | 918 | 1, 2, 4 MHz |
| 8 | 920 | 1, 2, 4 MHz |
| 9 | 922 | 1, 2, 4 MHz |
| 10 | 924 | 1, 2, 4, 8 MHz |
- Initialization: Configures the RTL-SDR dongle with specified sample rate
- Tuning: Centers the SDR on each channel's frequency
- Sampling: Collects IQ samples from the SDR
- FFT Analysis: Performs Fast Fourier Transform to get frequency spectrum
- Power Calculation: Converts to power spectral density in dB
- Noise Estimation: Uses 10th percentile of power to estimate noise floor
- Ranking: Sorts channels by noise level to find the cleanest
- Most RTL-SDR dongles don't support frequencies below 24 MHz natively
- For 902-928 MHz (HaLow), you need:
- An RTL-SDR with direct sampling mode (e.g., RTL-SDR Blog V3)
- Or an upconverter to shift the frequency range
- Or a specialized SDR like HackRF One, LimeSDR, or USRP
- Ensure you have proper authorization to transmit on HaLow frequencies
- This scanner only receives and does not transmit
- Check local regulations for sub-GHz ISM band usage
- Use an appropriate antenna for 900 MHz band
- Quarter-wave antenna length: ~8.2 cm
- Antenna placement affects measurements significantly
Error: "Failed to initialize RTL-SDR"
- Check if RTL-SDR is connected:
lsusb | grep RTL - Verify drivers:
rtl_test - Check permissions (see Installation step 3)
No devices found
- Make sure no other software is using the RTL-SDR (SDR#, GQRX, etc.)
- Try unplugging and reconnecting the dongle
Poor sensitivity / High noise floor
- Check antenna connection
- Try different gain settings
- Move away from interference sources (computers, monitors)
- Use a battery-powered laptop for cleaner power
- Support for EU, China, Japan, Korea frequency allocations
- GUI interface for real-time visualization
- Save scan results to file (CSV, JSON)
- Waterfall display of spectrum over time
- Support for other SDR hardware (HackRF, LimeSDR, etc.)
- Interference source identification
MIT License - Feel free to use and modify
Contributions are welcome! Please feel free to submit pull requests or open issues.