LPN
Low Power Node
Friend Node
Message Buffer
Friend Queue
Offline Storage
PollTimeout
Keepalive Timer
What is the Friendship Feature?
Imagine you have a battery-powered door sensor in a Bluetooth Mesh network. It cannot afford to keep its radio on all the time — that would drain the battery within days. But other nodes in the mesh might keep sending messages meant for it while it is sleeping.
The Friendship feature solves this problem. A nearby always-on node (called a Friend Node) acts like a postman — it collects and holds messages on behalf of the sleeping sensor. When the sensor wakes up, it asks the friend: “Got anything for me?” and the friend hands over all the buffered messages.
The sleeping sensor is called a Low Power Node (LPN). The always-on helper is the Friend Node. Together they form a Friendship.
Key Terms to Know
When an LPN wants a friend, it broadcasts a Friend Request message to the whole mesh. Any nearby Friend-capable node hears this and can reply with a Friend Offer.
Multiple Friend nodes may reply — the LPN picks the best one (based on signal strength and receive window size) and sends a Friend Poll directly to that chosen node. The Friend node responds with a Friend Update, and the friendship is now officially established.
Friendship Establishment Flow:
| LPN | Friend 1 | Friend 2 | Friend 3 |
|---|---|---|---|
| Needs Friend | |||
| ──Friend Request──▶ | ──▶ | ──▶ | ──▶ |
| Each Friend calculates delay = f(RSSI, ReceiveWindow) | |||
| ◀──Friend Offer── | ◀── | ||
| ◀──Friend Offer── | ◀── | ||
| ◀──Friend Offer── | ◀── | ||
| Chooses Friend 2 | |||
| ──Friend Poll──▶ | ──▶ | ||
| ◀──Friend Update── | ◀── | ||
| No Friendship | ✅ Friendship Established | No Friendship | |
Once friendship is established, the Friend node creates an empty Friend Subscription List and starts storing incoming messages for the LPN in the Friend Queue.
The LPN wakes up periodically and sends a Friend Poll message to ask: “Do you have anything for me?” The Friend node replies with the oldest message in the queue.
The LPN uses a Friend Sequence Number (FSN) bit in the Poll message to confirm receipt. If the FSN value is the same as the previous Poll, it means the LPN didn’t get the last message — the Friend re-sends it. If the FSN changes, the Friend knows the LPN got it and sends the next message.
FSN Logic — What the Friend Does:
| Poll FSN | Friend Action | Why |
|---|---|---|
| Same as last Poll | Re-send the same message (if not discarded) | LPN did not acknowledge previous delivery |
| Different from last Poll | Send next (oldest) message from queue | LPN confirmed receipt of previous message |
The Friend must reply within a window: ReceiveDelay ≤ reply time ≤ (ReceiveDelay + ReceiveWindow). This gives the LPN a predictable wakeup window — it can go to sleep right after the window closes.
Response Timing Window:
| LPN sends Friend Poll |
ReceiveDelay (min wait) |
✅ Valid window Friend replies here |
❌ Too late (after ReceiveDelay + ReceiveWindow) |
| e.g. 100ms | 100ms → 350ms | > 350ms |
Important note: Messages in the Friend Queue are sent unchanged — even if the IV Index (network security index) has changed on the Friend node. The LPN must poll at least once every 96 hours, or the Friend may discard queued messages.
A friendship has a PollTimeout timer on the Friend node side. Every time the Friend receives a Friend Poll, a Friend Subscription List Add, or a Friend Subscription List Remove message from the LPN, the timer resets.
If the LPN goes silent — doesn’t send any of those messages — and the PollTimeout timer expires, the Friend node:
- Terminates the friendship
- Discards everything in the Friend Queue
Friendship Termination by PollTimeout:
| LPN | Friend Node |
|---|---|
| Friendship Established | Friendship Established + PollTimeout starts |
| ──Friend Poll──▶ | ◀── Friend Update (PollTimeout resets) |
| LPN goes silent / offline | ⏳ PollTimeout counting down… |
| ── Poll ──✕ (no reply) | No message received |
| ── Poll ──✕ (no reply) | No message received |
| Friendship Terminated (LPN side) | ⌛ PollTimeout expired → Queue discarded |
The Friend also terminates the friendship immediately if it receives a Friend Clear message — this happens when the LPN has moved on and found a new Friend (see next section).
When an LPN drops its old Friend and establishes a friendship with a new one, the new Friend must notify the old Friend to clean up. This is done using a Friend Clear message.
The new Friend knows about the previous Friend because the LPN includes the old Friend’s address in the PreviousAddress field of the Friend Request message.
Friend Clear Procedure — Step by Step:
| Step | What Happens |
|---|---|
| 1 | New Friend sends Friend Clear to old Friend’s address with TTL set to maximum |
| 2 | Two timers start: Friend Clear Repeat (1 second) and Friend Clear Procedure (2 × PollTimeout) |
| 3 | If old Friend replies with Friend Clear Confirm → both timers cancelled, procedure done ✅ |
| 4 | If Repeat timer expires with no reply → send another Friend Clear, double the Repeat timer (1s → 2s → 4s → …) |
| 5 | If Procedure timer (2 × PollTimeout) expires → cancel Repeat timer, give up, procedure done ✅ |
Friend Clear Retry with Exponential Backoff:
| New Friend Node | Old Friend Node | Timer |
|---|---|---|
| ── Friend Clear ──▶ | (offline / no reply) | 0s — start |
| ── Friend Clear ──▶ | (offline / no reply) | 1s expired |
| ── Friend Clear ──▶ | ◀── Friend Clear Confirm | 2s expired |
| ✅ Procedure Complete — Old friendship cleared | ||
By default, the Friend only stores messages addressed directly to the LPN’s unicast address. But what if the LPN also wants messages sent to a group address (like “all lights” or “all sensors”)?
The LPN sends a Friend Subscription List Add message telling the Friend: “Also buffer messages sent to these group addresses for me.” The Friend adds them to the Friend Subscription List and replies with a Friend Subscription List Confirm.
Similarly, the LPN can send Friend Subscription List Remove to stop buffering messages for a particular group address.
Subscription List Update Flow:
| LPN | Friend Node |
|---|---|
| ── Subscription List Add [0xC001] ──▶ | Adds 0xC001 to Subscription List |
| ◀── Subscription List Confirm ── | Sent within ReceiveDelay + ReceiveWindow |
| ── Subscription List Remove [0xC001] ──▶ | Removes 0xC001 from Subscription List |
| ◀── Subscription List Confirm ── | TransactionNumber matches request |
BlueZ supports Bluetooth Mesh via the bluetooth-meshd daemon. You interact with it through D-Bus or the bluetoothctl / mesh-cfgclient tools. Below are practical examples.
1. Check if mesh daemon is running:
sudo systemctl status bluetooth-meshd
# Start it if not running
sudo systemctl start bluetooth-meshd
sudo systemctl enable bluetooth-meshd
2. Join a mesh network and provision a node:
# Start mesh-cfgclient (BlueZ mesh configuration client)
mesh-cfgclient
# Inside the tool — discover unprovisioned devices
discover-unprovisioned on
# Provision a device (UUID shown in scan results)
provision <device-UUID>
3. Get feature information from a node (including Friend/LPN support):
# Inside mesh-cfgclient — get composition data (includes feature flags)
composition-get <node-unicast-address> 0
# Feature flags in Composition Data Page 0:
# Bit 0 = Relay
# Bit 1 = Proxy
# Bit 2 = Friend <-- Friend feature supported
# Bit 3 = Low Power <-- LPN feature supported
4. Enable the Friend feature on a node via config model:
# Set Friend feature ON (1 = enable, 0 = disable)
# Uses Configuration Friend Set message
friend-set <node-unicast-address> 1
# Verify Friend state
friend-get <node-unicast-address>
5. D-Bus — interact with mesh node programmatically (Python example):
import dbus
# Connect to the system bus
bus = dbus.SystemBus()
# Get the mesh management interface
mesh_obj = bus.get_object(
'org.bluez.mesh',
'/org/bluez/mesh'
)
manager = dbus.Interface(mesh_obj, 'org.bluez.mesh.Network1')
# Attach to an existing mesh network node
# token is a 64-bit value returned at provisioning time
token = dbus.UInt64(0x1234567890ABCDEF)
manager.Attach('/org/example/mesh_app', token)
6. Monitor Friend/LPN messages using btmon:
# btmon captures all HCI events — mesh PDUs go over HCI as LE advertising
sudo btmon -w mesh_capture.btsnoop
# In another terminal, run your mesh application
# Then open the .btsnoop file in Wireshark for full mesh dissection
# Alternatively, filter mesh ADV packets live:
sudo btmon | grep -A5 "ADV_NONCONN_IND"
7. Check PollTimeout value on a Friend node (Config model):
# LPN Poll Timeout Get — asks Friend what timeout is configured for a given LPN
# inside mesh-cfgclient:
lpn-poll-timeout-get <friend-node-address> <lpn-unicast-address>
# Expected response includes PollTimeout in 100ms units
# e.g. value 0x000320 = 800 units × 100ms = 80 seconds
| Message | Sender | Purpose |
|---|---|---|
| Friend Request | LPN | Broadcast — “I am looking for a Friend” |
| Friend Offer | Friend Node | “I can be your Friend — here are my parameters” |
| Friend Poll | LPN | “Do you have messages for me?” (also resets PollTimeout) |
| Friend Update | Friend Node | Delivers a queued message (or confirms queue is empty) |
| Friend Clear | New Friend Node | “LPN has moved to me — please terminate your friendship” |
| Friend Clear Confirm | Old Friend Node | “Acknowledged — I have terminated the friendship” |
| Subscription List Add | LPN | Add group addresses to buffer on Friend side |
| Subscription List Remove | LPN | Remove group addresses from Friend’s buffer list |
| Subscription List Confirm | Friend Node | Confirms Add or Remove was applied |
Keep Learning Bluetooth Mesh
Explore more protocol layers on EmbeddedPathashala — free tutorials for engineers.
