|
1 | 1 | --- |
2 | 2 | title: Calibration |
| 3 | +description: How to calibrate ESPresense nodes — RSSI@1m, Absorption, and Rx Adj RSSI — with failure-mode triage from real Discussions threads. |
3 | 4 | sidebar: |
4 | 5 | order: 1 |
5 | 6 | --- |
6 | 7 |
|
| 8 | +*How-to · Last verified against firmware [v4.1.0b0](https://github.com/ESPresense/ESPresense/releases/tag/v4.1.0b0) on 2026-05-10.* |
7 | 9 |
|
8 | | -<img src="/images/calibration_screen.png" alt="Calibration section of the ESPresense settings UI" style="float:right;margin-left:20px;width:360px" /> |
| 10 | +<img src="/images/calibration_screen.png" alt="Calibration section of the ESPresense settings UI showing RSSI expected from a 0 dBm transmitter at 1 meter, Factor, and RSSI adjustment for receiver fields." style="float:right;margin-left:20px;width:360px" /> |
9 | 11 |
|
10 | | -Calibrating your ESPresense nodes helps each device estimate distance consistently, even when they use different antennas or are installed in different rooms. These settings are accessible from the Settings view in the device web UI. |
| 12 | +If distances feel wrong, nearest-node decisions flip, or your tracked devices read 3 m away when they're sitting next to a node — this is the page. Start with the triage table, then run the procedure on the matching setting. |
11 | 13 |
|
12 | | -## Quick calibration procedure |
| 14 | +## 30-second triage |
13 | 15 |
|
14 | | -1. Pick a **reference node** that will stay at its default **RSSI adjustment for receiver** of `0` and set it up in an open area. |
15 | | -2. Place a beacon that transmits **exactly 0 dBm** one meter away and record the average RSSI reported by the reference node. |
16 | | -3. Enter that value in **RSSI expected from a 0dBm transmitter at 1 meter** on every node that will track non-calibrated devices. |
17 | | -4. For each additional node, compare its reading of the same beacon at the same location and adjust **RSSI adjustment for receiver** as described below until its reported RSSI matches the reference node. |
18 | | -5. If the signal passes through walls or dense materials between rooms, increase the **Factor** slightly (for example, from `2.55` to `3.0`) and test whether calculated distances align better with reality. |
19 | | -6. Repeat the measurements in a second location to confirm the offsets before rolling the settings out across your deployment. |
| 16 | +| Symptom | Setting that usually fixes it | |
| 17 | +|---|---| |
| 18 | +| Same beacon at the same spot reads very different distances on different nodes | **Rx Adj RSSI** (per-node offset) | |
| 19 | +| All nodes agree, but distances are wrong at the far end of the house | **Absorption Factor** (walls/attenuation) | |
| 20 | +| Distance is wrong at 1 m — "I'm right next to it and it says 3 m" | **RSSI@1m** for that beacon type, or fix the beacon's broadcast power | |
| 21 | +| Distances jitter, occasionally jump 5–10 m | Antenna placement, metal enclosure, or 2.4 GHz interference — **not a calibration setting** | |
| 22 | +| Floorplan position drifts away from where the device actually is | Companion problem, not firmware. See [Floorplan drift is a different problem](#floorplan-drift-is-a-different-problem) below. | |
| 23 | +| Apple Watch / Apple device reads way further than reality | Different calibration path entirely — iBeacons broadcast their own `rssi@1m`, and resolved Apple identities use IRK enrollment. See the [Apple guide](/apple). | |
20 | 24 |
|
21 | | -## RSSI expected from a 0dBm transmitter at 1 meter |
| 25 | +Source: canonical pin [#2323 "Calibration — start here"](https://github.com/ESPresense/ESPresense/discussions/2323), maintained by the community. |
22 | 26 |
|
23 | | -This value establishes the reference point for devices that do not broadcast a calibrated `rssi@1m` (for example, many non-beacon wearables). To determine it: |
| 27 | +## The procedure |
24 | 28 |
|
25 | | -1. Use a beacon transmitting at exactly 0 dBm. |
26 | | -2. Place the beacon 1 meter away from the ESP32. |
27 | | -3. Observe the RSSI reading on the Settings page and record the average value. |
28 | | -4. Enter that number as the reference. |
| 29 | +You need: a beacon transmitting at **exactly 0 dBm** (a known-good iBeacon or Eddystone tag with calibrated tx power), a tape measure, and access to the **Settings** page of each node's web UI. |
29 | 30 |
|
30 | | -Devices that already advertise `rssi@1m` keep their own calibration; others use this reference plus any additional adjustments below. |
| 31 | +1. **Pick one reference node** in an open area, away from walls and metal. Leave its **Rx Adj RSSI** at `0`. Every other node gets calibrated against this one. |
| 32 | +2. **Place the beacon exactly 1 m from the reference node.** Watch the average RSSI for that beacon on the reference node's Settings page. Record it. |
| 33 | +3. **Enter that value** as **RSSI expected from a 0 dBm transmitter at 1 m** on **every node**. This is the global reference, not per-node. |
| 34 | +4. **For each other node**, put the same beacon at the same distance and compare to the reference node's reading. If it reads stronger (less negative), set a **negative** Rx Adj RSSI; if weaker, set a **positive** value. Adjust until both nodes report approximately the same RSSI for the beacon. |
| 35 | +5. **Move the beacon several meters through the kind of walls you actually have.** Adjust **Absorption Factor** (typical range `2.5`–`3.5`) until the reported *distance* matches reality. Higher = more attenuation assumed = larger reported distance for the same RSSI. |
| 36 | +6. **Verify at a second location.** Don't tune to one spot — it will overfit. |
31 | 37 |
|
32 | | -## Factor used to account for absorption, reflection, or diffraction |
| 38 | +Same procedure works for a single-node setup, except you skip step 4. |
33 | 39 |
|
34 | | -This factor adjusts for walls, furniture, or other environmental obstacles between rooms. Higher numbers assume more attenuation; lower numbers assume minimal obstruction. Increase the factor if reported distances are too small when signals pass through dense materials (brick, concrete, metal framing). |
| 40 | +:::tip[Calibrate at ≥ 2 m] |
| 41 | +Closer than that, near-field effects make the numbers lie. The 1 m measurement in step 2 is for the global reference only — use it to set RSSI@1m, then do all comparison measurements (steps 4–5) at 2 m or more. |
| 42 | +::: |
35 | 43 |
|
36 | | -## RSSI adjustment for receiver |
| 44 | +### Setting the values from MQTT |
37 | 45 |
|
38 | | -Different ESP32 boards and external antennas can report RSSI differently, even with identical firmware settings. The **RSSI adjustment for receiver** value is an offset (in dB) applied to *all* RSSI readings from a node so that mixed hardware stays aligned. |
| 46 | +If you prefer MQTT to the web UI, every calibration setting accepts a `…/set` topic. Replace `*` with the room/node name or use `*` to broadcast to all. |
39 | 47 |
|
40 | | -Typical use cases include: |
| 48 | +```text |
| 49 | +espresense/rooms/livingroom/ref_rssi/set -65 # RSSI@1m (int, dB) |
| 50 | +espresense/rooms/livingroom/rx_adj_rssi/set -3 # per-node offset (int, dB) |
| 51 | +espresense/rooms/livingroom/absorption/set 2.8 # absorption factor (float, 1.0–5.0) |
| 52 | +espresense/rooms/livingroom/forget_ms/set 300000 # forget unseen devices after (ms) |
| 53 | +``` |
41 | 54 |
|
42 | | -* Balancing a node that uses a high-gain external antenna against nodes with built-in antennas. |
43 | | -* Normalizing readings between dev boards that ship with slightly different radio front-end tuning. |
| 55 | +The settings are retained on the device and survive reboot. Values published unretained on `espresense/rooms/+/<key>` (no `/set`) are status only — don't write there. |
44 | 56 |
|
45 | | -To tune the RSSI adjustment for receiver: |
| 57 | +## The settings, in detail |
46 | 58 |
|
47 | | -1. Pick a reference node (leave its **RSSI adjustment for receiver** at 0) and place a known beacon at a fixed distance from both the reference and the node you want to adjust. |
48 | | -2. Compare their reported RSSI values for the same beacon. |
49 | | -3. On the node that reads stronger, set a **negative** value equal to the difference; on a weaker-reading node, set a **positive** value until both nodes report approximately the same RSSI. |
50 | | -4. Repeat with a second location to verify consistency, then apply the same offset to nodes that share the same antenna or board type. |
| 59 | +### RSSI expected from a 0 dBm transmitter at 1 m |
51 | 60 |
|
52 | | -Because the RSSI adjustment for receiver is an additive offset, it does not change the relative movement of a device within a single room, but it keeps multi-room trilateration and occupancy decisions fair between nodes with different hardware. |
| 61 | +The reference point for devices that don't broadcast their own `rssi@1m`. Set this once, globally — every node uses the same value. |
53 | 62 |
|
54 | | -## Forget beacon if not seen for (in milliseconds) |
| 63 | +- Used for: most wearables, generic BLE devices, Android tags, anything advertising without a calibrated power value. |
| 64 | +- **Not** used for: iBeacons and Eddystone beacons. Those broadcast their own `rssi@1m` and the node uses that. If a calibrated iBeacon reads wrong at 1 m, fix the beacon's broadcast power, not this setting. |
| 65 | +- Firmware constant: `EDDYSTONE_ADD_1M = -41`. Eddystone broadcasts its calibrated power at **0 m**, not 1 m — the firmware subtracts 41 dB internally. If you're hand-comparing Eddystone numbers, add 41. |
| 66 | +- MQTT key: `ref_rssi` (yes, just `ref_rssi`, not `rx_ref_rssi`). |
55 | 67 |
|
56 | | -Controls how long a MAC address stays in the internal tracking list. Shorter durations remove stale devices sooner but may cause the same device to be re-added frequently if it only broadcasts occasionally. The default is 300000 ms (5 minutes). |
| 68 | +### Factor used to account for absorption, reflection, or diffraction |
57 | 69 |
|
58 | | -## Calibration tips |
| 70 | +The path-loss exponent. Higher numbers assume more attenuation, which produces a longer reported distance for the same RSSI. Range is `1`–`5`, typical is `2.5`–`3.5`. |
59 | 71 |
|
60 | | -* Re-run the reference measurement if you change antennas or enclosure materials. |
61 | | -* Perform RSSI adjustment for receiver comparisons with beacons at least a few meters away to avoid near-field effects. |
62 | | -* When installing multiple nodes in one room, calibrate them together to avoid uneven presence detection at room boundaries. |
| 72 | +- Free air: ~2.0. |
| 73 | +- Drywall, residential interior: 2.5–3.0. |
| 74 | +- Brick, dense furniture, multi-room: 3.0–3.5. |
| 75 | +- 1 m thick stone (yes, a real user case from [#1817](https://github.com/ESPresense/ESPresense/discussions/1817)): higher; consider adding more nodes instead. |
| 76 | +- MQTT key: `absorption`. |
63 | 77 |
|
64 | | -## Troubleshooting |
| 78 | +### RSSI adjustment for receiver |
65 | 79 |
|
66 | | -### RSSI seems incorrect |
| 80 | +An additive offset (in dB) applied to *every* RSSI reading from this specific node. Different ESP32 boards, antennas, and even USB cables can shift readings by several dB; this normalizes them so trilateration is fair across mixed hardware. |
67 | 81 |
|
68 | | -Note that `rssi@1m` is broadcast in the beacons from `ibeacon` and `eddy` devices. If those are not correct, you need to fix the beacon to send the correct value. |
| 82 | +- Reference node: leave at `0`. |
| 83 | +- Stronger-reading node (RSSI is less negative than reference): set **negative**. |
| 84 | +- Weaker-reading node: set **positive**. |
| 85 | +- Because it's additive, it doesn't change relative motion *within* a single room — but it keeps multi-room decisions consistent. |
| 86 | +- MQTT key: `rx_adj_rssi`. |
69 | 87 |
|
70 | | -For all other devices, `rssi@1m` is the **RSS expected from a 0dBm transmitter at 1 meter** setting plus an offset to correct for broadcast power. See [rssi.h](https://github.com/ESPresense/ESPresense/blob/main/lib/BleFingerprint/rssi.h) for the exact values used or to confirm how offsets are applied. |
| 88 | +### Forget beacon if not seen for (in milliseconds) |
| 89 | + |
| 90 | +How long a MAC address stays in the internal tracking list after the last advertisement. Default: `300000` (5 minutes). Shorter values remove stale devices faster but cause re-add churn for devices that broadcast intermittently. MQTT key: `forget_ms`. |
| 91 | + |
| 92 | +## Failure modes (and where they came from) |
| 93 | + |
| 94 | +These are the most-asked calibration problems in [Discussions](https://github.com/ESPresense/ESPresense/discussions). When you hit one of them, you are not alone. |
| 95 | + |
| 96 | +### "I can walk across the entire house and the distance only goes from 1.0 to 2.5" |
| 97 | + |
| 98 | +User [@Zipties](https://github.com/Zipties) in [#213](https://github.com/ESPresense/ESPresense/discussions/213). Cause: RSSI@1m was set far too pessimistic (too low / too negative) for the device. With a too-low RSSI@1m, every distance gets compressed — the math collapses. |
| 99 | + |
| 100 | +**Fix:** redo step 2 of the procedure. Measure the actual average RSSI at 1 m for the device type you're tracking. If you're tracking an iPhone, calibrate using an iPhone, not a beacon — Apple devices have their own typical tx power that the firmware compensates for via `APPLE_TX = 0`, but the global RSSI@1m still has to be in the right ballpark. |
| 101 | + |
| 102 | +### "Mi7 band sometimes says NOT HOME when I'm in my office" |
| 103 | + |
| 104 | +User [@scargill](https://github.com/scargill) in [#1687](https://github.com/ESPresense/ESPresense/discussions/1687). Cause: small / low-power BLE wearables advertise infrequently and weakly. Cranking RSSI@1m down to "see further" is the wrong knob — it just makes other distances inaccurate. |
| 105 | + |
| 106 | +**Fix:** |
| 107 | + |
| 108 | +- Add a node closer to the office, don't push existing nodes harder. |
| 109 | +- Use **Rx Adj RSSI** to compensate if a specific node has a known-weak antenna. |
| 110 | +- Increase **Forget beacon if not seen for** so an unseen-but-recent device isn't dropped between advertisements. |
| 111 | +- Tune the "away" detection window in Home Assistant, not in firmware — that's where the flap actually shows up. |
| 112 | + |
| 113 | +### "Distances seem fine but the wrong room flips on and off" |
| 114 | + |
| 115 | +User [@plackettsj1](https://github.com/plackettsj1) in [#1817](https://github.com/ESPresense/ESPresense/discussions/1817). This one fooled people because the per-axis coordinates were stable to centimeters and *still* the room flipped. Cause: Companion-side aggregation/room-boundary logic, not firmware calibration. |
| 116 | + |
| 117 | +See [Floorplan drift is a different problem](#floorplan-drift-is-a-different-problem) below. |
| 118 | + |
| 119 | +### "Where does the original calibration writeup live?" |
| 120 | + |
| 121 | +Most-upvoted Q&A is user [@Erwinsmith101](https://github.com/Erwinsmith101)'s [#353](https://github.com/ESPresense/ESPresense/discussions/353) and DT's original writeup in [`#324`](https://github.com/ESPresense/ESPresense/issues/324). Both still match the current procedure on this page. |
| 122 | + |
| 123 | +## The easy path: let Companion auto-calibrate |
| 124 | + |
| 125 | +If you're running [ESPresense-Companion](https://github.com/ESPresense/ESPresense-companion) with a floorplan, hand calibration is mostly optional. Companion sends optimization beacons between nodes, compares measured vs. expected distances against your floorplan, and adjusts **Rx Adj RSSI** and **Absorption** automatically. |
| 126 | + |
| 127 | +Requirements: |
| 128 | + |
| 129 | +- Node coordinates on the floorplan are correct (measure with a tape, don't eyeball — even 50 cm matters). |
| 130 | +- At least three nodes that can hear each other. |
| 131 | +- Auto-optimization enabled in the Companion config. |
| 132 | + |
| 133 | +Results show up on the **Calibration** page in the Companion UI, where you can also reset them across all nodes. Full details: [Companion → Optimization](/companion/optimization). |
| 134 | + |
| 135 | +## Floorplan drift is a different problem |
| 136 | + |
| 137 | +If your *coordinates* are wrong on the floorplan — not your distances — calibration isn't the right knob. Check: |
| 138 | + |
| 139 | +- **Node coordinates on the map.** Even a 50 cm error compounds across rooms. |
| 140 | +- **Number of nodes seeing the device.** Aim for 5+ fixes for a stable solve. |
| 141 | +- **One outlier node** dragging the solve. Hover the device in Companion to see per-node distance circles — the one circle that doesn't intersect the others is the bad fix. Recalibrate or move that node. |
| 142 | +- **Z-axis / room-boundary logic.** [#1817](https://github.com/ESPresense/ESPresense/discussions/1817) is the worked example. |
| 143 | + |
| 144 | +## Re-calibrate if any of these change |
| 145 | + |
| 146 | +- Antenna (built-in → external, or vice versa). |
| 147 | +- Enclosure (especially metal, but even some 3D-printed enclosures with conductive infill shift readings). |
| 148 | +- USB cable to the node (long thin cables introduce noise). |
| 149 | +- The room itself: new furniture, big appliance, water tank, mirror. |
| 150 | + |
| 151 | +## Still stuck? |
| 152 | + |
| 153 | +Open a [Q&A discussion](https://github.com/ESPresense/ESPresense/discussions/categories/q-a) with: |
| 154 | + |
| 155 | +- Firmware version (Settings page → bottom). |
| 156 | +- Board model and antenna type. |
| 157 | +- The device(s) you're tracking. |
| 158 | +- Screenshots of the Settings page and a beacon's distance reading at a known location. |
| 159 | + |
| 160 | +Faster turnaround for back-and-forth: [Discord](https://discord.gg/jbqmn7V6n6). |
| 161 | + |
| 162 | +## Reference |
| 163 | + |
| 164 | +- Settings page → [Calibration section](/configuration/settings#calibration) |
| 165 | +- Per-device firmware constants: [`src/rssi.h`](https://github.com/ESPresense/ESPresense/blob/main/src/rssi.h) |
| 166 | +- Companion auto-optimization: [Companion → Optimization](/companion/optimization) |
| 167 | +- Canonical pin (maintained reference): [#2323 "Calibration — start here"](https://github.com/ESPresense/ESPresense/discussions/2323) |
0 commit comments