What is the Friendship Feature?
In Bluetooth Mesh, most nodes are always-on — they relay messages, respond instantly, and stay active on the network. But what about a small battery-powered sensor that cannot afford to keep its radio on all the time? That is where the Friendship feature comes in.
A Low Power Node (LPN) is a device that wants to sleep most of the time to save battery. The problem is that if it sleeps, it will miss messages sent to it. The solution is a Friend node — a mains-powered or well-powered device that stays awake, collects messages on behalf of the LPN, and hands them over when the LPN wakes up and asks for them.
Think of it like a post office. The LPN does not stand at its mailbox all day. Instead, the Friend node is the post office — it collects all the letters, and when the LPN visits, it gets everything in one shot.
Key Terms in This Post
The LPN does not just connect to any Friend node automatically. There is a proper handshake. Here is the sequence of messages that sets up a friendship:
| Low Power Node | Message | Friend Node |
|---|---|---|
| Sends | Friend Request ➜ | Receives & evaluates |
| Receives & picks best offer | ← Friend Offer | Sends |
| Sends | Friend Poll ➜ | Receives within 1 sec → friendship confirmed |
| Receives → friendship confirmed | ← Friend Update | Sends |
| ✅ Friendship Established | ||
Breaking down each message:
- Friend Request: The LPN broadcasts this to find potential Friend nodes. It carries parameters like RSSIFactor and ReceiveWindowFactor, which tell candidate Friend nodes how to prioritize their reply timing.
- Friend Offer: Any Friend node that can meet the LPN’s requirements responds. The delay before responding is calculated as:
Local Delay = ReceiveWindowFactor × ReceiveWindow − RSSIFactor × RSSI
If Local Delay > 100, that value is used in milliseconds. Otherwise, a minimum of 100 ms is applied. This way, the Friend node that best matches the LPN’s preferences replies first. - Friend Poll: The LPN chooses one Friend Offer (usually from the first reply — which is the best match) and sends a Friend Poll within 1 second. This is the LPN’s confirmation of the friendship.
- Friend Update: The Friend node replies with current IV Index, IV Update Flag, and Key Refresh Flag — the initial security sync.
The Friend node tracks a FriendCounter, a 2-byte value that starts at 0 and increments with every Friend Offer sent. This counter is used later to derive the friendship’s unique security keys. If the LPN was previously friends with a different Friend node, that old friendship is automatically terminated when a new one starts.
Once a friendship exists, the Friend node starts collecting messages for the LPN. Specifically, it stores two types of things in the Friend Queue:
- Network messages addressed to the LPN (or to group/virtual addresses the LPN has subscribed to)
- Security updates — changes to IV Index, IV Update flag, or Key Refresh flag
The LPN wakes up periodically and sends a Friend Poll to ask for any waiting messages. The Friend node replies with one message at a time from the queue.
| LPN (sleeping most of the time) | What happens | Friend Node (always awake) |
|---|---|---|
| 😴 Sleeping | Network message arrives for LPN | 📥 Stores in Friend Queue |
| 😃 Wakes up → sends Friend Poll (FSN=0) | Poll ➜ | Receives Poll, checks FSN |
| Receives message, authenticates → changes FSN to 1 | ← Message from Queue | Sends queued message |
| Sends next Poll (FSN=1) | Poll ➜ | FSN changed → send next message or “no more” |
The Friend Sequence Number (FSN) is the key to reliable delivery. Here is how it works:
- The LPN keeps an FSN value and sends it in every Friend Poll.
- When the LPN gets a message and successfully authenticates it using friendship security credentials, it increments its FSN.
- The next Poll carries the new FSN. The Friend node sees the FSN changed, so it knows the last message was received, and it can move to the next one.
- If there was no acknowledgement (LPN did not respond or did not change FSN), the Friend node resends the same message. This gives you reliable, in-order delivery even over a wireless link.
The Friend Subscription List is set up after the friendship is established. The LPN sends Friend Subscription List Add messages to tell the Friend node which group or virtual addresses it cares about. The Friend node then stores messages for those addresses too — not just unicast messages to the LPN.
Bluetooth Mesh uses two different sets of security credentials, and knowing which one is used for which message is important.
| Message | Security Credentials Used | Why |
|---|---|---|
| Friend Request | Master | No friendship exists yet — no shared friendship keys |
| Friend Offer | Master | Still pre-friendship, keys not yet derived |
| Friend Poll | Friendship | Friendship just established; keys now available |
| Friend Update | Friendship | Encrypted channel already active |
| Friend Subscription List Add / Confirm | Friendship | Part of the secured friendship exchange |
| Stored messages (Friend → LPN) | Friendship | Delivered only to this LPN via friendship tunnel |
| Friend Clear / Friend Clear Confirm | Master | Sent to the old Friend node which may not share keys |
| All other mesh messages | Master | Default for all non-friendship traffic |
There is one more nuance for messages sent by the LPN (outgoing). The LPN can choose which credentials to use based on a model configuration flag called the Publish Friendship Credentials Flag:
- If the flag is set, the LPN sends using friendship credentials. Only the Friend node can receive and decrypt it. The Friend node then re-relays the message using master credentials, so the rest of the mesh gets it.
- If the flag is not set, the LPN sends using master credentials. Any relay node — including the Friend node — within radio range can pick it up and forward it.
BlueZ includes mesh/ support (introduced in BlueZ 5.50+). For Low Power Node behaviour, the relevant daemon is bluetooth-meshd. Here is how you interact with it to set LPN features.
Check if meshd is running:
sudo systemctl status bluetooth-meshd
Connect to the mesh network via D-Bus and check node features:
# Using gdbus to inspect a mesh node's features
gdbus introspect --system \
--dest org.bluez.mesh \
--object-path /org/bluez/mesh
# Check feature bits on a provisioned node (replace 0000 with actual node index)
gdbus call --system \
--dest org.bluez.mesh \
--object-path /org/bluez/mesh/node0000 \
--method org.freedesktop.DBus.Properties.Get \
org.bluez.mesh.Node1 Features
The Features property returns a bitmask. The bit positions are:
| Bit | Feature | Meaning |
|---|---|---|
| Bit 0 | Relay | Can relay mesh messages |
| Bit 1 | Proxy | GATT proxy support |
| Bit 2 | Friend | Can act as a Friend node |
| Bit 3 | LowPower | Acts as a Low Power Node |
Writing a simple application node with LPN enabled (Python via D-Bus):
import dbus
BUS_NAME = "org.bluez.mesh"
IFACE_APP = "org.bluez.mesh.Application1"
IFACE_NODE = "org.bluez.mesh.Node1"
# When you expose your Application object over D-Bus,
# set the LowPower feature flag to True in your properties:
class MeshApplication(dbus.service.Object):
@dbus.service.method(dbus.PROPERTIES_IFACE, in_signature='ss', out_signature='v')
def Get(self, interface, prop):
if interface == IFACE_APP:
if prop == "CompanyID":
return dbus.UInt16(0x05F1) # Linux Foundation
if prop == "ProductID":
return dbus.UInt16(0x0001)
if prop == "VersionID":
return dbus.UInt16(0x0001)
raise dbus.exceptions.DBusException("Unknown property")
# To request LPN behaviour when attaching to meshd,
# set Features in the Attach call options:
def attach_as_lpn(bus, token):
mesh_mgr = dbus.Interface(
bus.get_object(BUS_NAME, "/org/bluez/mesh"),
"org.bluez.mesh.Network1"
)
# token is returned during Provisioning
# The application path is where your D-Bus object lives
app_path = "/com/example/meshapp"
mesh_mgr.Attach(app_path, token)
Monitoring Friend Poll / Friend Update at HCI level using btmon:
# Start btmon to capture all HCI events in real time
sudo btmon
# In another terminal, start the mesh daemon
sudo bluetooth-meshd --nodetach --debug
# btmon output will show mesh ADV packets.
# To filter only mesh traffic, pipe through grep:
sudo btmon 2>&1 | grep -i "mesh\|friend\|poll\|update"
Using meshctl to provision and check friendship status:
# Launch interactive meshctl
sudo meshctl
# Inside meshctl shell:
# Discover unprovisioned devices
discover-unprovisioned on
# Provision a node (replace UUID with the target device's UUID)
provision
# Once provisioned, list known nodes
nodes
# Get a node's composition data (includes supported features)
get-composition
Multiple Friend nodes can hear the LPN’s Friend Request. If they all replied at exactly the same time, the LPN would receive a collision of signals and likely decode none of them.
The Bluetooth Mesh spec solves this with the Friend Offer Delay formula. Each potential Friend node calculates its own delay before replying:
Local Delay = (ReceiveWindowFactor × ReceiveWindow) − (RSSIFactor × RSSI)
Minimum enforced delay = 100 ms (if Local Delay ≤ 100)
Practical effect:
- If the LPN cares more about a small ReceiveWindow (fast wake-up response), it sets a high ReceiveWindowFactor. Friend nodes with a small window calculate a lower delay and reply first.
- If the LPN cares more about signal quality (strong RSSI), it sets a high RSSIFactor. The Friend node with the strongest signal to the LPN gets a lower delay and replies first.
- The LPN picks the first Friend Offer it receives — which is naturally the best-matching node. Power efficient and collision-free.
| Concept | One-line Summary |
|---|---|
| LPN | Battery-powered node that sleeps; relies on a Friend node to hold messages |
| Friend Node | Always-on node that stores messages and hands them over when LPN polls |
| Friend Queue | Buffer inside Friend node that holds messages + security updates for the LPN |
| Friend Poll | LPN’s “give me my mail” message; carries FSN for reliable delivery |
| FSN | Sequence number that confirms a message was received; prevents re-delivery |
| Friendship security | Unique keys derived from FriendCounter; encrypts LPN↔Friend traffic only |
| Master security | Network-wide keys; used for Friend Request, Offer, Clear, and all other traffic |
| FriendCounter | 2-byte counter on Friend node; increments per Offer; seeds friendship key derivation |
| Friend Offer Delay | Staggered reply time so the best-matching Friend node answers first, collision-free |
Continue Learning Bluetooth Mesh
Next up: Bluetooth Mesh Security — IV Index, Key Refresh, and how the network rotates keys without going offline.
