A Bluetooth Low Energy (BLE) scanner with advanced Resolvable Private Address (RPA) resolution. Discover nearby BLE devices, track a specific device by MAC address, or resolve privacy-randomized addresses using an Identity Resolving Key (IRK).
Written by: David Kennedy (@HackingDave) Company: TrustedSec
-o -)GPS location stamping requires the gpsd daemon running with a connected GPS receiver. If gpsd is not running, btrpa-scan continues normally without GPS.
| Platform | Install | Start |
|---|---|---|
| macOS | brew install gpsd |
gpsd -n /dev/tty.usbserial-* |
| Debian/Ubuntu | sudo apt install gpsd gpsd-clients |
sudo systemctl start gpsd |
| Fedora/RHEL | sudo dnf install gpsd gpsd-clients |
sudo systemctl start gpsd |
| Arch | sudo pacman -S gpsd |
sudo systemctl start gpsd |
| Windows | Use gpsd via WSL or MSYS2 | See WSL instructions above |
To verify gpsd is working:
# Check that gpsd is listening
gpspipe -w -n 5
# Or use the curses monitor
cgps
| Platform | Notes |
|---|---|
| macOS | Uses CoreBluetooth. IRK mode leverages an undocumented API to retrieve real Bluetooth addresses instead of UUIDs. --active has no effect — CoreBluetooth always scans actively. |
| Linux | May require root or CAP_NET_ADMIN capability for scanning. |
| Windows | Native WinRT Bluetooth API — real MAC addresses available natively. TUI requires pip install windows-curses. |
This project uses pyproject.toml (PEP 621), the modern Python packaging standard. It defines the project as an installable package with a registered CLI command — no need to run .py files directly.
uvx btrpa-scan --all
uvx --from git+https://github.com/hackingdave/btrpa-scan.git btrpa-scan --all
uv tool install btrpa-scan
Or directly from GitHub:
uv tool install git+https://github.com/hackingdave/btrpa-scan.git
pip install btrpa-scan
For GUI support (Flask-based radar interface):
pip install btrpa-scan[gui]
git clone https://github.com/hackingdave/btrpa-scan.git
cd btrpa-scan
pip install .
usage: btrpa-scan [-h] [-a] [--irk HEX] [--irk-file PATH] [-t TIMEOUT]
[--output {csv,json,jsonl}] [-o FILE] [--log FILE]
[-v | -q] [--min-rssi DBM] [--rssi-window N] [--active]
[--environment {free_space,indoor,outdoor}]
[--ref-rssi DBM] [--name-filter PATTERN]
[--alert-within METERS] [--tui] [--gui] [--gui-port PORT]
[--no-gps] [--adapters LIST] [mac]
BLE Scanner — discover all devices or hunt for a specific one
positional arguments:
mac Target MAC address to search for (omit to scan all)
optional arguments:
-h, --help show this help message and exit
-a, --all Scan for all broadcasting devices
--irk HEX Resolve RPAs using this Identity Resolving Key (32 hex chars)
--irk-file PATH Read IRK(s) from a file (one per line, hex format)
-t, --timeout TIMEOUT Scan timeout in seconds (default: 30, or infinite for --irk)
--output {csv,json,jsonl}
Batch output format written at end of scan
-o, --output-file FILE
Output file path (default: btrpa-scan-results.<format>;
use - for stdout)
--log FILE Stream detections to a CSV file in real time
-v, --verbose Verbose mode — show additional details
-q, --quiet Quiet mode — suppress per-device output, show summary only
--min-rssi DBM Minimum RSSI threshold (e.g. -70) — ignore weaker signals
--rssi-window N RSSI sliding window size for averaging (default: 1 = no averaging)
--active Use active scanning (sends SCAN_REQ for additional data)
--environment {free_space,indoor,outdoor}
Distance estimation path-loss model (default: free_space)
--ref-rssi DBM Calibrated RSSI at 1 metre for distance estimation
--name-filter PATTERN Filter devices by name (case-insensitive substring match)
--alert-within METERS Proximity alert when device is within this distance
--tui Live-updating terminal table instead of scrolling output
--gui Launch web-based radar interface in the browser
--gui-port PORT Port for GUI web server (default: 5000)
--no-gps Disable GPS location stamping (GPS is on by default via gpsd)
--adapters LIST Comma-separated Bluetooth adapter names (e.g. hci0,hci1)
Scan for all broadcasting BLE devices (default 30-second timeout):
btrpa-scan --all
With a custom timeout:
btrpa-scan --all -t 60
Search for a specific device by MAC address:
btrpa-scan AA:BB:CC:DD:EE:FF
Resolve Resolvable Private Addresses using an Identity Resolving Key. This mode runs indefinitely by default until stopped with Ctrl+C:
btrpa-scan --irk 0123456789ABCDEF0123456789ABCDEF
The IRK can be provided in several formats:
| Format | Example |
|---|---|
| Plain hex | 0123456789ABCDEF0123456789ABCDEF |
| Colon-separated | 01:23:45:67:89:AB:CD:EF:01:23:45:67:89:AB:CD:EF |
| Dash-separated | 01-23-45-67-89-AB-CD-EF-01-23-45-67-89-AB-CD-EF |
| 0x-prefixed | 0x0123456789ABCDEF0123456789ABCDEF |
Load one or more IRKs from a file. Each line should contain one IRK in any supported hex format. Lines starting with # are treated as comments:
btrpa-scan --irk-file keys.txt
Example keys.txt:
# Alice's phone
0123456789ABCDEF0123456789ABCDEF
# Bob's watch
FEDCBA9876543210FEDCBA9876543210
When multiple IRKs are loaded, each detected RPA is checked against all keys. The summary shows total matches across all keys.
Set the BTRPA_IRK environment variable to avoid passing the key on the command line:
export BTRPA_IRK=0123456789ABCDEF0123456789ABCDEF
btrpa-scan
Priority: --irk > --irk-file > BTRPA_IRK
Only show devices with signal strength above a threshold:
btrpa-scan --all --min-rssi -70
BLE RSSI is inherently noisy. Use a sliding window average for more stable distance estimates and to filter out spurious weak detections:
btrpa-scan --all --rssi-window 5
When windowing is active, the display shows both raw and averaged RSSI (e.g. RSSI: -65 dBm (avg: -62 dBm over 5 readings)), and distance estimation uses the averaged value. The --min-rssi filter also applies to the averaged RSSI, preventing a single noisy spike from dropping a device.
Filter devices by name using a case-insensitive substring match:
btrpa-scan --all --name-filter "AirPods"
Only devices whose advertised name contains the given pattern will be shown. Devices with no name are excluded when a name filter is active.
Passive scanning (the default) only sees advertisements. Active scanning sends SCAN_REQ and gets SCAN_RSP, which can reveal additional service UUIDs and device names:
btrpa-scan --all --active
Note: On macOS, CoreBluetooth always scans actively regardless of this flag. On Linux/BlueZ, active scanning may require root or
CAP_NET_ADMIN.
Distance estimation uses a path-loss exponent that varies by environment. The default (free_space, n=2.0) assumes no obstructions. For more realistic estimates indoors:
btrpa-scan --all --environment indoor
| Preset | Path-Loss Exponent (n) | Use Case |
|---|---|---|
free_space |
2.0 | Open air, line of sight |
outdoor |
2.2 | Parks, parking lots |
indoor |
3.0 | Offices, homes, buildings |
Higher n values produce larger distance estimates for the same RSSI, reflecting signal attenuation from walls and obstacles.
By default btrpa-scan derives the expected RSSI at 1 metre from the advertised TX Power using an empirically validated 59 dB offset (the iBeacon standard). For even better accuracy you can supply a calibrated value measured in your own environment:
--ref-rssi:btrpa-scan --all --ref-rssi -55
When --ref-rssi is set, TX Power is ignored entirely. This also enables distance estimates for devices that don't advertise TX Power.
Trigger an audible bell and visual alert when a device is estimated within a given distance. Requires that the target device advertise TX Power:
btrpa-scan AA:BB:CC:DD:EE:FF --alert-within 5.0
Works in all modes including IRK resolution:
btrpa-scan --irk <key> --alert-within 3.0
Replace scrolling output with a live-updating terminal table, sorted by signal strength:
btrpa-scan --all --tui
The TUI shows all detected devices in a compact table with address, name, RSSI, averaged RSSI, estimated distance, detection count, and last-seen time. Resolved IRK matches are shown in bold, and devices within the --alert-within threshold are highlighted.
Combine with other flags:
btrpa-scan --irk <key> --tui --rssi-window 5 --environment indoor --alert-within 5.0
Launch a browser-based radar interface with an animated sweep, real-time device tracking, and GPS map:
btrpa-scan --all --gui
The GUI features:
GUI mode scans continuously by default (no 30-second timeout). Press Ctrl+C to stop. Use -t to set a specific scan duration:
# Scan for 60 seconds with indoor path-loss model
btrpa-scan --all --gui -t 60 --environment indoor
# Custom port
btrpa-scan --all --gui --gui-port 8080
# Combine with RSSI averaging and proximity alerts
btrpa-scan --all --gui --rssi-window 5 --alert-within 5.0
Note:
--guirequires Flask and flask-socketio (pip install btrpa-scan[gui]). Cannot be combined with--tuior--quiet.
Stream each detection to a CSV file as it happens (useful for long-running scans where you want incremental data):
```bash btrpa-scan --all --l
$ claude mcp add btrpa-scan \
-- python -m otcore.mcp_server <graph>