Android counts an app's scan *starts*: five in any thirty seconds and the
platform answers `SCAN_FAILED_SCANNING_TOO_FREQUENTLY` and stops
returning results. It does this silently as far as btleplug is concerned,
so the app goes on asking and simply stops being told about anything.
`scan_loop` opened a session every 2.9 s — a 2.5 s window plus 400 ms
idle. **Ten starts per thirty seconds, twice the limit, with nothing else
running.** Add the trainer's 15 s search or a pod's 20 s one, which
`click.rs` notes runs to the full timeout because a Click only advertises
after a button press, and the app spends much of its life muted. A rider
who wakes the pods first is doing exactly the thing that pushes the count
over, and then the trainer cannot be found — not because it is not
advertising, but because the app is no longer allowed to hear it.
Starting a scan is the expensive act, not running one, so hold the
session and sample it:
- scan.rs splits `scan` into `begin` / `peek` / `end`, sharing one
describe-filter-rank path (`collect`). `scan` stays as the one-shot
form for the probe tool.
- `scan_loop` opens one session per SCAN_WINDOW (now 20 s) and peeks
every 700 ms, publishing each time. The radio starts a seventh as
often and the list updates four times *quicker* than the old
whole-pass cadence.
- The session is recycled rather than held forever: a new adapter each
cycle is what drops peripherals that have left the room, so 20 s is
how stale a departed device may look. That was ~3 s before, and it is
the one thing this trade gives up.
- The sample loop selects on the scan switch, so a suspension still
lands immediately. A connect suspends this loop precisely so the two
do not fight over the radio, and a suspension that took twenty seconds
to arrive would be no suspension at all.
Instrumented at the choke point every caller passes through, because
"the radio is busy" and "the peripheral is asleep" look identical from
outside: every session start logs the concurrent depth and how many
starts there have been in the last thirty seconds, and warns when either
number is a problem. Measured on the tablet after this change — one
start per ~21 s, `recent=2`, against a limit of five.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>