A Bluetooth beacon is dumb by design: it wakes, transmits a small payload, and goes back to sleep. The position of a receiver is computed elsewhere, by software that turns radio measurements into coordinates. This article walks through the three layers that actually matter — converting signal strength to distance, solving for position, and smoothing the result — with the numbers an engineer needs to set expectations.
RSSI to distance: the log-distance model
The most common input is Received Signal Strength Indication (RSSI). Under the log-distance path-loss model, power falls off with roughly the square of distance, modulated by an environment factor:
RSSI(d) = RSSI0 - 10 * n * log10(d / d0) + X_sigma
- `RSSI0` — measured power at reference distance `d0` (typically 1 m)
- `n` — path-loss exponent (2.0 free space, 2.5–4.0 indoor)
- `X_sigma` — Gaussian shadow fading, std dev 3–8 dB indoor
Invert it to estimate distance:
d = d0 * 10^((RSSI0 - RSSI) / (10 * n))
Worked example: RSSI0 = -59 dBm, n = 2.0, measured RSSI = -75 dBm, d0 = 1 m:
d = 10^(( -59 - (-75) ) / 20) = 10^(16/20) = 10^0.8 ≈ 3.98 m
The catch is X_sigma. A ±6 dB swing at n = 3 moves the estimate by a factor of 10^(6/30) ≈ 1.58. So a single RSSI sample is only good to ~±50% distance. That is why raw distance is never the final answer.
Trilateration: solve for the intersection
With three or more Beacon anchors at known coordinates, trilaterate the receiver. Each anchor gives a noisy radius; least-squares finds the point minimizing squared residuals:
<h1>anchors: list of (x_i, y_i, r_i)</h1>
<h1>minimize sum_i ( sqrt((x-x_i)^2 + (y-y_i)^2) - r_i )^2</h1>
<h1>solve with Gauss-Newton / Levenberg-Marquardt</h1>
def trilaterate(anchors):
p = centroid(anchors) # init guess
for _ in range(20):
J, f = jacobian_residual(p, anchors)
dp = solve(J.T @ J + lam*I, J.T @ f) # LM step
p = p - dp
if norm(dp) < 1e-3: break
return p
Geometry matters: Dilution of Precision (GDOP) explodes when anchors are collinear or the receiver sits outside the anchor hull. Keep anchors spread in 2D (ideally 3+) and cover the area from multiple sides. A 2.5–3.5 m mounting height with anchors on different walls typically yields 1–3 m error in open indoor space.
Fingerprinting: skip the physics
Instead of modeling propagation, fingerprinting learns the space. Offline phase: walk a grid, record the RSSI vector at each reference point (RP). Online phase: match the live vector to the closest RP:
<h1>kNN / Weighted-kNN</h1>
def locate(live_vec, rp_db):
dists = [euclidean(live_vec, rp.vec) for rp in rp_db]
k_nearest = argsort(dists)[:k]
w = 1 / (dists[k_nearest] + eps)
x = sum(w * rp_db[i].x for i in k_nearest) / sum(w)
y = sum(w * rp_db[i].y for i in k_nearest) / sum(w)
return x, y
Fingerprinting is robust to multipath and non-line-of-sight because it bakes those effects into the survey. Cost: the offline survey is laborious (~1 RP/m² for good resolution) and must be re-done when the layout changes. Typical error: 0.5–2 m.
Smoothing: kill the jitter
A raw track jumps frame to frame. Apply a 1-D Kalman filter per axis, or a particle filter when you have motion constraints:
<h1>1-D constant-velocity Kalman, per coordinate</h1>
x_hat = A @ x; P = A @ P @ A.T + Q
K = P @ H.T @ inv(H @ P @ H.T + R)
x = x_hat + K @ (z - H @ x_hat); P = (I - K @ H) @ P
<h1>R: measurement noise (~ from RSSI std); Q: process noise (~ from walking speed)</h1>
Tune R high (trust model) when RSSI is noisy; lower Q for slow movers. For portable tags a particle filter that rejects impossible moves (through walls) beats naive averaging.
Method comparison
| Method | Input | Typical error | Survey cost | Best for |
|---|---|---|---|---|
| Trilateration | 3+ anchors, RSSI→dist | 1–3 m | Low | Stable layouts, few anchors |
| Fingerprinting | RSSI vectors | 0.5–2 m | High | Multipath, NLOS, fine grids |
| Fusion (RSSI + IMU) | RSSI + accelerometer | 0.3–1 m | Medium | Wearables, dead-reckoning |
Practical deployment rules
- Calibrate `RSSI0` and `n` per site; do not trust factory values.
- Use 4+ anchors per zone, mounted 2.5–3.5 m, on different walls.
- Report distance uncertainty (std dev), not just a point — downstream logic should gate on it.
- Smooth before you decide; a 3-sample median filter catches RF spikes cheaply.
- Combine with the scheduling and RF-planning steps from earlier articles: anchor density and gateway placement set the ceiling on accuracy.
A Beacon system’s value is not the broadcast — it is the solver behind it. Get the model, the geometry, and the filter right, and you turn noisy radio into a track a warehouse or hospital can act on.