What is this about?
When devices talk in a Bluetooth Mesh network, they don’t just shout into the air randomly. There are clear rules about who sends, who responds, and what happens when nobody replies. This post walks you through those rules using plain language and real BlueZ-based code snippets.
You will learn: the two message types (acknowledged vs unacknowledged), how publish/subscribe works, what state transitions look like, and when the server should retransmit.
Key Concepts in This Post
1. Two Ways a Node Can Send a Message
An unacknowledged message is a one-way broadcast. The sender transmits and moves on — it does not wait for any reply. Think of it like dropping a flyer in a letterbox. You don’t know if anyone read it.
When is it used? When a state changes on a node and it wants to inform the group. For example, a light turns on and publishes its new ON state to a group address. All nodes in the group receive it, but none send a reply back.
Key rule: The sender has no way to confirm delivery or processing.
📊 Message Flow — Unacknowledged
| Client Node |
XYZ Set Unacknowledged ──────▶
No reply comes back
|
Server Node |
| Server receives and processes. Publishes new state to Publish Group separately. | ||
An acknowledged message expects a reply. After the sender transmits, it waits for a status message back from the receiver. If no reply arrives within a timeout period, the sender can retransmit.
Important numbers to remember:
- The timeout for waiting should be at least 30 seconds (application-specific).
- If no response arrives by then, the sender treats the message as undelivered.
- Sending acknowledged messages to a group address is not recommended — you don’t know how many nodes will reply.
TTL tip: If the original message had TTL = 0, the response should also use TTL = 0.
📊 Acknowledged GET Flow
| Client |
XYZ Get ──────────────────▶
◀────────────── XYZ Status
|
Server |
📊 Acknowledged SET Flow (with Publish Group)
| Client |
XYZ Set ────▶
◀── XYZ Status
(client subscribed to publish group gets 2nd status)
|
Server |
XYZ Status ──▶
|
Publish Group |
2. Publish and Subscribe
A model publishes when it sends an unsolicited (nobody asked) message to a destination address. Every model inside a node has exactly one publish address. That address can be a unicast, group, or virtual address.
Think of it like a sensor node that publishes temperature every 10 seconds to a group address — no one needs to ask. It just broadcasts on schedule.
📊 Publish Address Concept
| Node A Model: Sensor Publish Address: 0xC000 (group) |
➡ | Group 0xC000 All subscribers receive the published message |
➡ | Node B, C, D… Subscribed to 0xC000 |
A model subscribes by adding group or virtual addresses to its subscription list. Any message sent to those addresses will be processed by this model. A model always implicitly listens to its own unicast address as well — you don’t need to configure that.
Subscription addresses can only be group or virtual addresses — never unicast. Unicast is handled automatically.
3. State Transitions
When a node receives a message to change its state (like “set brightness to 80%”), the state may not jump instantly. It can transition gradually from the current value to the target value.
Three important time concepts:
- Transition Time — total time from the initial state to the target state
- Remaining Time — time still left from now to the target state
- Present State — the current mid-transition value
📊 State Transition Timeline
|
||||||||||||
A Status message can be sent at any point and will always carry the current present state. If the transition is still ongoing, it also includes the remaining time. Once present state equals target state, the transition is complete.
Publishing rule during transitions:
- When the transition ends → publish a status message immediately.
- If the transition takes more than 2 seconds → also publish a status within 1 second of starting.
4. Periodic Publishing and Retransmissions
A model can be set to publish status messages regularly — even if nothing has changed. This is controlled by the Publish Period setting.
- If Publish Period > 0 → send a status at least once per period.
- If Publish Period = 0 → only publish when a state change happens (and publishing is enabled).
📊 Periodic Publish Timeline (Period = T)
| Status t=0 |
── T ──▶ | Status t=T |
── T ──▶ | Status t=2T |
── T ──▶ | Status t=3T |
⋯ |
When a new message is published, any pending retransmissions of the previous message are cancelled. The new message is retransmitted according to two settings:
- Publish Retransmit Count — how many times to retransmit
- Publish Retransmit Interval Steps — how long to wait between retransmissions
This ensures stale data is never retransmitted after a fresh update arrives.
5. BlueZ Code Examples
In BlueZ, you can send a mesh message from the meshctl command-line tool. Here is how an unacknowledged Set message looks:
# Start meshctl and attach to your mesh network
$ meshctl
# Connect to provisioned mesh network
[meshctl]# join /var/lib/bluetooth/mesh
# Send an unacknowledged Generic OnOff Set to group 0xC000
# Opcode 0x8203 = Generic OnOff Set Unacknowledged
# Payload: 01 (ON), TID: 01
[meshctl]# send 0100 8203 01 01
# No response expected — fire and forget
An acknowledged GET uses opcode 0x8201 (Generic OnOff Get). BlueZ’s mesh daemon handles the response reception. In a real application you register a message handler:
# Generic OnOff Get — acknowledged (expects Generic OnOff Status back)
# Opcode 0x8201
[meshctl]# send 0100 8201
# Server replies with Generic OnOff Status (opcode 0x8204)
# You will see the reply in the mesh app callback:
# Present OnOff: 0x01 (ON)
# Target OnOff: 0x01
# Remaining Time: 0x00 (already at target)
In a Python/D-Bus BlueZ mesh application:
import dbus
# D-Bus interface for BlueZ Mesh Application
# When a message arrives, the mesh daemon calls your MessageReceived method
class MeshApplication(dbus.service.Object):
@dbus.service.method("org.bluez.mesh.Application1",
in_signature="oqqaay", out_signature="")
def MessageReceived(self, path, source, key_index, data):
opcode = data[0]
# Generic OnOff Status = 0x04 (short opcode, 1 byte)
if opcode == 0x04:
present_onoff = data[1]
print(f"Present OnOff State: {'ON' if present_onoff else 'OFF'}")
# If transition is ongoing, remaining time is in data[3]
if len(data) > 3:
remaining_time = data[3]
print(f"Remaining transition time: {remaining_time}")
Before a model can publish, you must configure its publish address using the Configuration Client. Here is how to set a model’s publish address to group 0xC000:
# Connect to the Configuration Server of the target node
# Node unicast address: 0x0002
# Element index: 0 (primary element)
# Model ID: 0x1000 (Generic OnOff Server)
[meshctl]# config-pub-set 0002 0000 0xC000 0 0 0 1000
# Parameters:
# 0002 = destination node unicast address
# 0000 = AppKey index
# 0xC000 = publish address (group)
# 0 = publish TTL (0 = use default)
# 0 = publish period (0 = publish on state change only)
# 0 = retransmit count + interval
# 1000 = Model ID (Generic OnOff Server)
# After this, the server publishes OnOff Status to 0xC000
# whenever its state changes
# Add a subscription address to a model's subscription list
# Node: 0x0003, Element: primary, Model: Generic OnOff Client (0x1001)
# Subscribe to group: 0xC000
[meshctl]# config-sub-add 0003 0000 0xC000 1001
# Parameters:
# 0003 = destination node address
# 0000 = AppKey index
# 0xC000 = group address to subscribe to
# 1001 = Model ID (Generic OnOff Client)
# Now node 0x0003's Generic OnOff Client will receive
# all messages sent to group 0xC000
6. Quick Reference Summary
| Feature | Unacknowledged | Acknowledged |
|---|---|---|
| Response expected? | ❌ No | ✅ Yes (Status message) |
| Delivery confirmation? | ❌ None | ✅ Via status reply |
| Retransmit on no reply? | N/A | ✅ Yes (app-specific) |
| Timeout value | N/A | Min 30 seconds |
| Use with group address? | ✅ Fine | ⚠️ Not recommended |
| Aspect | Publish | Subscribe |
|---|---|---|
| Direction | Outgoing (model sends) | Incoming (model listens) |
| Address count per model | 1 publish address | 1 or more subscription addresses |
| Address types | Unicast, Group, or Virtual | Group or Virtual only |
| Configured by | Foundation model (Config Client) | Foundation model (Config Client) |
Next in the Bluetooth Mesh Series
Coming up: Mesh Security — Network Keys, Application Keys, and IV Index
