Vendor model development overview

To implement a new Bluetooth® mesh model, apply the steps described in this step-by-step model development process overview.

Defining a model identifier

Models define basic functionality of a node in the Bluetooth mesh network. Once it is decided on what functionality the model will define, its identifier must be specified to be able to recognize it among the other models in the mesh network.

As defined by the Bluetooth mesh profile specification, the vendor model identifiers are composed of two unique 16-bit values specifying a company identifier (Company ID), and a vendor-assigned model identifier (Model ID) tied to this Company ID.

#define YOUR_COMPANY_ID 0x1234
#define YOUR_MODEL_ID   0x5678

The Company ID must be registered with the Bluetooth® Special Interest Group (SIG), and the vendor owning the Company ID may freely allocate the model IDs for its Company ID. See Bluetooth SIG company identifiers for a list of Company IDs.

Adding the model to the node composition data

Once the model has its own Model ID, it can be added to the node composition data. This will register the model on the node, and enable the model configuration through the Configuration Client so that it can communicate with other models in the mesh network.

Note

The node composition data is passed to bt_mesh_init() at the mesh stack initialization, and cannot be changed at run time.

To add the model to the node composition data, use the BT_MESH_MODEL_VND_CB macro, that requires at least the Company ID and the Model ID to be provided. Below is an example of the simplest model initialization in the node composition data.

BT_MESH_MODEL_VND_CB(YOUR_COMPANY_ID,
                     YOUR_MODEL_ID,
                     BT_MESH_MODEL_NO_OPS,
                     NULL,
                     NULL,
                     NULL)

The third argument, the BT_MESH_MODEL_NO_OPS macro, specifies an empty operation code (opcode) list, meaning that the model won’t receive any messages. The other arguments are optional and set to NULL. The following sections describe how and when to use these arguments.

When defining the node composition data, pass the vendor models in the second parameter of the BT_MESH_ELEM macro:

static struct bt_mesh_elem elements[] = {
    BT_MESH_ELEM(
        1,
        BT_MESH_MODEL_LIST(BT_MESH_MODEL_CFG_SRV(&cfg_srv),
                           BT_MESH_MODEL_HEALTH_SRV(&health_srv,
                                                    &health_pub)),
        BT_MESH_MODEL_LIST(BT_MESH_MODEL_VND_CB(YOUR_COMPANY_ID,
                                                YOUR_MODEL_ID,
                                                BT_MESH_MODEL_NO_OPS,
                                                NULL,
                                                NULL,
                                                NULL))
    ),
};

Defining opcodes for the messages

The communication between the nodes within a mesh network is done by means of message exchange. Therefore, if you want to implement your own node behavior, you need to define your own set of messages that will be associated with this behavior. To do that, you need to define vendor-specific opcodes for new messages, using the BT_MESH_MODEL_OP_3 macro. This macro encodes an opcode into the special format defined by the Bluetooth mesh profile specification. Each vendor-specific message must be tied with a Company ID, passed as a second parameter to the macro:

BT_MESH_MODEL_OP_3(0x01, YOUR_COMPANY_ID)

The two most significant bits of the first octet in a vendor-specific opcode are always set to 1. Therefore, you can specify up to 64 different vendor-specific opcodes.

You can wrap your opcode in a macro to make it convenient to use in the future:

#define MESSAGE_SET_OPCODE    BT_MESH_MODEL_OP_3(0x01, YOUR_COMPANY_ID)
#define MESSAGE_ACK_OPCODE    BT_MESH_MODEL_OP_3(0x02, YOUR_COMPANY_ID)
#define MESSAGE_STATUS_OPCODE BT_MESH_MODEL_OP_3(0x03, YOUR_COMPANY_ID)

Receiving messages

If the model is to receive messages, create an opcode list that will define a list of messages that your model will receive. To create the opcode list, initialize an array of bt_mesh_model_op type with the following required parameters:

  1. Message opcode, bt_mesh_model_op.opcode, to register a message to be received by the model.

  2. Minimal message length, bt_mesh_model_op.len, that prevents the model from receiving messages shorter than the specified value.

  3. Message handler, bt_mesh_model_op.func, which is used to process the received message.

