Chat sample walk-through

After covering the basics of the model implementation, let’s take a look at how it works on the example of the Bluetooth Mesh: Chat sample. The sample implements a vendor model that is called the Chat Client model.

The code below shows how Company ID, Model ID and message opcodes for the messages are defined in the Chat Client model:

/** Company ID of the Bluetooth Mesh Chat Client model. */
#define BT_MESH_CHAT_CLI_VENDOR_COMPANY_ID    CONFIG_BT_COMPANY_ID_NORDIC

/** Model ID of the Bluetooth Mesh Chat Client model. */
#define BT_MESH_CHAT_CLI_VENDOR_MODEL_ID      0x000A

/** Non-private message opcode. */
#define BT_MESH_CHAT_CLI_OP_MESSAGE BT_MESH_MODEL_OP_3(0x0A, \
				       BT_MESH_CHAT_CLI_VENDOR_COMPANY_ID)

/** Private message opcode. */
#define BT_MESH_CHAT_CLI_OP_PRIVATE_MESSAGE BT_MESH_MODEL_OP_3(0x0B, \
				       BT_MESH_CHAT_CLI_VENDOR_COMPANY_ID)

/** Message reply opcode. */
#define BT_MESH_CHAT_CLI_OP_MESSAGE_REPLY BT_MESH_MODEL_OP_3(0x0C, \
				       BT_MESH_CHAT_CLI_VENDOR_COMPANY_ID)

/** Presence message opcode. */
#define BT_MESH_CHAT_CLI_OP_PRESENCE BT_MESH_MODEL_OP_3(0x0D, \
				       BT_MESH_CHAT_CLI_VENDOR_COMPANY_ID)

/** Presence get message opcode. */
#define BT_MESH_CHAT_CLI_OP_PRESENCE_GET BT_MESH_MODEL_OP_3(0x0E, \
				       BT_MESH_CHAT_CLI_VENDOR_COMPANY_ID)

The next code snippet shows the declaration of the opcode list:

const struct bt_mesh_model_op _bt_mesh_chat_cli_op[] = {
	{
		BT_MESH_CHAT_CLI_OP_MESSAGE,
		BT_MESH_LEN_MIN(BT_MESH_CHAT_CLI_MSG_MINLEN_MESSAGE),
		handle_message
	},
	{
		BT_MESH_CHAT_CLI_OP_PRIVATE_MESSAGE,
		BT_MESH_LEN_MIN(BT_MESH_CHAT_CLI_MSG_MINLEN_MESSAGE),
		handle_private_message
	},
	{
		BT_MESH_CHAT_CLI_OP_MESSAGE_REPLY,
		BT_MESH_LEN_EXACT(BT_MESH_CHAT_CLI_MSG_LEN_MESSAGE_REPLY),
		handle_message_reply
	},
	{
		BT_MESH_CHAT_CLI_OP_PRESENCE,
		BT_MESH_LEN_EXACT(BT_MESH_CHAT_CLI_MSG_LEN_PRESENCE),
		handle_presence
	},
	{
		BT_MESH_CHAT_CLI_OP_PRESENCE_GET,
		BT_MESH_LEN_EXACT(BT_MESH_CHAT_CLI_MSG_LEN_PRESENCE_GET),
		handle_presence_get
	},
	BT_MESH_MODEL_OP_END,
};

To send presence and non-private messages, the Chat Client model uses the model publication context. This will let the Configuration Client configure an address where the messages will be published to. Therefore, the model needs to declare bt_mesh_model_pub, net_buf_simple and a buffer for a message to be published. In addition to that, the model needs to store a pointer to bt_mesh_model, to use the Access API.

All these parameters are unique for each model instance. Therefore, together with other model specific parameters, they can be enclosed within a structure defining the model context:

/**
 * Bluetooth Mesh Chat Client model context.
 */
struct bt_mesh_chat_cli {
	/** Access model pointer. */
	const struct bt_mesh_model *model;
	/** Publish parameters. */
	struct bt_mesh_model_pub pub;
	/** Publication message. */
	struct net_buf_simple pub_msg;
	/** Publication message buffer. */
	uint8_t buf[BT_MESH_MODEL_BUF_LEN(BT_MESH_CHAT_CLI_OP_MESSAGE,
					  BT_MESH_CHAT_CLI_MSG_MAXLEN_MESSAGE)];
	/** Handler function structure. */
	const struct bt_mesh_chat_cli_handlers *handlers;
	/** Current Presence value. */
	enum bt_mesh_chat_cli_presence presence;
};

