Bluetooth Mesh — Foundation Models

Bluetooth Mesh — Foundation Models
Pages 136–139 · State Definitions, Composition Data & Model Publication
4
Key Sections
BlueZ
Code Examples
BLE Mesh
Protocol

What are Foundation Models?

When you build a Bluetooth Mesh network, every node (light bulb, sensor, switch) needs a way to tell the network who it is, what features it has, and how it should send messages. That is exactly what Foundation Models do — they are the core set of rules and data structures that make configuring and managing a mesh network possible.

Think of Foundation Models as the identity card + settings panel built into every mesh node. Without them, the provisioner (the device that adds nodes to the network) would have no idea what a new node can do or how to talk to it.

Keywords to Know

Foundation Model Composition Data Model Publication Publish Address Publish Period Log Field Endianness CID / PID / VID Features Bitmask Element SIG Model ID Vendor Model ID Step Resolution

📐 Section 4.1 — Conventions

Before diving into the data structures, the spec sets two ground rules that everything else follows.

4.1.1 Endianness — Little Endian

All multi-byte numbers in the Foundation layer are stored little endian — the least significant byte comes first. For example, the value 0x1234 is stored as 0x34 0x12 on the wire.

Byte Position Byte 0 (first on wire) Byte 1
Value 0x1234 0x34 (LSB) 0x12 (MSB)

4.1.2 Log Field Transformation — Compressing 2 bytes into 1

The mesh spec often needs to store a value that could be up to 65535 (2 bytes) into a single byte field. It does this using a logarithmic trick: instead of storing the exact value, it stores a log field value n where 2^(n-1) is the largest power of 2 that fits in the real value.

Simple rule: Log field value = the number of bits needed to represent the value. Log 0x01 → value is exactly 1. Log 0x08 → value is somewhere between 128 and 255.

Log Field (1 byte) Actual 2-byte Range
0x01 0x0001 (exactly 1)
0x04 0x0008 – 0x000F (8 to 15)
0x08 0x0080 – 0x00FF (128 to 255)
0x10 0x8000 – 0xFFFF (32768 to 65535)

💡 Why is this useful? Saving 1 byte per field matters a lot in BLE mesh where access payload size is limited. The tradeoff is that you lose precision — you only know the range, not the exact value.

🪪 Section 4.2.1 — Composition Data (The Node’s Identity Card)

Every node in a mesh network carries a Composition Data state. Think of it as a structured data packet that answers: “Who made me? What am I? What can I do? What models do I run?”

The data is split into pages. Page 0 is mandatory. Others are optional. The full state must not exceed the maximum BLE access payload size.

Composition Data Page 0 Fields

Field Size What it means
CID 2 bytes Company ID — assigned by Bluetooth SIG (e.g. Nordic = 0x0059)
PID 2 bytes Product ID — vendor-defined (e.g. 0x0001 = Smart Bulb v1)
VID 2 bytes Version ID — firmware/hardware version, vendor-defined
CRPL 2 bytes Minimum replay protection list entries — prevents replay attacks
Features 2 bytes Bitmask of supported node features (Relay, Proxy, Friend, Low Power)
Elements variable One or more element descriptions (see below)

Features Bitmask — What the node can do

Bit Feature What it does
0 Relay Re-broadcasts mesh messages to extend range
1 Proxy Bridges GATT phone apps into the mesh network
2 Friend Buffers messages for sleepy Low Power nodes
3 Low Power Battery-saving mode; pairs with a Friend node
4–15 RFU Reserved for future use

💡 Example: A node with Features = 0x0003 means bits 0 and 1 are set — it supports both Relay and Proxy. A simple sensor with no relay capability would have Features = 0x0000.

Elements — The building blocks inside a node

A node contains one or more elements. Each element is like a separate logical device inside the node. A smart power strip, for example, might have one element per socket. Each element runs its own set of models.

Field Size Meaning
Loc 2 bytes Location descriptor (e.g. “front”, “top”, “left”) from GATT Namespace
NumS 1 byte Number of SIG (standard) Model IDs that follow
NumV 1 byte Number of Vendor (custom) Model IDs that follow
SIG Models variable Array of NumS standard 2-byte model IDs
Vendor Models variable Array of NumV vendor 4-byte model IDs

Visual: A Node with 2 Elements

Composition Data Page 0
CID
2B
PID
2B
VID
2B
CRPL
2B
Features
2B
Elements →
Element 0
Loc | NumS=3 | NumV=0
Model0 | Model1 | Model2
Element 1
Loc | NumS=2 | NumV=2
Model0 | Model1 | VModel0 | VModel1

BlueZ Example — Reading Composition Data via meshctl

In BlueZ’s meshctl tool, after provisioning a node you can retrieve its Composition Data Page 0:

# Launch meshctl and join the network
$ sudo meshctl

# Inside meshctl: provision a nearby unprovisioned device
[meshctl]# discover-unprovisioned on
[meshctl]# provision <UUID>

# After provisioning, send a Config Composition Data Get message
# This is opcode 0x8008, targeting page 0
[meshctl]# menu config
[meshctl - config]# target <unicast_address>
[meshctl - config]# composition-get 0

# The node responds with its Page 0 data.
# BlueZ prints: CID, PID, VID, CRPL, Features, and each element's models

If you are reading raw HCI data, the Composition Data response comes back as a little-endian byte stream. Here is how you would parse the Features field in C:

#include <stdio.h>
#include <stdint.h>

typedef struct {
    uint16_t cid;
    uint16_t pid;
    uint16_t vid;
    uint16_t crpl;
    uint16_t features;
} comp_data_page0_header_t;

