RSSI and distance
Each BLE beacon sends out a signal which is picked up by fixed points and then the strength of the signal measured, which gives the Received Signal Strength Indicator (RSSI), measured in dBm. Higher strength means higher proximity. According to the Log-Distance Path Loss Equation:
d = 10 ^ ((TxPower − RSSI) / (10 × n))
Where:
d = Distance in metres
TxPower = RSSI at 1 m (usually calibrated, around −59 to −65 dBm)
RSSI = current RSSI in dBm
n = path loss exponent (typically 2.0 for free space, 2.5 to 4.0 indoors)
function rssiToDistance(rssi, txPower = -59, n = 2.5) {
if (rssi === 0) return -1
const ratio = (txPower - rssi) / (10 * n)
return Math.pow(10, ratio)
}
rssiToDistance(-59) // → ~1.0m
rssiToDistance(-70) // → ~3.5m
rssiToDistance(-85) // → ~14m
⚠️ RSSI is noisy. A single reading varies ±10 dBm from interference. Never feed raw RSSI into trilateration — smooth first with a Kalman filter.
class KalmanFilter {
constructor(R = 0.125, Q = 0.5) {
this.R = R; this.Q = Q
this.x = null; this.P = 1
}
filter(z) {
if (this.x === null) { this.x = z; return z }
const K = this.P / (this.P + this.R)
this.x = this.x + K * (z - this.x)
this.P = (1 - K) * this.P + this.Q
return this.x
}
}
// One filter per anchor per tag
const filter = new KalmanFilter()
const distance = rssiToDistance(filter.filter(rawRssi))
Trilateration — the math
With distances from 3+ anchors at known positions, solve:
(x − x₁)² + (y − y₁)² = d₁²
(x − x₂)² + (y − y₂)² = d₂²
(x − x₃)² + (y − y₃)² = d₃²
Subtract equation 1 from equations 2 and 3 → eliminates x² and y² → two linear equations → solve for x and y.
Full implementation
const anchors = [
{ id: 'A1', x: 0, y: 0 },
{ id: 'A2', x: 10, y: 0 },
{ id: 'A3', x: 5, y: 8 },
{ id: 'A4', x: 0, y: 8 },
]
function trilaterate(anchors, distances) {
const ref = anchors[0]
const A = [], b = []
for (let i = 1; i < anchors.length; i++) {
const a = anchors[i]
A.push([2*(a.x-ref.x), 2*(a.y-ref.y)])
b.push(
distances[i]**2 - distances[0]**2
- a.x**2 + ref.x**2
- a.y**2 + ref.y**2
)
}
const [x, y] = leastSquares(A, b)
return { x: Math.round(x*100)/100, y: Math.round(y*100)/100 }
}
function leastSquares(A, b) {
let AtA = [[0,0],[0,0]], Atb = [0,0]
for (let i = 0; i < A.length; i++) {
const [a0,a1] = A[i]
AtA[0][0]+=a0*a0; AtA[0][1]+=a0*a1
AtA[1][0]+=a1*a0; AtA[1][1]+=a1*a1
Atb[0]+=a0*b[i]; Atb[1]+=a1*b[i]
}
const det = AtA[0][0]*AtA[1][1] - AtA[0][1]*AtA[1][0]
return [
(AtA[1][1]*Atb[0] - AtA[0][1]*Atb[1]) / det,
(AtA[0][0]*Atb[1] - AtA[1][0]*Atb[0]) / det
]
}
Real World Accuracy
| Environment | Path Loss n | Accuracy | Notes |
|---|---|---|---|
| Open Office | 2.0 - 2.5 | 1 – 2m | Best conditions |
| Warehouse | 2.5 – 3.0 | 2 – 4m | Metal shelving multipath |
| Hospital | 3.0 – 3.5 | 2 – 5m | Lots of dense walls, equipment |
| Manufacturing | 3.0 – 4.0 | 3 – 6m | Heavier machinery, RF-dense |
Tip for Calibration: calibrate txPower individually at each anchor point in your environment. Stand 1m away, take 100 RSSI samples. Calibration increases accuracy by 30-40% over manufacture default values.
What to Build on Top
Place the trilaterated coordinate system onto a SVG floor plan – correlate the meters from the real world to pixels using scale factor. Asset pin locations are updated based on incoming RSSI coordinates received via Server-Sent Events or WebSocket.
AssetTrackPro's BLE indoor positioning technology is based on calibrated RSSI trilateration + Kalman Filtering, delivering accurate positions
Explore AssetTrackPro ↗
Top comments (0)