Iris Classon
Iris Classon - In Love with Code

Why BLE RSSI Spikes on Android

Working on the BLE emulator, I kept noticing that RSSI readings on Android were jumpier than on iOS. Not just a little — noticeably more erratic, even when nothing was moving and the environment hadn’t changed.

It took me a while to understand why. The answer is scan modes and duty cycling.

Scan modes

When you start a BLE scan on Android you can choose how aggressively the radio listens:

  • SCAN_MODE_LOW_POWER — short bursts with long gaps. This is the default.
  • SCAN_MODE_BALANCED — moderate frequency
  • SCAN_MODE_LOW_LATENCY — as close to continuous as you can get

The problem with anything other than LOW_LATENCY is that the scanner goes quiet between windows. BLE devices advertise at their own intervals — often 100 to 1000 ms — and Android simply misses packets while the scan window is closed. When scanning resumes, you get whatever advertisement happens to arrive next. Because you missed the intermediate packets, that value can differ significantly from the last one you saw, and that shows up as a spike.

You set it like this:

var settings = new ScanSettings.Builder()
    .SetScanMode(ScanMode.LowLatency)
    .Build();

Duty cycling

Even with LOW_LATENCY, Android may still apply internal duty cycling depending on device and OS version. You don’t control this, and missed packets are simply lost — they are not queued.

What you do see is irregular timing between callbacks. Samples arrive unevenly, and because RSSI varies naturally due to multipath, antenna orientation, and nearby obstacles, uneven sampling makes the signal look more erratic than it actually is.

RSSI is inherently noisy even under ideal conditions — scan behavior amplifies that noise rather than causing it. iOS is smoother here. CoreBluetooth handles scan scheduling internally and tends to produce more consistent readings. Android gives you more control, but also more noise.

Why it matters

If you read my previous post on RSSI proximity estimation, you might recognize why the outlier rejection and the consecutive-hits guard were necessary. I didn’t write them because I had read a spec. I wrote them because raw Android readings were doing strange things and those two additions made behavior noticeably more stable.

Duty cycling is a large part of why.

The guards are not a workaround for bad code. They are a response to how the Android Bluetooth stack actually behaves.

If you need better readings

A few things that help:

  • Use SCAN_MODE_LOW_LATENCY if battery is not a concern
  • Increase your smoothing alpha slightly on Android compared to iOS
  • Be more aggressive with outlier rejection — a 15 dBm threshold is reasonable, but you may want to go lower

For my emulator it was enough to just know that the jumpiness was expected, and not a sign that something else was wrong.

Comments

Leave a comment below, or by email.

Last modified on 2026-02-27

comments powered by Disqus