ble mesh tutorial – BLE Mesh Provisioning

EmbeddedPathashala · BLE Mesh Series

ble mesh tutorial – BLE Mesh Provisioning
Part 4 — Devices, Nodes & Provisioning with BlueZ

How an unprovisioned device joins a mesh network — and how to do it yourself using meshctl and mesh-cfgclient.

5
Provisioning Steps
3
Key Types
4
Auth Methods

Key Concepts in This Post

Provisioner Unprovisioned Device Node Device Key (DevKey) Unicast Address Attention Timer OOB Authentication meshctl mesh-cfgclient

From Unprovisioned Device to Mesh Node

In Bluetooth Mesh, joining a network is called provisioning — not pairing or bonding. It is a fundamentally different process: instead of two devices linking directly, a Provisioner (typically a smartphone or a Linux gateway running meshctl) invites a device into the network, assigns it a unicast address, and hands it the keys it needs to participate.

Once provisioned, the device is a node and can send/receive mesh messages. If a node is later removed from the network, it returns to being an unprovisioned device.

🔌 Devices vs Nodes

BEFORE PROVISIONING
📱
Unprovisioned Device
  • Cannot send or receive mesh messages
  • Broadcasts Unprovisioned Device Beacon
  • Has no unicast address yet
  • Has no network or application keys
  • Waiting to be invited by a Provisioner

Provisioning

AFTER PROVISIONING
🏢
Mesh Node
  • Full mesh participant
  • Has a unique 16-bit unicast address
  • Holds NetKey(s), AppKey(s), DevKey
  • Can send, receive, and relay messages
  • Managed by a Configuration Client

🔐 The Three Key Types
🌐 NetKey (Network Key)

Shared by all nodes in a subnet. Used to encrypt and authenticate at the Network Layer. A relay node only needs the NetKey to forward messages — it never sees the payload.

📋 AppKey (Application Key)

Shared by nodes that belong to the same application group (e.g., all lights). Used to encrypt payload at the Upper Transport Layer. Bound to a NetKey.

🔑 DevKey (Device Key)

Unique per node. Assigned during provisioning. Used by the Configuration Client to securely communicate with each individual node — e.g., to push new NetKeys or AppKeys.

🚀 Provisioning Flow — Step by Step

1
Device Beaconing

The unprovisioned device broadcasts a Mesh Beacon on BLE advertising channels. The beacon contains the device UUID (128-bit) and optional OOB info flags.

2
Provisioner Invites

The Provisioner (e.g., meshctl) scans, finds the UUID, and sends a Provisioning Invite PDU. It also sets the attention timer — the device flashes, beeps, or vibrates to confirm identity.

3
Key Exchange (ECDH)

Device and Provisioner exchange ECDH public keys to establish a shared secret. This prevents eavesdroppers from obtaining the provisioning data exchanged in the next steps.

4
Authentication

Device is authenticated using an OOB method: No OOB (skip), Static OOB (fixed code), Output OOB (device blinks a number), or Input OOB (user types a number into device). This prevents rogue devices joining.

5
✅ Provisioning Data Distributed

Provisioner sends the device: NetKey, NetKey Index, IV Index, a Unicast Address, and IV Update flags. Device derives its DevKey from the shared secret. Device is now a node.

What is the Attention Timer?

During provisioning, the Provisioner sets a non-zero attention timer (in seconds) on the device. While this timer is running, the device identifies itself using any capability it has — flashing an LED, making a sound, or vibrating. When the timer expires, the device stops. This allows a Provisioner to confirm it is talking to the right physical device in a room full of identical sensors.

🔧 BlueZ: Full Provisioning Walkthrough

Step 1 — Start Mesh Daemon & Launch meshctl
bash — Terminal 1: start daemon
# Stop bluetoothd (cannot run alongside bluetooth-meshd)
sudo systemctl stop bluetooth

# Start the BlueZ mesh daemon
sudo bluetooth-meshd --nodetach --debug
bash — Terminal 2: start meshctl
meshctl
Step 2 — Create or Join a Mesh Network
meshctl — create a new mesh network
# Inside meshctl interactive prompt:

[meshctl]# create
# Creates a new mesh network.
# BlueZ generates a primary NetKey and assigns
# this machine the Provisioner role with unicast addr 0x0001.
# Network config stored in: ~/.config/meshctl/