The last element in the opcode list is always the BT_MESH_MODEL_OP_END macro:

static void handle_message_set(struct bt_mesh_model *model,
                               struct bt_mesh_msg_ctx *ctx,
                               struct net_buf_simple *buf)
{
    // Message handler code
}

static void handle_message_ack(struct bt_mesh_model *model,
                               struct bt_mesh_msg_ctx *ctx,
                               struct net_buf_simple *buf)
{
    // Message handler code
}

static void handle_message_status(struct bt_mesh_model *model,
                                  struct bt_mesh_msg_ctx *ctx,
                                  struct net_buf_simple *buf)
{
    // Message handler code
}

const struct bt_mesh_model_op _opcode_list[] = {
    { MESSAGE_SET_OPCODE,    MESSAGE_SET_LEN,    handle_message_set },
    { MESSAGE_ACK_OPCODE,    MESSAGE_ACK_LEN,    handle_message_ack },
    { MESSAGE_STATUS_OPCODE, MESSAGE_STATUS_LEN, handle_message_status },
    BT_MESH_MODEL_OP_END,
};

To associate the opcode list with your model, use the BT_MESH_MODEL_VND_CB macro. It will initialize the bt_mesh_model.op field of the model context:

BT_MESH_MODEL_VND_CB(YOUR_COMPANY_ID,
                     YOUR_MODEL_ID,
                     _opcode_list,
                     NULL,
                     NULL,
                     NULL)

Sending messages

Before sending a message, you need to prepare a buffer that will contain the message data together with the opcode. This can be done using the BT_MESH_MODEL_BUF_DEFINE macro. It creates and initializes an instance of net_buf_simple, therefore, use the Network Buffer API to fill up the buffer:

BT_MESH_MODEL_BUF_DEFINE(buf, MESSAGE_SET_OPCODE, MESSAGE_SET_LEN);

To set the opcode of the message, call bt_mesh_model_msg_init():

bt_mesh_model_msg_init(&buf, MESSAGE_SET_OPCODE);

As described in Access API, the model can send a message in two ways:

  1. By using a custom bt_mesh_msg_ctx.

  2. By using a model publication context.

If you want your model to control a destination address or some other parameters of a message, you can initialize bt_mesh_msg_ctx with custom parameters, and pass it together with a message buffer to bt_mesh_model_send():

static int send_message(struct bt_mesh_model *model, uint16_t addr)
{
    struct bt_mesh_msg_ctx ctx = {
            .addr = addr,
            .app_idx = model->keys[0],
            .send_ttl = BT_MESH_TTL_DEFAULT,
    };

    BT_MESH_MODEL_BUF_DEFINE(buf, MESSAGE_SET_OPCODE, MESSAGE_SET_LEN);
    bt_mesh_model_msg_init(&buf, MESSAGE_SET_OPCODE);

    // Fill the message buffer here

    return bt_mesh_model_send(model, &ctx, &buf, NULL, NULL);
}

Note

Before sending the messages, you still need to bind an application key to your model using the Configuration Client.

The bt_mesh_model_send() function is also used if you need to send a reply on a received message. To do that, use the message context passed to a handler of a message that needs to be replied to, when calling bt_mesh_model_send():

static void handle_message_set(struct bt_mesh_model *model,
                               struct bt_mesh_msg_ctx *ctx,
                               struct net_buf_simple *buf)
{
    BT_MESH_MODEL_BUF_DEFINE(reply, MESSAGE_ACK_OPCODE, MESSAGE_ACK_LEN);
    bt_mesh_model_msg_init(&reply, MESSAGE_ACK_OPCODE);

    // Fill the reply buffer here

    (void) bt_mesh_model_send(model, ctx, &reply, NULL, NULL);
}

The model publication context defines the behavior of messages to be published by the model, and it is configured by the Configuration Client. If you want your model to send messages using the model publication context, create a bt_mesh_model_pub instance and pass it to BT_MESH_MODEL_VND_CB macro to initialize bt_mesh_model.pub:

static struct bt_mesh_model_pub pub_ctx;

