Approximating BLE Proximity with RSSI in .NET on iOS
I’ve continued working on my hobby project, a Bluetooth device emulator, and at some point I had what felt like a brilliant idea. I wanted to map out nearby Bluetooth devices from the receiving end and get a sense of where they were located.
That turns out not to be a straightforward problem. Bluetooth doesn’t tell you where something is. What you get is signal strength, and distance is just one of many factors that affect it.
Still, it felt like there had to be a way. I started playing around with this mostly for fun, knowing that I wouldn’t be able to produce a reliable distance estimate good enough for real-world use. There are simply too many variables that influence the signal.
But for the sake of curiosity, I wanted to see how far I could get.
RSSI
RSSI, Received Signal Strength Indicator, is a measure of how strong a received radio signal is. In BLE it is expressed in negative dBm values:
- closer to 0 means stronger signal
- more negative means weaker signal
So -50 is stronger than -80.
The important detail is that RSSI is not a distance. It is affected by distance, but also by a number of other factors that can easily dominate the reading.
What affects RSSI the most
Obstacles and materials
Walls, metal, and even people can significantly reduce signal strength. The human body is mostly water, which absorbs 2.4 GHz signals very effectively. Simply putting a phone in your pocket can change RSSI noticeably.
Indoor reflections and multipath
Indoors, signals bounce off surfaces and arrive via multiple paths. Some reinforce each other, others cancel out. This creates rapid fluctuations in RSSI even when nothing is moving.
Distance and transmit power
Distance still matters, and so does transmit power, but they are only part of the picture. Two identical distances can produce very different RSSI values depending on the environment.
The distance formula
There is a well-known formula to estimate distance:
distance ≈ 10 ^ ((TxPowerAt1m - RSSI) / (10 * n))
It works in theory, but in practice it depends on two values you rarely know precisely:
- RSSI at exactly 1 meter for your specific devices
- the environment factor
n, which changes depending on walls, furniture, and people
You can calibrate these values, but even then the estimate will drift as soon as conditions change.
The result is that distance calculations tend to look precise but behave inconsistently.
The math behind it
The formula is based on a simplified radio propagation model called the log-distance path loss model, which in turn is derived from the Friis transmission equation.
The key idea is that signal strength does not decrease linearly with distance. It decreases logarithmically as the signal spreads out.
In practice, engineers express signal strength in decibels, which turns the relationship into a linear equation:
RSSI = TxPowerAt1m - 10 * n * log10(d)
Where:
dis distanceTxPowerAt1mis the expected RSSI at 1 meternis the path-loss exponent
If you rearrange that equation to solve for distance, you get:
d = 10 ^ ((TxPowerAt1m - RSSI) / (10 * n))
That is the formula we started with.
What it is really doing is estimating how far away a device would be if the signal behaved in a smooth, predictable way. Indoors, that assumption rarely holds.
Proximity, not distance
Instead of asking “how many meters away is this device,” a more reliable question is: is it very close, nearby, or far away?
That maps much better to what RSSI can actually tell you.
A simple set of buckets might look like this:
>= -60→ immediate-60 to -75→ near-75 to -88→ far< -88→ unknown
These values are not universal. You will need to tune them based on your environment and hardware.
Working with Core Bluetooth in .NET
When scanning, you get RSSI from the discovery callback. When connected, you explicitly request updates.
public override void DiscoveredPeripheral(
CBCentralManager central,
CBPeripheral peripheral,
NSDictionary advertisementData,
NSNumber RSSI)
{
if (RSSI != null)
{
var value = RSSI.Int32Value;
// feed into estimator
}
}For connected devices:
peripheral.ReadRSSI();And handle the callback:
public override void RssiRead(CBPeripheral peripheral, NSNumber rssi, NSError error)
{
if (error != null || rssi == null)
return;
var value = rssi.Int32Value;
// feed into estimator
}Smoothing RSSI
Raw RSSI is too unstable to use directly. You need to smooth it.
A simple and effective approach is an exponential moving average:
_ema = alpha * rssi + (1 - alpha) * _ema;Where alpha is typically between 0.2 and 0.3.
You should also ignore obvious outliers. If a value suddenly jumps by 15 dB compared to your current average, it is often just noise.
Instead of switching proximity immediately, require a few consecutive readings before changing state. This stabilizes the experience significantly.
A practical estimator
Putting it together:
public enum BleProximity
{
Unknown,
Immediate,
Near,
Far
}
public sealed class BleRssiEstimator
{
private double? _ema;
private readonly double _alpha = 0.25;
private BleProximity _current = BleProximity.Unknown;
private BleProximity _candidate;
private int _candidateHits;
public void AddSample(int rssi)
{
if (rssi >= 0)
return;
if (_ema.HasValue && Math.Abs(rssi - _ema.Value) > 15)
return;
_ema = _ema.HasValue
? _alpha * rssi + (1 - _alpha) * _ema.Value
: rssi;
UpdateProximity();
}
public BleProximity Current => _current;
private void UpdateProximity()
{
if (!_ema.HasValue)
return;
var next = Classify(_ema.Value);
if (next == _current)
{
_candidateHits = 0;
return;
}
if (next != _candidate)
{
_candidate = next;
_candidateHits = 1;
return;
}
if (++_candidateHits >= 3)
{
_current = next;
_candidateHits = 0;
}
}
private static BleProximity Classify(double rssi)
{
if (rssi >= -60) return BleProximity.Immediate;
if (rssi >= -75) return BleProximity.Near;
if (rssi >= -88) return BleProximity.Far;
return BleProximity.Unknown;
}
}Calibration matters
If you want reasonable behavior, you need to calibrate:
- Place devices 1 meter apart
- Measure RSSI for 10–20 seconds
- Average the result
- Adjust your thresholds based on real-world testing
Even small changes in placement or environment can shift results noticeably, so calibration is not a one-time task.
RSSI works best when you:
- treat it as a relative signal, not a precise measurement
- smooth aggressively
- use proximity buckets instead of meters
Comments
Last modified on 2026-02-20