# Alternatively, load an existing network:
[meshctl]# import /tmp/mesh-network.json
Step 3 — Discover and Provision an Unprovisioned Device
meshctl — scan and provision
# Start scanning for unprovisioned device beacons
[meshctl]# discover-unprovisioned on

# Expected output (when a device advertises its beacon):
# Unprovisioned device:
#   UUID = aabbccddeeff00112233445566778899
#   OOB  = 0x0000

# Stop scanning
[meshctl]# discover-unprovisioned off

# Provision the device using its UUID
# BlueZ will run ECDH key exchange + No-OOB auth by default
[meshctl]# provision aabbccddeeff00112233445566778899

# Expected output on success:
# Provisioning node aabbccddeeff00112233445566778899
# Provisioning complete
# Node 0x0002 added

# List all nodes now in the network
[meshctl]# node-list
# Unicast | UUID                             | DevKey
# 0x0001  | <provisioner uuid>               | ...
# 0x0002  | aabbccddeeff00112233445566778899 | ...
Step 4 — Configure the Node with mesh-cfgclient

After provisioning, the node only has the NetKey. You must push an AppKey and bind it to the node’s models before it can process application messages.

bash — launch mesh-cfgclient (BlueZ configuration client)
# mesh-cfgclient is the BlueZ configuration client tool
# It communicates with nodes over Foundation Model messages
mesh-cfgclient

# Inside mesh-cfgclient interactive prompt:

# Target node 0x0002 (the newly provisioned node)
[mesh-cfgclient]# target 0002

# Get the node composition data (element count, models)
[mesh-cfgclient]# get-composition 0
# Returns: CID, PID, VID, element list, model IDs per element

# Add AppKey index 0 bound to NetKey index 0
[mesh-cfgclient]# appkey-add 0 0
# Arguments: <net_key_index> <app_key_index>

# Bind AppKey 0 to the Generic OnOff Server model (0x1000)
# on element index 0 of node 0x0002
[mesh-cfgclient]# bind 0002 0 0 1000
# Arguments: <node_addr> <elem_idx> <app_key_idx> <model_id>

# Set publish address: node 0x0002 publishes to group 0xC000
[mesh-cfgclient]# pub-set 0002 C000 0 0 0 1000
# Arguments: <node> <pub_addr> <app_key_idx> <period> <retransmit> <model_id>

# Add subscription: node 0x0002 subscribes to group 0xC000
[mesh-cfgclient]# sub-add 0002 0 C000 1000
# Arguments: <node> <elem_idx> <sub_addr> <model_id>

[mesh-cfgclient]# quit

💡
Provisioner vs Configuration Client — What is the Difference?

A Provisioner adds unprovisioned devices to the network (meshctl). A Configuration Client configures already-provisioned nodes — distributing AppKeys, binding models, and setting publish/subscribe addresses (mesh-cfgclient). In practice, the same device (your Linux machine or smartphone) acts as both. They communicate using different key types: provisioning uses ECDH, while configuration uses the node’s unique DevKey.

📝 A Device Can Be in Multiple Mesh Networks

A single physical device can be provisioned into more than one mesh network. Each provisioning creates a separate instance with its own unicast address and device key. This is useful for a device that bridges two separate mesh networks, or a contractor’s phone that needs to manage multiple buildings. Each instance is completely isolated by its own set of keys and addresses.

🏆 Series Recap

Part 1 — Architecture

8-layer stack from BLE Core to Model Layer. Two separate encryption keys (NetKey, AppKey). Works on any BLE chip.

Part 2 — Layers Deep Dive

PDU encapsulation, AES-CCM encryption at two layers, SAR in lower transport, Bearer types, BlueZ D-Bus API.

Part 3 — Operation

Managed-flood relay, TTL countdown, message cache (SRC+SEQ), subnets with NetKey isolation, IV Index, BlueZ meshd setup.

Part 4 — Provisioning

ECDH key exchange, OOB authentication, attention timer, meshctl provisioning, mesh-cfgclient AppKey distribution and model binding.

You Now Understand Bluetooth Mesh!

You have covered the full BLE Mesh stack — from radio to application model — and seen how BlueZ implements it on Linux.

Next up on EmbeddedPathashala: Writing a Custom BlueZ Mesh Application from Scratch — implementing a Generic OnOff Server node in Python using the D-Bus API.

More on EmbeddedPathashala ← Part 3: Mesh Operation

Leave a Reply

Your email address will not be published. Required fields are marked *