BT_MESH_MODEL_VND_CB(YOUR_COMPANY_ID,
                     YOUR_MODEL_ID,
                     _opcode_list,
                     &pub_ctx,
                     NULL,
                     NULL)

You must initialize the bt_mesh_model_pub.msg publication buffer when using the model publication context. This can be done in two ways. Either by using the NET_BUF_SIMPLE macro:

static struct bt_mesh_model_pub pub_ctx = {
    .msg = NET_BUF_SIMPLE(BT_MESH_MODEL_BUF_LEN(MESSAGE_SET_OPCODE,
                                                MESSAGE_SET_MAXLEN)),
}

Or, for example, in the bt_mesh_model_cb.init callback, using net_buf_simple_init_with_data():

static struct bt_mesh_model_pub pub_ctx;
static struct net_buf_simple pub_msg;
static uint8_t buf[BT_MESH_MODEL_BUF_LEN(MESSAGE_SET_OPCODE,
                                         MESSAGE_SET_MAXLEN)];

static int model_init(struct bt_mesh_model *model)
{
    model->pub = &pub_ctx;
    net_buf_simple_init_with_data(&pub_msg, buf, sizeof(buf));
    pub_ctx.msg = &pub_msg;

    return 0;
}

Note

The publication buffer size must be big enough to fit the longest message to be published.

How to initialize bt_mesh_model_cb.init is described later in this guide.

When the model supports the model publication, configure the model to send messages at certain periods, regardless of the current model state, using the Configuration Client. This is useful for periodic data publication, for example, if it changes over time. To support the model publication, initialize the bt_mesh_model_pub.update callback.

If the periodic publication is configured by the Configuration Client, the access layer calls the bt_mesh_model_pub.update callback in the beginning of each publication period. It resets the buffer provided in bt_mesh_model_pub.msg. Therefore, you only need to fill the data into the buffer:

static int update_handler(struct bt_mesh_model *model)
{
    bt_mesh_model_msg_init(model->pub->msg, MESSAGE_STATUS_OPCODE);

    // Fill the model publication buffer here

    return 0;
}

Associating the user data

You can associate your data with the bt_mesh_model structure, through the bt_mesh_model.user_data field. This is useful for restoring your context associated with the model, whenever any of the callbacks defined by the Access API are called. To associate the data, pass the pointer to your data to the BT_MESH_MODEL_VND_CB macro.

Defining model callbacks

The Access API provides a set of callbacks that are called when certain events occur. These callbacks are defined in bt_mesh_model_cb.

A diagram representing the callbacks for Bluetooth mesh models.

Bluetooth mesh model callbacks

bt_mesh_model_cb.settings_set

This handler is called when the model data is restored from the persistent storage. If you need to store data in the persistent storage, use the bt_mesh_model_data_store() function. To use the persitent storage, it needs to be enabled with CONFIG_BT_SETTINGS. For more information on persistent storage, see Settings.

bt_mesh_model_cb.start

This handler is called after the node has been provisioned, or after all mesh data is loaded from the persistent storage. When this callback fires, the mesh model may start its behavior, and all Access APIs are ready for use.

bt_mesh_model_cb.init

This handler is called on every model instance during the mesh initialization. Implement it if you need to do additional model initialization before the mesh stack starts, for example, to initialize the model publication context. If any of the model init callbacks return an error, the mesh subsystem initialization is aborted, and the error is returned to the caller of bt_mesh_init().

bt_mesh_model_cb.reset

The model reset handler is called when the mesh node is reset. All of the model’s configuration is deleted on reset, and the model should clear its state. If the model stores any persistent data, this needs to be erased manually.

If you want to use any of these callbacks, create an instance of bt_mesh_model_cb and initialize any of the required callbacks. Use the BT_MESH_MODEL_VND_CB macro to associate the callbacks with your model. It will initialize the bt_mesh_model.cb field of the model context:

static struct bt_mesh_model_cb model_cbs;

BT_MESH_MODEL_VND_CB(YOUR_COMPANY_ID,
                     YOUR_MODEL_ID,
                     _opcode_list,
                     &pub_ctx,
                     NULL,
                     &model_cbs)