To initialize the model publication context and store bt_mesh_model, the model uses the bt_mesh_model_cb.init handler:

static int bt_mesh_chat_cli_init(const struct bt_mesh_model *model)
{
	struct bt_mesh_chat_cli *chat = model->rt->user_data;

	chat->model = model;

	net_buf_simple_init_with_data(&chat->pub_msg, chat->buf,
				      sizeof(chat->buf));
	chat->pub.msg = &chat->pub_msg;
	chat->pub.update = bt_mesh_chat_cli_update_handler;

	return 0;
}

The model implements the bt_mesh_model_cb.start handler to notify a user that the mesh data has been loaded from persistent storage and when the node is provisioned:

static int bt_mesh_chat_cli_start(const struct bt_mesh_model *model)
{
	struct bt_mesh_chat_cli *chat = model->rt->user_data;

	if (chat->handlers->start) {
		chat->handlers->start(chat);
	}

	return 0;
}

The presence value of the model needs to be stored in the persistent storage, so it can be restored at the kit reboot. The bt_mesh_model_data_store() function is called to store the presence value:

int bt_mesh_chat_cli_presence_set(struct bt_mesh_chat_cli *chat,
			 enum bt_mesh_chat_cli_presence presence)
{
	if (presence != chat->presence) {
		chat->presence = presence;

		if (IS_ENABLED(CONFIG_BT_SETTINGS)) {
			(void) bt_mesh_model_data_store(chat->model, true,
						NULL, &presence,
						sizeof(chat->presence));
		}
	}

	encode_presence(chat->model->pub->msg, chat->presence);

	return bt_mesh_model_publish(chat->model);
}

To restore the presence value, the model implements the bt_mesh_model_cb.settings_set handler:

#ifdef CONFIG_BT_SETTINGS
static int bt_mesh_chat_cli_settings_set(const struct bt_mesh_model *model,
					 const char *name,
					 size_t len_rd,
					 settings_read_cb read_cb,
					 void *cb_arg)
{
	struct bt_mesh_chat_cli *chat = model->rt->user_data;

	if (name) {
		return -ENOENT;
	}

	ssize_t bytes = read_cb(cb_arg, &chat->presence,
				sizeof(chat->presence));
	if (bytes < 0) {
		return bytes;
	}

	if (bytes != 0 && bytes != sizeof(chat->presence)) {
		return -EINVAL;
	}

	return 0;
}
#endif

The stored presence value needs to be reset if the node reset is applied. This will make the model start as it was just initialized. This is done through the bt_mesh_model_cb.reset handler:

static void bt_mesh_chat_cli_reset(const struct bt_mesh_model *model)
{
	struct bt_mesh_chat_cli *chat = model->rt->user_data;

	chat->presence = BT_MESH_CHAT_CLI_PRESENCE_AVAILABLE;

	if (IS_ENABLED(CONFIG_BT_SETTINGS)) {
		(void) bt_mesh_model_data_store(model, true, NULL, NULL, 0);
	}
}

Note

The bt_mesh_model_cb.settings_set handler, and both bt_mesh_model_data_store() calls, are only compiled if CONFIG_BT_SETTINGS is enabled, making it possible to exclude this functionality if the persistent storage is not enabled.

The following code initializes the model callbacks structure:

const struct bt_mesh_model_cb _bt_mesh_chat_cli_cb = {
	.init = bt_mesh_chat_cli_init,
	.start = bt_mesh_chat_cli_start,
#ifdef CONFIG_BT_SETTINGS
	.settings_set = bt_mesh_chat_cli_settings_set,
#endif
	.reset = bt_mesh_chat_cli_reset,
};

At this point, everything is ready for defining an entry for the node composition data. For the convenience, the initialization of the BT_MESH_MODEL_VND_CB macro is wrapped into a BT_MESH_MODEL_CHAT_CLI macro, which only requires a pointer to the instance of the model context defined above:

/** @def BT_MESH_MODEL_CHAT_CLI
 *
 * @brief Bluetooth Mesh Chat Client model composition data entry.
 *
 * @param[in] _chat Pointer to a @ref bt_mesh_chat_cli instance.
 */
#define BT_MESH_MODEL_CHAT_CLI(_chat)                                          \
		BT_MESH_MODEL_VND_CB(BT_MESH_CHAT_CLI_VENDOR_COMPANY_ID,       \
			BT_MESH_CHAT_CLI_VENDOR_MODEL_ID,                      \
			_bt_mesh_chat_cli_op, &(_chat)->pub,                   \
			BT_MESH_MODEL_USER_DATA(struct bt_mesh_chat_cli,       \
						_chat),                        \
			&_bt_mesh_chat_cli_cb)

The user data, which stores a pointer to the model context, is initialized through the BT_MESH_MODEL_USER_DATA macro. This is done to ensure that the correct data type is passed to BT_MESH_MODEL_CHAT_CLI. The following code snippet shows how the defined macro is used when defining the node composition data:

static struct bt_mesh_chat_cli chat = {
	.handlers = &chat_handlers,
};

static struct bt_mesh_elem elements[] = {
	BT_MESH_ELEM(
		1,
		BT_MESH_MODEL_LIST(
			BT_MESH_MODEL_CFG_SRV,
			BT_MESH_MODEL_HEALTH_SRV(&health_srv, &health_pub)),
		BT_MESH_MODEL_LIST(BT_MESH_MODEL_CHAT_CLI(&chat))),
};

The Chat Client model allows sending of private messages. This means that only the node with the specified address will receive the message. This is done by initializing bt_mesh_msg_ctx with custom parameters:

int bt_mesh_chat_cli_private_message_send(struct bt_mesh_chat_cli *chat,
					  uint16_t addr,
					  const uint8_t *msg)
{
	struct bt_mesh_msg_ctx ctx = {
		.addr = addr,
		.app_idx = chat->model->keys[0],
		.send_ttl = BT_MESH_TTL_DEFAULT,
		.send_rel = true,
	};

	BT_MESH_MODEL_BUF_DEFINE(buf, BT_MESH_CHAT_CLI_OP_PRIVATE_MESSAGE,
				 BT_MESH_CHAT_CLI_MSG_MAXLEN_MESSAGE);
	bt_mesh_model_msg_init(&buf, BT_MESH_CHAT_CLI_OP_PRIVATE_MESSAGE);

	net_buf_simple_add_mem(&buf, msg,
			       strnlen(msg,
				       CONFIG_BT_MESH_CHAT_CLI_MESSAGE_LENGTH));
	net_buf_simple_add_u8(&buf, '\0');

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

Note that it also sets bt_mesh_msg_ctx.send_rel to ensure that the message is delivered to the destination.

When the Chat Client model receives a private text message, or some of the nodes want to get the current presence of the model, it needs to send an acknowledgment back to the originator of the message. This is done by calling the bt_mesh_model_send() function, with bt_mesh_msg_ctx passed to the message handler:

static void send_message_reply(struct bt_mesh_chat_cli *chat,
			     struct bt_mesh_msg_ctx *ctx)
{
	BT_MESH_MODEL_BUF_DEFINE(msg, BT_MESH_CHAT_CLI_OP_MESSAGE_REPLY,
				 BT_MESH_CHAT_CLI_MSG_LEN_MESSAGE_REPLY);
	bt_mesh_model_msg_init(&msg, BT_MESH_CHAT_CLI_OP_MESSAGE_REPLY);

	(void)bt_mesh_model_send(chat->model, ctx, &msg, NULL, NULL);
}

static int handle_private_message(const struct bt_mesh_model *model, struct bt_mesh_msg_ctx *ctx,
				  struct net_buf_simple *buf)
{
	struct bt_mesh_chat_cli *chat = model->rt->user_data;
	const uint8_t *msg;

	msg = extract_msg(buf);

	if (chat->handlers->private_message) {
		chat->handlers->private_message(chat, ctx, msg);
	}

	send_message_reply(chat, ctx);
	return 0;
}