15  Indoor Positioning with BLE Beacons

Practical BLE Development with ESP32

networking
wireless
bluetooth
ble
esp32
lab
Author

IoT Textbook

Published

January 19, 2026

Keywords

ble, esp32, arduino, wokwi, gatt, beacon, implementation, lab

15.1 Start With the Story

A beacon signal near a doorway is not yet a position. RSSI, calibration, anchor placement, filtering, movement, walls, and uncertainty decide whether a location claim is useful or misleading.

Read this chapter from the final decision backward. Ask what the system will do with the position, then check whether the beacon evidence is strong enough for that action.

Phoebe the physics guide

Phoebe’s Why

The trilateration geometry later in this chapter is exact – three circles genuinely meet at one point. What is not exact is the radius of each circle, because RSSI-to-distance conversion is a logarithmic relationship: distance sits in an exponent, not a coefficient. That means a fixed dB error does not add a fixed number of metres; it multiplies the distance estimate by a fixed ratio. A small, fast RSSI jitter (frame-to-frame noise) stays close to that ratio’s linear approximation, but a large swing from a wall, a body, or a reflection does not – the exponential curve pulls away from the straight-line approximation fast enough that using the small-signal formula on a big swing quietly understates the real error.

The Derivation

Log-distance path-loss model:

\[RSSI(d) = RSSI(d_0) - 10n\log_{10}\!\left(\frac{d}{d_0}\right)\]

Inverted to estimate distance from a measured RSSI:

\[\hat d = d_0 \cdot 10^{\frac{RSSI(d_0)-RSSI}{10n}}\]

Exact ratio for a measurement offset \(\delta = RSSI(d_0) - RSSI\) (positive \(\delta\) = weaker signal, larger estimate):

\[\frac{\hat d}{d_0} = 10^{\delta/(10n)}\]

Small-signal (linearized) fractional error, valid only while \(\delta\) stays small:

\[\frac{\Delta \hat d}{\hat d} \approx \frac{\ln 10}{10n}\,\delta\]

Worked Numbers: Recomputing This Chapter’s Own Claim

  • Checking the quiz’s own \(\pm8\) dBm claim (2 m reference, \(n=2\), a standard indoor exponent): a \(-8\) dB (weaker) offset gives \(\hat d = 2\times10^{8/20}=5.02\) m – matching the stated “5.0 m” upper bound closely. A \(+8\) dB (stronger) offset gives \(\hat d = 2\times10^{-8/20}=0.796\) m, not the stated “1.4m” lower bound. Reaching exactly 1.4 m from \(+8\) dB would need \(n\approx5.16\), a much steeper exponent than the one that fits the upper bound – worth a source check, though it does not change the lesson: even the “closer” side of the noise band still swings the estimate by close to a metre.
  • Why the linear formula cannot be trusted at 8 dB: the small-signal formula predicts a \(92.1\%\) increase at \(\delta=8\); the exact ratio gives \(151\%\). The two only agree for small \(\delta\) – another reason environmental RSSI swings from walls and bodies need the exact exponential form, not the derivative shortcut.
  • A regime where the linear form is fair – fast RSSI jitter: at \(n=2\), each dB of measurement noise moves the estimate by \(\ln(10)/20=11.5\%\). Applied to this chapter’s own trilateration distances (1.78 m, 4.47 m, 2.82 m) with a modest \(\pm1\) dB frame-to-frame jitter: \(\pm0.205\) m, \(\pm0.515\) m, and \(\pm0.325\) m respectively – the same 11.5% fraction of each anchor’s own range, which is why the farthest beacon contributes the largest absolute wobble to the least-squares fit even when every beacon has identically noisy RSSI.

15.2 From Beacons to a Position

In Building BLE Apps on ESP32 you built a scanner, a GATT temperature server, and an iBeacon transmitter. This chapter takes those beacons and answers a bigger question: can a device work out where it is indoors, where GPS cannot reach? The answer is yes — with three beacons, their signal strengths, and some honest math about how approximate radio distance really is.

15.3 Build the Positioning System, Step by Step

Three BLE beacons with overlapping range circles and an estimated position at the intersection, with accuracy 2-5 meters and minimum three beacons
Three beacons, three distance estimates, one intersection: trilateration turns RSSI into a position — honest accuracy is 2–5 metres.

A single beacon can say “near” or “far.” Three beacons let you estimate a position. That follows naturally from the previous step: convert each beacon RSSI to an approximate distance, draw a circle around each fixed beacon, and find the point where the circles overlap most closely.

System element Role in the positioning lab What the code uses
Beacon 1 Fixed anchor at (0, 0) RSSI converted to distance d1
Beacon 2 Fixed anchor at (5, 0) RSSI converted to distance d2
Beacon 3 Fixed anchor at (0, 5) RSSI converted to distance d3
Mobile device Unknown point being estimated Best-fit (x, y) position
Residual error Quality check for the estimate Larger error means noisy RSSI or a poor path-loss model

Think of each distance as drawing a circle around a beacon. The estimated position is where the three circles overlap most closely.

import numpy as np
from scipy.optimize import least_squares

def trilaterate(beacons, distances):
    """
    Calculate position from beacon positions and distances.

    Args:
        beacons: List of (x, y) beacon positions
        distances: List of distances to each beacon

    Returns:
        (x, y) estimated position
    """
    def residuals(point, beacons, distances):
        return [
            np.sqrt((point[0] - b[0])**2 + (point[1] - b[1])**2) - d
            for b, d in zip(beacons, distances)
        ]

    # Initial guess: centroid of beacons
    x0 = np.mean([b[0] for b in beacons])
    y0 = np.mean([b[1] for b in beacons])

    result = least_squares(
        residuals,
        [x0, y0],
        args=(beacons, distances)
    )

    return tuple(result.x)

# Example usage
beacons = [(0, 0), (5, 0), (0, 5)]
distances = [1.78, 4.47, 2.82]  # From RSSI
position = trilaterate(beacons, distances)
print(f"Estimated position: {position}")
Try It: BLE Trilateration Simulator

Place 3 beacons and adjust the measured distance from each to see where trilateration estimates your position. The circles represent the distance measurement from each beacon — the estimated position is where they intersect.

Checkpoint: you should now be able to explain why three noisy distances produce a best-fit position rather than a guaranteed exact location. The residual error tells you whether the three circles agree.

Check: RSSI Distance Estimation

Check: Why Three Beacons Still Miss