void print_features(uint16_t features) {
    printf("Node Features:\n");
    printf("  Relay:     %s\n", (features & (1 << 0)) ? "Yes" : "No");
    printf("  Proxy:     %s\n", (features & (1 << 1)) ? "Yes" : "No");
    printf("  Friend:    %s\n", (features & (1 << 2)) ? "Yes" : "No");
    printf("  Low Power: %s\n", (features & (1 << 3)) ? "Yes" : "No");
}

int main() {
    /* Example: features = 0x0003 means Relay + Proxy supported */
    uint16_t features = 0x0003;
    print_features(features);
    return 0;
}

📡 Section 4.2.2 — Model Publication (How a model sends messages)

Once a node is provisioned and its composition is known, the next question is: how does a model know where to send messages, how often, and with what settings? That is controlled by the Model Publication state.

Each model in each element has its own independent Model Publication state instance. The state holds:

Component Role
Publish Address Where to send the message (group address, unicast, or Label UUID)
Publish Period How often to publish (encoded as steps × resolution)
Publish AppKey Index Which application key to use for encrypting messages
Friendship Credential Flag Whether to use friendship security credentials
Publish TTL Time-To-Live — how many hops the message can take
Retransmission Count How many times to re-send the same message
Retransmit Interval Steps Delay between retransmissions

4.2.2.1 Publish Address

This is the destination address for messages the model publishes. It can be:

  • Unassigned address (0x0000) — model is inactive, sends nothing unsolicited
  • Unicast address — messages go to one specific node
  • Group address — messages go to all subscribers in a group (e.g. all lights in a room)
  • Label UUID — a virtual address derived from a 128-bit UUID

💡 If a model’s publish address is set to 0x0000 (unassigned), it will only reply to acknowledged messages — it never sends anything on its own.

4.2.2.2 Publish Period

The Publish Period is a 1-byte value packed with two fields using a simple bit layout:

Bits 5..0 — Number of Steps (6 bits) Bits 7..6 — Step Resolution (2 bits)
b5 b4 b3 b2 b1 b0 b7 b6

The Step Resolution (2 bits) sets the unit of time:

Value (bits 7:6) Step Unit Max Period (63 steps)
0b00 100 ms 6.3 seconds
0b01 1 second 63 seconds
0b10 10 seconds 630 seconds (~10.5 min)
0b11 10 minutes 630 minutes (~10.5 hours)

Actual Period = Number of Steps × Step Unit

💡 Example: Byte = 0x26 → binary 0010 0110 → Step Resolution = 00 (100 ms), Number of Steps = 100110 = 38. Publish Period = 38 × 100 ms = 3800 ms = 3.8 seconds.

BlueZ Example — Setting Model Publication via meshctl

# Inside meshctl config menu, target the provisioned node
[meshctl - config]# target 0x0005

# Set publication for a Generic OnOff Server model (SIG Model ID 0x1000)
# Syntax: pub-set <element_address> <pub_address> <appkey_index> <ttl> <period> <retransmit> <model_id>
#
# pub_address  : 0xC000 (a group address — all lights in the room)
# appkey_index : 0      (use AppKey 0)
# ttl          : 5      (message lives for 5 hops)
# period       : 0x26   (38 steps × 100ms = 3.8 seconds)
# retransmit   : 0x05   (5 retransmissions, default interval)
# model_id     : 0x1000 (Generic OnOff Server)

[meshctl - config]# pub-set 0x0005 0xC000 0 5 0x26 0x05 0x1000

# A successful Config Model Publication Status response confirms the change.

Here is how you would build the Publish Period byte in C before passing it to your mesh stack:

#include <stdint.h>
#include <stdio.h>

/* Step Resolution values */
#define STEP_RES_100MS   0x00
#define STEP_RES_1S      0x01
#define STEP_RES_10S     0x02
#define STEP_RES_10MIN   0x03

uint8_t build_publish_period(uint8_t steps, uint8_t resolution) {
    /* steps       : 0-63  (6 bits)
       resolution  : 0-3   (2 bits, goes into bits 7:6) */
    return (uint8_t)((resolution << 6) | (steps & 0x3F));
}

int main() {
    /* Publish every 1 second: 1 step × 1s resolution */
    uint8_t period = build_publish_period(1, STEP_RES_1S);
    printf("Publish Period byte for 1s: 0x%02X\n", period);  /* 0x41 */

    /* Publish every 10 minutes */
    uint8_t period2 = build_publish_period(1, STEP_RES_10MIN);
    printf("Publish Period byte for 10min: 0x%02X\n", period2);  /* 0xC1 */

    return 0;
}

✅ Quick Recap — What You Learned
Topic One-line Summary
Foundation Models Core building blocks that let nodes announce identity and get configured
Little Endian All multi-byte values put the LSB first on the wire
Log Field Compresses 2-byte range into 1 byte using a power-of-2 index
Composition Data Page 0 Node’s identity card: company, product, version, features, and elements
Features Bitmask 4 bits tell you if a node can do Relay, Proxy, Friend, or Low Power
Elements Logical sub-devices inside a node, each running its own model set
Model Publication Per-model state that controls destination, frequency, and TTL of messages
Publish Period 1-byte value: 6-bit step count × 2-bit resolution (100ms to 10min per step)

Keep Learning Bluetooth Mesh

This post is part of the EmbeddedPathashala Bluetooth Classic & Mesh series.

Back to Home All BT Posts

Leave a Reply

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