Install · Use · Limits · Troubleshooting · How it works · Contributing
Pick a device from a list, then walk around — the beeping speeds up as you get closer, like a metal detector.
Offline. No account, no cloud, no companion app on the target device. Runs on macOS, Windows and Linux.
TRACKING Pixel 8
A1B2C3D4-5E6F-7890-ABCD-EF1234567890
███████████████████████████████░░░░░░░░░░░░░░░░░░ 62%
▲ WARMER — keep going that way
signal -58.3 dBm distance ~1.8 m update live sound on
history ▁▂▃▃▅▅▆▇
[c] calibrate at 1m [m] mute [q] quit
Read this before you rely on it.
It plays hotter/colder. It does not point. Bluetooth gives you signal strength, not direction. You find things by sweeping the area and following the pitch — not by following an arrow. There is no map and no bearing, and any tool claiming otherwise from a single radio is guessing.
Range is roughly 10–30 m, and walls, furniture and your own body cut it hard. Holding your laptop between yourself and the device measurably weakens the signal. Good for a house, a flat, an office floor. Marginal in a large shop.
The device has to be awake and advertising. A phone asleep in a drawer may
broadcast nothing your computer can see, and a phone with a dead battery
broadcasts nothing at all. This is precisely why Apple's Find My and Google's
Find My Device use a mesh of other people's phones instead of your laptop — if
your phone is genuinely lost outside your home, use those. btrack is for
"it's in this building somewhere."
Track only devices you own or have permission to look for. Following someone else's device around is stalking, and in many places a crime.
The "warmer/colder" call is deliberately conservative: it stays quiet unless the movement is statistically distinguishable from noise, because a detector that says "warmer" while you stand still is worse than one that says nothing.
Walking at ~1 m/s changes the signal at a rate that depends on how far away you already are:
| Distance | Signal change while walking |
|---|---|
| 2 m | ~7.5 dB/s |
| 3 m | ~4.4 dB/s |
| 5 m | ~2.4 dB/s |
| 8 m | ~1.5 dB/s |
| 12 m | ~0.9 dB/s |
Which the detector picks up like this (simulated, 15 runs each). "False trend" is how often it claims movement while the device is stationary:
| Noise | False trend | 1.5 dB/s | 2.5 dB/s | 4 dB/s | 6 dB/s |
|---|---|---|---|---|---|
| 3 dB (quiet room) | 3% | 82% | 100% | 100% | 100% |
| 5 dB (typical) | 7% | 32% | 84% | 100% | 100% |
| 8 dB (cluttered) | 7% | 6% | 36% | 84% | 99% |
In practice: within about 5 m it tracks you reliably. Beyond ~10 m a slow approach is genuinely below the noise floor — walk decisively, or watch the percentage and history sparkline rather than waiting for a "warmer".
Python 3.9 or newer.
git clone https://github.com/tal7aouy/btrack.git
cd btrack
python3 -m venv .venv && source .venv/bin/activate
pip install -r requirements.txtOr install it as a command:
pip install -e .On Linux you also need PortAudio for sound (sudo apt install libportaudio2).
Without it everything still works, just silently.
python3 -m btrack # scan, pick from the list, track
btrack # same, if you installed with pip install -e .Scan, choose a number, then walk. Press q to stop.
Nearby Bluetooth LE devices
# RSSI SEEN NAME / ADDRESS
1 -47 dBm 31 Pixel 8 A1B2C3D4-5E6F-...
2 -63 dBm 12 Galaxy Buds 7788AABB-CCDD-...
3 -88 dBm 3 (unnamed) 11223344-5566-...
Number to track, [r] rescan, [q] quit:
Calibrate for a much better distance readout. Hold your computer about a
metre from the device and press c. That pins the top of the scale to this
particular device's transmit power, which varies a lot between models.
| Flag | Does |
|---|---|
-a, --address ADDR |
skip the picker, track this address directly |
-t, --scan-time N |
seconds to scan (default 6) |
--named-only |
hide devices that advertise no name |
--min-rssi N |
hide devices weaker than N dBm |
--no-audio |
silent, meter only |
--volume 0.0-1.0 |
beep volume (default 0.35) |
--tx-power N |
reference RSSI at 1 m (default -59; c sets it live) |
--path-loss N |
2.0 open space, 2.5 default, 3.0+ cluttered |
--smoothing N |
EMA weight; lower is steadier but slower (default 0.2) |
--lost-after N |
seconds of silence before reporting a lost signal |
--no-color |
plain output |
--log PATH |
append every tick to PATH for later replay/plotting |
--log-format csv|json |
session log format (default: csv; json is one object per line) |
| Sound | Meaning |
|---|---|
| Slow low beeps | far away |
| Faster, higher beeps | closing in |
| Two-tone rising chirp | you moved closer — keep going |
| Two-tone falling chirp | you moved further away — turn back |
| Rapid triple chirp | within arm's reach |
| Sparse low tick | signal lost, sweep the area |
Phones rotate their Bluetooth address every ~15 minutes to stop exactly this kind of tracking. That does not break the tool, because you scan, pick and track inside one session — but it does mean you cannot save an address today and reuse it tomorrow. Always scan fresh.
- Android shows up well, usually with a recognisable name.
- iPhone advertises with a randomised address and typically no name, so it
appears as
(unnamed). Opening the Bluetooth settings screen on the phone makes it advertise much more visibly — useful if a friend can help — and pairing it with your computer first makes it far easier to pick out. - macOS hides real MAC addresses and gives each host its own UUID for the same device, so addresses are not portable between computers.
If the target is not in the list at all: it is asleep, out of range, or is a classic-Bluetooth-only device (see below).
On macOS this one message has two completely different causes, because a permission denial is reported as a powered-off radio. If Bluetooth is on and you still get this, it is permissions: find the Bluetooth entry in the system privacy settings and enable your terminal app there. (The exact path moves between macOS releases; search the system settings for "Bluetooth".)
Related: run btrack from a terminal you launched yourself. Started from
inside another tool — an IDE task runner, an agent, a daemon — macOS denies
Bluetooth silently and reports the radio as off. The permission prompt only
appears for a terminal you opened.
The target is asleep, out of range, or classic-Bluetooth only. Waking a phone and opening its Bluetooth settings screen makes it advertise far more visibly.
Some devices advertise very slowly — well under once per second. The lost-signal
threshold adapts to each device's observed rate, but if it still flickers, raise
the floor with --lost-after 8.
Normal — RSSI is noisy. Watch the sparkline and the trend arrow rather than the
raw number, and lower --smoothing (e.g. 0.1) for a steadier but slower read.
- Scan —
bleakperforms a BLE scan and reports each advertisement's RSSI. On macOS this needs a nudge: CoreBluetooth coalesces repeat advertisements from the same device unless the scan opts in, andbleakstarts its scan with no options, so a device is reported roughly once per scan. Fine for discovery, useless for tracking.scanner.pypatches the scan to setCBCentralManagerScanOptionAllowDuplicatesKey, which measurably raised the sample rate on real hardware. If the patch ever fails against a futurebleak, scanning still works — just with far fewer readings. - Smooth — raw RSSI swings several dB while perfectly still, so the displayed level is an exponential moving average.
- Trend — warmer/colder comes from a least-squares fit over the raw samples, not the smoothed ones. Regressing the EMA reports confident nonsense because its samples are autocorrelated by construction. A trend is announced only when the slope beats 2.5× its own standard error, so the detector tightens up in quiet conditions and goes conservative in noisy ones.
- Distance — the usual log-distance path loss model. Indicative only; treat it as a rough band, not a measurement.
- Sound — pulse rate and pitch encode proximity, pattern shape encodes trend, all generated as sine bursts at runtime.
- BLE only. Classic Bluetooth (BR/EDR) has no cross-platform Python story, so an old headset that only does classic and is not in pairing mode will not appear. Every modern phone advertises BLE, so this rarely matters in practice.
- One radio, no triangulation. Two receivers at known positions could actually locate a device rather than just rank distance. That is a much bigger project.
- The distance number is not survey-grade. A wall can shift it by a factor of two.
pip install -r requirements.txt pytest
python3 -m pytest tests/ -qThe test suite runs without Bluetooth hardware or a sound card: the trend detector is tested statistically against simulated noise, and the tracking loop is driven end-to-end by a fake radio with a virtual clock.
Issues and pull requests welcome at github.com/tal7aouy/btrack.
Please run the tests before opening a PR. If you change anything in rssi.py,
check the statistical tests still pass — the thresholds there are tuned, and a
change that makes the trend detector more eager will show up as a rising
false-positive rate rather than as a broken test elsewhere.