zephyr/subsys/bluetooth/mesh/access.c
Pavel Vasilyev 177e9b93bf Bluetooth: Mesh: Add API to store model's user data in settings work
Mesh models may have a data that needs to be stored persistently.
Currently, the models should call bt_mesh_model_data_store and the store
will happen in the calling context. Most likely that it will be called
in BT RX thread as this is the context from which model's opcodes
handlers are called. Thus, the thread will be blocked until the store is
finished.

Another issues is that some models may have states that changes
frequently. Triggering the store on every state change may wear out
flash. Therefore, the models need to implement some postpone mechanism
to reduce the flash wear out.

The mesh stack has already implemented the mechanism of deferred store
with its own settings. The models could use it instead of implementing
their own mechanism.

In combination with the mesh settings workqueue, the models can store
their data without blocking the stack work.

Signed-off-by: Pavel Vasilyev <pavel.vasilyev@nordicsemi.no>
2023-05-08 09:58:21 +02:00

2147 lines
47 KiB
C

/*
* Copyright (c) 2017 Intel Corporation
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <zephyr/kernel.h>
#include <errno.h>
#include <stdlib.h>
#include <zephyr/sys/util.h>
#include <zephyr/sys/byteorder.h>
#include <zephyr/net/buf.h>
#include <zephyr/bluetooth/bluetooth.h>
#include <zephyr/bluetooth/mesh.h>
#include "common/bt_str.h"
#include "host/testing.h"
#include "mesh.h"
#include "adv.h"
#include "net.h"
#include "lpn.h"
#include "transport.h"
#include "access.h"
#include "foundation.h"
#include "op_agg.h"
#include "settings.h"
#define LOG_LEVEL CONFIG_BT_MESH_ACCESS_LOG_LEVEL
#include <zephyr/logging/log.h>
LOG_MODULE_REGISTER(bt_mesh_access);
/* Model publication information for persistent storage. */
struct mod_pub_val {
uint16_t addr;
uint16_t key;
uint8_t ttl;
uint8_t retransmit;
uint8_t period;
uint8_t period_div:4,
cred:1;
};
struct comp_foreach_model_arg {
struct net_buf_simple *buf;
size_t *offset;
};
static const struct bt_mesh_comp *dev_comp;
static uint16_t dev_primary_addr;
static void (*msg_cb)(uint32_t opcode, struct bt_mesh_msg_ctx *ctx, struct net_buf_simple *buf);
/* Structure containing information about model extension */
struct mod_relation {
/** Element that composition data base model belongs to. */
uint8_t elem_base;
/** Index of composition data base model in its element. */
uint8_t idx_base;
/** Element that composition data extension model belongs to. */
uint8_t elem_ext;
/** Index of composition data extension model in its element. */
uint8_t idx_ext;
/** Type of relation; value in range 0x00-0xFE marks correspondence
* and equals to Correspondence ID; value 0xFF marks extension
*/
uint8_t type;
};
#ifdef CONFIG_BT_MESH_MODEL_EXTENSION_LIST_SIZE
#define MOD_REL_LIST_SIZE CONFIG_BT_MESH_MODEL_EXTENSION_LIST_SIZE
#else
#define MOD_REL_LIST_SIZE 0
#endif
/* List of all existing extension relations between models */
static struct mod_relation mod_rel_list[MOD_REL_LIST_SIZE];
#define MOD_REL_LIST_FOR_EACH(idx) \
for ((idx) = 0; \
(idx) < ARRAY_SIZE(mod_rel_list) && \
!(mod_rel_list[(idx)].elem_base == 0 && \
mod_rel_list[(idx)].idx_base == 0 && \
mod_rel_list[(idx)].elem_ext == 0 && \
mod_rel_list[(idx)].idx_ext == 0); \
(idx)++)
#define IS_MOD_BASE(mod, idx) \
(mod_rel_list[(idx)].elem_base == (mod)->elem_idx && \
mod_rel_list[(idx)].idx_base == (mod)->mod_idx && \
!(mod_rel_list[(idx)].elem_ext != (mod)->elem_idx && \
mod_rel_list[(idx)].idx_ext != (mod)->mod_idx))
#define IS_MOD_EXTENSION(mod, idx) \
(mod_rel_list[(idx)].elem_ext == (mod)->elem_idx && \
mod_rel_list[(idx)].idx_ext == (mod)->mod_idx && \
!(mod_rel_list[(idx)].elem_base != (mod)->elem_idx && \
mod_rel_list[(idx)].idx_base != (mod)->mod_idx))
#define RELATION_TYPE_EXT 0xFF
void bt_mesh_model_foreach(void (*func)(struct bt_mesh_model *mod,
struct bt_mesh_elem *elem,
bool vnd, bool primary,
void *user_data),
void *user_data)
{
int i, j;
for (i = 0; i < dev_comp->elem_count; i++) {
struct bt_mesh_elem *elem = &dev_comp->elem[i];
for (j = 0; j < elem->model_count; j++) {
struct bt_mesh_model *model = &elem->models[j];
func(model, elem, false, i == 0, user_data);
}
for (j = 0; j < elem->vnd_model_count; j++) {
struct bt_mesh_model *model = &elem->vnd_models[j];
func(model, elem, true, i == 0, user_data);
}
}
}
static size_t bt_mesh_comp_elem_size(const struct bt_mesh_elem *elem)
{
return (4 + (elem->model_count * 2U) + (elem->vnd_model_count * 4U));
}
static uint8_t *data_buf_add_u8_offset(struct net_buf_simple *buf,
uint8_t val, size_t *offset)
{
if (*offset >= 1) {
*offset -= 1;
return NULL;
}
return net_buf_simple_add_u8(buf, val);
}
static void data_buf_add_le16_offset(struct net_buf_simple *buf,
uint16_t val, size_t *offset)
{
if (*offset >= 2) {
*offset -= 2;
return;
} else if (*offset == 1) {
*offset -= 1;
net_buf_simple_add_u8(buf, (val >> 8));
} else {
net_buf_simple_add_le16(buf, val);
}
}
static void comp_add_model(struct bt_mesh_model *mod, struct bt_mesh_elem *elem,
bool vnd, void *user_data)
{
struct comp_foreach_model_arg *arg = user_data;
if (vnd) {
data_buf_add_le16_offset(arg->buf, mod->vnd.company, arg->offset);
data_buf_add_le16_offset(arg->buf, mod->vnd.id, arg->offset);
} else {
data_buf_add_le16_offset(arg->buf, mod->id, arg->offset);
}
}
#if defined(CONFIG_BT_MESH_LARGE_COMP_DATA_SRV)
static void data_buf_add_mem_offset(struct net_buf_simple *buf,
const void *mem, size_t len,
size_t *offset)
{
if (*offset >= len) {
*offset -= len;
return;
} else if (*offset > 0) {
net_buf_simple_add_mem(buf, ((uint8_t *)mem), (len - *offset));
} else {
net_buf_simple_add_mem(buf, mem, len);
}
}
static size_t metadata_model_size(struct bt_mesh_model *mod,
struct bt_mesh_elem *elem, bool vnd)
{
const struct bt_mesh_models_metadata_entry *entry;
size_t size = 0;
if (!mod->metadata) {
return size;
}
if (vnd) {
size += sizeof(mod->vnd.company);
size += sizeof(mod->vnd.id);
} else {
size += sizeof(mod->id);
}
size += sizeof(uint8_t);
for (entry = *mod->metadata; entry && entry->len; ++entry) {
size += sizeof(entry->len) + sizeof(entry->id) + entry->len;
}
return size;
}
size_t bt_mesh_metadata_page_0_size(void)
{
const struct bt_mesh_comp *comp;
size_t size = 0;
int i, j;
comp = bt_mesh_comp_get();
for (i = 0; i < dev_comp->elem_count; i++) {
struct bt_mesh_elem *elem = &dev_comp->elem[i];
size += sizeof(elem->model_count) +
sizeof(elem->vnd_model_count);
for (j = 0; j < elem->model_count; j++) {
struct bt_mesh_model *model = &elem->models[j];
size += metadata_model_size(model, elem, false);
}
for (j = 0; j < elem->vnd_model_count; j++) {
struct bt_mesh_model *model = &elem->vnd_models[j];
size += metadata_model_size(model, elem, true);
}
}
return size;
}
static int metadata_add_model(struct bt_mesh_model *mod,
struct bt_mesh_elem *elem, bool vnd,
void *user_data)
{
const struct bt_mesh_models_metadata_entry *entry;
struct comp_foreach_model_arg *arg = user_data;
struct net_buf_simple *buf = arg->buf;
size_t *offset = arg->offset;
size_t model_size;
uint8_t count = 0;
uint8_t *count_ptr;
model_size = metadata_model_size(mod, elem, vnd);
if (*offset >= model_size) {
*offset -= model_size;
return 0;
}
if (net_buf_simple_tailroom(buf) < (model_size + BT_MESH_MIC_SHORT)) {
LOG_DBG("Model metadata didn't fit in the buffer");
return -E2BIG;
}
comp_add_model(mod, elem, vnd, user_data);
count_ptr = data_buf_add_u8_offset(buf, 0, offset);
if (mod->metadata) {
for (entry = *mod->metadata; entry && entry->data != NULL; ++entry) {
data_buf_add_le16_offset(buf, entry->len, offset);
data_buf_add_le16_offset(buf, entry->id, offset);
data_buf_add_mem_offset(buf, entry->data, entry->len, offset);
count++;
}
}
if (count_ptr) {
*count_ptr = count;
}
return 0;
}
int bt_mesh_metadata_get_page_0(struct net_buf_simple *buf, size_t offset)
{
const struct bt_mesh_comp *comp;
struct comp_foreach_model_arg arg = {
.buf = buf,
.offset = &offset,
};
uint8_t *mod_count_ptr;
uint8_t *vnd_count_ptr;
int i, j, err;
comp = bt_mesh_comp_get();
for (i = 0; i < comp->elem_count; i++) {
struct bt_mesh_elem *elem = &dev_comp->elem[i];
/* Check that the buffer has available tailroom for metadata item counts */
if (net_buf_simple_tailroom(buf) < (((offset == 0) ? 2 : (offset == 1) ? 1 : 0)
+ BT_MESH_MIC_SHORT)) {
LOG_DBG("Model metadata didn't fit in the buffer");
return -E2BIG;
}
mod_count_ptr = data_buf_add_u8_offset(buf, 0, &offset);
vnd_count_ptr = data_buf_add_u8_offset(buf, 0, &offset);
for (j = 0; j < elem->model_count; j++) {
struct bt_mesh_model *model = &elem->models[j];
if (!model->metadata) {
continue;
}
err = metadata_add_model(model, elem, false, &arg);
if (err) {
return err;
}
if (mod_count_ptr) {
(*mod_count_ptr) += 1;
}
}
for (j = 0; j < elem->vnd_model_count; j++) {
struct bt_mesh_model *model = &elem->vnd_models[j];
if (!model->metadata) {
continue;
}
err = metadata_add_model(model, elem, true, &arg);
if (err) {
return err;
}
if (vnd_count_ptr) {
(*vnd_count_ptr) += 1;
}
}
}
return 0;
}
#endif
size_t bt_mesh_comp_page_0_size(void)
{
const struct bt_mesh_comp *comp;
const struct bt_mesh_elem *elem;
size_t size = 10;
int i;
comp = bt_mesh_comp_get();
for (i = 0; i < comp->elem_count; i++) {
elem = &comp->elem[i];
size += bt_mesh_comp_elem_size(elem);
}
return size;
}
static int comp_add_elem(struct net_buf_simple *buf, struct bt_mesh_elem *elem,
size_t *offset)
{
struct comp_foreach_model_arg arg = {
.buf = buf,
.offset = offset,
};
const size_t elem_size = bt_mesh_comp_elem_size(elem);
int i;
if (*offset >= elem_size) {
*offset -= elem_size;
return 0;
}
if (net_buf_simple_tailroom(buf) < (elem_size + BT_MESH_MIC_SHORT)) {
if (IS_ENABLED(CONFIG_BT_MESH_LARGE_COMP_DATA_SRV)) {
/* Mesh Profile 1.1 Section 4.4.1.2.2:
* If the complete list of models does not fit in the Data field,
* the element shall not be reported.
*/
LOG_DBG("Element 0x%04x didn't fit in the Data field",
elem->addr);
return 0;
}
LOG_ERR("Too large device composition");
return -E2BIG;
}
data_buf_add_le16_offset(buf, elem->loc, offset);
data_buf_add_u8_offset(buf, elem->model_count, offset);
data_buf_add_u8_offset(buf, elem->vnd_model_count, offset);
for (i = 0; i < elem->model_count; i++) {
struct bt_mesh_model *model = &elem->models[i];
comp_add_model(model, elem, false, &arg);
}
for (i = 0; i < elem->vnd_model_count; i++) {
struct bt_mesh_model *model = &elem->vnd_models[i];
comp_add_model(model, elem, true, &arg);
}
return 0;
}
int bt_mesh_comp_data_get_page_0(struct net_buf_simple *buf, size_t offset)
{
uint16_t feat = 0U;
const struct bt_mesh_comp *comp;
int i;
comp = bt_mesh_comp_get();
if (IS_ENABLED(CONFIG_BT_MESH_RELAY)) {
feat |= BT_MESH_FEAT_RELAY;
}
if (IS_ENABLED(CONFIG_BT_MESH_GATT_PROXY)) {
feat |= BT_MESH_FEAT_PROXY;
}
if (IS_ENABLED(CONFIG_BT_MESH_FRIEND)) {
feat |= BT_MESH_FEAT_FRIEND;
}
if (IS_ENABLED(CONFIG_BT_MESH_LOW_POWER)) {
feat |= BT_MESH_FEAT_LOW_POWER;
}
data_buf_add_le16_offset(buf, comp->cid, &offset);
data_buf_add_le16_offset(buf, comp->pid, &offset);
data_buf_add_le16_offset(buf, comp->vid, &offset);
data_buf_add_le16_offset(buf, CONFIG_BT_MESH_CRPL, &offset);
data_buf_add_le16_offset(buf, feat, &offset);
for (i = 0; i < comp->elem_count; i++) {
int err;
err = comp_add_elem(buf, &comp->elem[i], &offset);
if (err) {
return err;
}
}
return 0;
}
static uint8_t count_mod_ext(struct bt_mesh_model *mod, uint8_t *max_offset)
{
int i;
uint8_t extensions = 0;
int8_t offset, offset_record = 0;
MOD_REL_LIST_FOR_EACH(i) {
if (IS_MOD_EXTENSION(mod, i) &&
mod_rel_list[i].type == RELATION_TYPE_EXT) {
extensions++;
offset = mod_rel_list[i].elem_ext -
mod_rel_list[i].elem_base;
if (abs(offset) > abs(offset_record)) {
offset_record = offset;
}
}
}
memcpy(max_offset, &offset_record, sizeof(uint8_t));
return extensions;
}
static bool is_cor_present(struct bt_mesh_model *mod, uint8_t *cor_id)
{
int i;
MOD_REL_LIST_FOR_EACH(i) {
if ((IS_MOD_BASE(mod, i) ||
IS_MOD_EXTENSION(mod, i)) &&
mod_rel_list[i].type < RELATION_TYPE_EXT) {
memcpy(cor_id, &mod_rel_list[i].type, sizeof(uint8_t));
return true;
}
}
return false;
}
static void prep_model_item_header(struct bt_mesh_model *mod, uint8_t *cor_id,
uint8_t *mod_cnt, struct net_buf_simple *buf)
{
uint8_t ext_mod_cnt;
bool cor_present;
uint8_t mod_elem_info = 0;
int8_t max_offset;
ext_mod_cnt = count_mod_ext(mod, &max_offset);
cor_present = is_cor_present(mod, cor_id);
mod_elem_info = ext_mod_cnt << 2;
if (ext_mod_cnt > 31 ||
max_offset > 3 ||
max_offset < -4) {
mod_elem_info |= BIT(1);
}
if (cor_present) {
mod_elem_info |= BIT(0);
}
net_buf_simple_add_u8(buf, mod_elem_info);
if (cor_present) {
net_buf_simple_add_u8(buf, *cor_id);
}
memset(mod_cnt, ext_mod_cnt, sizeof(uint8_t));
}
static void add_items_to_page(struct net_buf_simple *buf, struct bt_mesh_model *mod,
uint8_t ext_mod_cnt)
{
int i, offset;
uint8_t mod_idx;
MOD_REL_LIST_FOR_EACH(i) {
if (IS_MOD_EXTENSION(mod, i)) {
offset = mod->elem_idx - mod_rel_list[i].elem_base;
mod_idx = mod_rel_list[i].idx_base;
if (ext_mod_cnt < 32 &&
offset < 4 &&
offset > -5) {
/* short format */
if (offset < 0) {
offset += 8;
}
offset |= mod_idx << 3;
net_buf_simple_add_u8(buf, offset);
} else {
/* long format */
if (offset < 0) {
offset += 256;
}
net_buf_simple_add_u8(buf, offset);
net_buf_simple_add_u8(buf, mod_idx);
}
}
}
}
int bt_mesh_comp_data_get_page_1(struct net_buf_simple *buf)
{
const struct bt_mesh_comp *comp;
uint8_t cor_id = 0;
uint8_t ext_mod_cnt = 0;
int i, j;
comp = bt_mesh_comp_get();
for (i = 0; i < comp->elem_count; i++) {
net_buf_simple_add_u8(buf, comp->elem[i].model_count);
net_buf_simple_add_u8(buf, comp->elem[i].vnd_model_count);
for (j = 0; j < comp->elem[i].model_count; j++) {
prep_model_item_header(&comp->elem[i].models[j],
&cor_id, &ext_mod_cnt, buf);
if (ext_mod_cnt != 0) {
add_items_to_page(buf,
&comp->elem[i].models[j],
ext_mod_cnt);
}
}
for (j = 0; j < comp->elem[i].vnd_model_count; j++) {
prep_model_item_header(&comp->elem[i].vnd_models[j],
&cor_id, &ext_mod_cnt, buf);
if (ext_mod_cnt != 0) {
add_items_to_page(buf,
&comp->elem[i].vnd_models[j],
ext_mod_cnt);
}
}
}
return 0;
}
int32_t bt_mesh_model_pub_period_get(struct bt_mesh_model *mod)
{
int32_t period;
if (!mod->pub) {
return 0;
}
switch (mod->pub->period >> 6) {
case 0x00:
/* 1 step is 100 ms */
period = (mod->pub->period & BIT_MASK(6)) * 100U;
break;
case 0x01:
/* 1 step is 1 second */
period = (mod->pub->period & BIT_MASK(6)) * MSEC_PER_SEC;
break;
case 0x02:
/* 1 step is 10 seconds */
period = (mod->pub->period & BIT_MASK(6)) * 10U * MSEC_PER_SEC;
break;
case 0x03:
/* 1 step is 10 minutes */
period = (mod->pub->period & BIT_MASK(6)) * 600U * MSEC_PER_SEC;
break;
default:
CODE_UNREACHABLE;
}
if (mod->pub->fast_period) {
if (!period) {
return 0;
}
return MAX(period >> mod->pub->period_div, 100);
} else {
return period;
}
}
static int32_t next_period(struct bt_mesh_model *mod)
{
struct bt_mesh_model_pub *pub = mod->pub;
uint32_t period = 0;
uint32_t elapsed;
elapsed = k_uptime_get_32() - pub->period_start;
LOG_DBG("Publishing took %ums", elapsed);
if (mod->pub->count) {
/* If a message is to be retransmitted, period should include time since the first
* publication until the last publication.
*/
period = BT_MESH_PUB_TRANSMIT_INT(mod->pub->retransmit);
period *= BT_MESH_PUB_MSG_NUM(mod->pub);
if (period && elapsed >= period) {
LOG_WRN("Retransmission interval is too short");
/* Return smallest positive number since 0 means disabled */
return 1;
}
}
if (!period) {
period = bt_mesh_model_pub_period_get(mod);
if (!period) {
return 0;
}
}
if (elapsed >= period) {
LOG_WRN("Publication sending took longer than the period");
/* Return smallest positive number since 0 means disabled */
return 1;
}
return period - elapsed;
}
static void publish_sent(int err, void *user_data)
{
struct bt_mesh_model *mod = user_data;
int32_t delay;
LOG_DBG("err %d, time %u", err, k_uptime_get_32());
delay = next_period(mod);
if (delay) {
LOG_DBG("Publishing next time in %dms", delay);
/* Using schedule() in case the application has already called
* bt_mesh_publish, and a publication is pending.
*/
k_work_schedule(&mod->pub->timer, K_MSEC(delay));
}
}
static void publish_start(uint16_t duration, int err, void *user_data)
{
if (err) {
LOG_ERR("Failed to publish: err %d", err);
publish_sent(err, user_data);
return;
}
}
static const struct bt_mesh_send_cb pub_sent_cb = {
.start = publish_start,
.end = publish_sent,
};
static int publish_transmit(struct bt_mesh_model *mod)
{
NET_BUF_SIMPLE_DEFINE(sdu, BT_MESH_TX_SDU_MAX);
struct bt_mesh_model_pub *pub = mod->pub;
struct bt_mesh_msg_ctx ctx = BT_MESH_MSG_CTX_INIT_PUB(pub);
struct bt_mesh_net_tx tx = {
.ctx = &ctx,
.src = bt_mesh_model_elem(mod)->addr,
.friend_cred = pub->cred,
};
net_buf_simple_add_mem(&sdu, pub->msg->data, pub->msg->len);
return bt_mesh_trans_send(&tx, &sdu, &pub_sent_cb, mod);
}
static int pub_period_start(struct bt_mesh_model_pub *pub)
{
int err;
pub->count = BT_MESH_PUB_TRANSMIT_COUNT(pub->retransmit);
if (!pub->update) {
return 0;
}
err = pub->update(pub->mod);
pub->period_start = k_uptime_get_32();
if (err) {
/* Skip this publish attempt. */
LOG_DBG("Update failed, skipping publish (err: %d)", err);
pub->count = 0;
publish_sent(err, pub->mod);
return err;
}
return 0;
}
static void mod_publish(struct k_work *work)
{
struct k_work_delayable *dwork = k_work_delayable_from_work(work);
struct bt_mesh_model_pub *pub = CONTAINER_OF(dwork,
struct bt_mesh_model_pub,
timer);
int err;
if (pub->addr == BT_MESH_ADDR_UNASSIGNED ||
atomic_test_bit(bt_mesh.flags, BT_MESH_SUSPENDED)) {
/* Publication is no longer active, but the cancellation of the
* delayed work failed. Abandon recurring timer.
*/
return;
}
LOG_DBG("%u", k_uptime_get_32());
if (pub->count) {
pub->count--;
if (pub->retr_update && pub->update &&
bt_mesh_model_pub_is_retransmission(pub->mod)) {
err = pub->update(pub->mod);
if (err) {
publish_sent(err, pub->mod);
return;
}
}
} else {
/* First publication in this period */
err = pub_period_start(pub);
if (err) {
return;
}
}
err = publish_transmit(pub->mod);
if (err) {
LOG_ERR("Failed to publish (err %d)", err);
publish_sent(err, pub->mod);
}
}
struct bt_mesh_elem *bt_mesh_model_elem(struct bt_mesh_model *mod)
{
return &dev_comp->elem[mod->elem_idx];
}
struct bt_mesh_model *bt_mesh_model_get(bool vnd, uint8_t elem_idx, uint8_t mod_idx)
{
struct bt_mesh_elem *elem;
if (elem_idx >= dev_comp->elem_count) {
LOG_ERR("Invalid element index %u", elem_idx);
return NULL;
}
elem = &dev_comp->elem[elem_idx];
if (vnd) {
if (mod_idx >= elem->vnd_model_count) {
LOG_ERR("Invalid vendor model index %u", mod_idx);
return NULL;
}
return &elem->vnd_models[mod_idx];
} else {
if (mod_idx >= elem->model_count) {
LOG_ERR("Invalid SIG model index %u", mod_idx);
return NULL;
}
return &elem->models[mod_idx];
}
}
#if defined(CONFIG_BT_MESH_MODEL_VND_MSG_CID_FORCE)
static int bt_mesh_vnd_mod_msg_cid_check(struct bt_mesh_model *mod)
{
uint16_t cid;
const struct bt_mesh_model_op *op;
for (op = mod->op; op->func; op++) {
cid = (uint16_t)(op->opcode & 0xffff);
if (cid == mod->vnd.company) {
continue;
}
LOG_ERR("Invalid vendor model(company:0x%04x"
" id:0x%04x) message opcode 0x%08x",
mod->vnd.company, mod->vnd.id, op->opcode);
return -EINVAL;
}
return 0;
}
#endif
static void mod_init(struct bt_mesh_model *mod, struct bt_mesh_elem *elem,
bool vnd, bool primary, void *user_data)
{
int i;
int *err = user_data;
if (*err) {
return;
}
if (mod->pub) {
mod->pub->mod = mod;
k_work_init_delayable(&mod->pub->timer, mod_publish);
}
for (i = 0; i < mod->keys_cnt; i++) {
mod->keys[i] = BT_MESH_KEY_UNUSED;
}
mod->elem_idx = elem - dev_comp->elem;
if (vnd) {
mod->mod_idx = mod - elem->vnd_models;
if (IS_ENABLED(CONFIG_BT_MESH_MODEL_VND_MSG_CID_FORCE)) {
*err = bt_mesh_vnd_mod_msg_cid_check(mod);
if (*err) {
return;
}
}
} else {
mod->mod_idx = mod - elem->models;
}
if (mod->cb && mod->cb->init) {
*err = mod->cb->init(mod);
}
}
int bt_mesh_comp_register(const struct bt_mesh_comp *comp)
{
int err;
/* There must be at least one element */
if (!comp || !comp->elem_count) {
return -EINVAL;
}
dev_comp = comp;
err = 0;
if (IS_ENABLED(CONFIG_BT_MESH_COMP_PAGE_1)) {
memset(mod_rel_list, 0, sizeof(mod_rel_list));
}
bt_mesh_model_foreach(mod_init, &err);
if (IS_ENABLED(CONFIG_BT_MESH_COMP_PAGE_1)) {
int i;
MOD_REL_LIST_FOR_EACH(i) {
LOG_DBG("registered %s",
mod_rel_list[i].type < RELATION_TYPE_EXT ?
"correspondence" : "extension");
LOG_DBG("\tbase: elem %u idx %u",
mod_rel_list[i].elem_base,
mod_rel_list[i].idx_base);
LOG_DBG("\text: elem %u idx %u",
mod_rel_list[i].elem_ext,
mod_rel_list[i].idx_ext);
}
if (i < MOD_REL_LIST_SIZE) {
LOG_WRN("Unused space in relation list: %d",
MOD_REL_LIST_SIZE - i);
}
}
return err;
}
void bt_mesh_comp_provision(uint16_t addr)
{
int i;
dev_primary_addr = addr;
LOG_DBG("addr 0x%04x elem_count %zu", addr, dev_comp->elem_count);
for (i = 0; i < dev_comp->elem_count; i++) {
struct bt_mesh_elem *elem = &dev_comp->elem[i];
elem->addr = addr++;
LOG_DBG("addr 0x%04x mod_count %u vnd_mod_count %u", elem->addr, elem->model_count,
elem->vnd_model_count);
}
}
void bt_mesh_comp_unprovision(void)
{
LOG_DBG("");
dev_primary_addr = BT_MESH_ADDR_UNASSIGNED;
}
uint16_t bt_mesh_primary_addr(void)
{
return dev_primary_addr;
}
static uint16_t *model_group_get(struct bt_mesh_model *mod, uint16_t addr)
{
int i;
for (i = 0; i < mod->groups_cnt; i++) {
if (mod->groups[i] == addr) {
return &mod->groups[i];
}
}
return NULL;
}
struct find_group_visitor_ctx {
uint16_t *entry;
struct bt_mesh_model *mod;
uint16_t addr;
};
static enum bt_mesh_walk find_group_mod_visitor(struct bt_mesh_model *mod, void *user_data)
{
struct find_group_visitor_ctx *ctx = user_data;
if (mod->elem_idx != ctx->mod->elem_idx) {
return BT_MESH_WALK_CONTINUE;
}
ctx->entry = model_group_get(mod, ctx->addr);
if (ctx->entry) {
ctx->mod = mod;
return BT_MESH_WALK_STOP;
}
return BT_MESH_WALK_CONTINUE;
}
uint16_t *bt_mesh_model_find_group(struct bt_mesh_model **mod, uint16_t addr)
{
struct find_group_visitor_ctx ctx = {
.mod = *mod,
.entry = NULL,
.addr = addr,
};
bt_mesh_model_extensions_walk(*mod, find_group_mod_visitor, &ctx);
*mod = ctx.mod;
return ctx.entry;
}
static struct bt_mesh_model *bt_mesh_elem_find_group(struct bt_mesh_elem *elem,
uint16_t group_addr)
{
struct bt_mesh_model *model;
uint16_t *match;
int i;
for (i = 0; i < elem->model_count; i++) {
model = &elem->models[i];
match = model_group_get(model, group_addr);
if (match) {
return model;
}
}
for (i = 0; i < elem->vnd_model_count; i++) {
model = &elem->vnd_models[i];
match = model_group_get(model, group_addr);
if (match) {
return model;
}
}
return NULL;
}
struct bt_mesh_elem *bt_mesh_elem_find(uint16_t addr)
{
uint16_t index;
if (!BT_MESH_ADDR_IS_UNICAST(addr)) {
return NULL;
}
index = addr - dev_comp->elem[0].addr;
if (index >= dev_comp->elem_count) {
return NULL;
}
return &dev_comp->elem[index];
}
bool bt_mesh_has_addr(uint16_t addr)
{
uint16_t index;
if (BT_MESH_ADDR_IS_UNICAST(addr)) {
return bt_mesh_elem_find(addr) != NULL;
}
if (IS_ENABLED(CONFIG_BT_MESH_ACCESS_LAYER_MSG) && msg_cb) {
return true;
}
for (index = 0; index < dev_comp->elem_count; index++) {
struct bt_mesh_elem *elem = &dev_comp->elem[index];
if (bt_mesh_elem_find_group(elem, addr)) {
return true;
}
}
return false;
}
#if defined(CONFIG_BT_MESH_ACCESS_LAYER_MSG)
void bt_mesh_msg_cb_set(void (*cb)(uint32_t opcode, struct bt_mesh_msg_ctx *ctx,
struct net_buf_simple *buf))
{
msg_cb = cb;
}
#endif
int bt_mesh_access_send(struct bt_mesh_msg_ctx *ctx, struct net_buf_simple *buf, uint16_t src_addr,
const struct bt_mesh_send_cb *cb, void *cb_data)
{
struct bt_mesh_net_tx tx = {
.ctx = ctx,
.src = src_addr,
};
LOG_DBG("net_idx 0x%04x app_idx 0x%04x dst 0x%04x", tx.ctx->net_idx, tx.ctx->app_idx,
tx.ctx->addr);
LOG_DBG("len %u: %s", buf->len, bt_hex(buf->data, buf->len));
if (!bt_mesh_is_provisioned()) {
LOG_ERR("Local node is not yet provisioned");
return -EAGAIN;
}
return bt_mesh_trans_send(&tx, buf, cb, cb_data);
}
uint8_t bt_mesh_elem_count(void)
{
return dev_comp->elem_count;
}
bool bt_mesh_model_has_key(struct bt_mesh_model *mod, uint16_t key)
{
int i;
for (i = 0; i < mod->keys_cnt; i++) {
if (mod->keys[i] == key ||
(mod->keys[i] == BT_MESH_KEY_DEV_ANY &&
BT_MESH_IS_DEV_KEY(key))) {
return true;
}
}
return false;
}
static bool model_has_dst(struct bt_mesh_model *mod, uint16_t dst)
{
if (BT_MESH_ADDR_IS_UNICAST(dst)) {
return (dev_comp->elem[mod->elem_idx].addr == dst);
} else if (BT_MESH_ADDR_IS_GROUP(dst) || BT_MESH_ADDR_IS_VIRTUAL(dst) ||
(BT_MESH_ADDR_IS_FIXED_GROUP(dst) && mod->elem_idx != 0)) {
return !!bt_mesh_model_find_group(&mod, dst);
}
/* If a message with a fixed group address is sent to the access layer,
* the lower layers have already confirmed that we are subscribing to
* it. All models on the primary element should receive the message.
*/
return mod->elem_idx == 0;
}
static const struct bt_mesh_model_op *find_op(struct bt_mesh_elem *elem,
uint32_t opcode, struct bt_mesh_model **model)
{
uint8_t i;
uint8_t count;
/* This value shall not be used in shipping end products. */
uint32_t cid = UINT32_MAX;
struct bt_mesh_model *models;
/* SIG models cannot contain 3-byte (vendor) OpCodes, and
* vendor models cannot contain SIG (1- or 2-byte) OpCodes, so
* we only need to do the lookup in one of the model lists.
*/
if (BT_MESH_MODEL_OP_LEN(opcode) < 3) {
models = elem->models;
count = elem->model_count;
} else {
models = elem->vnd_models;
count = elem->vnd_model_count;
cid = (uint16_t)(opcode & 0xffff);
}
for (i = 0U; i < count; i++) {
const struct bt_mesh_model_op *op;
if (IS_ENABLED(CONFIG_BT_MESH_MODEL_VND_MSG_CID_FORCE) &&
cid != UINT32_MAX &&
cid != models[i].vnd.company) {
continue;
}
*model = &models[i];
for (op = (*model)->op; op->func; op++) {
if (op->opcode == opcode) {
return op;
}
}
}
*model = NULL;
return NULL;
}
static int get_opcode(struct net_buf_simple *buf, uint32_t *opcode)
{
switch (buf->data[0] >> 6) {
case 0x00:
case 0x01:
if (buf->data[0] == 0x7f) {
LOG_ERR("Ignoring RFU OpCode");
return -EINVAL;
}
*opcode = net_buf_simple_pull_u8(buf);
return 0;
case 0x02:
if (buf->len < 2) {
LOG_ERR("Too short payload for 2-octet OpCode");
return -EINVAL;
}
*opcode = net_buf_simple_pull_be16(buf);
return 0;
case 0x03:
if (buf->len < 3) {
LOG_ERR("Too short payload for 3-octet OpCode");
return -EINVAL;
}
*opcode = net_buf_simple_pull_u8(buf) << 16;
/* Using LE for the CID since the model layer is defined as
* little-endian in the mesh spec and using BT_MESH_MODEL_OP_3
* will declare the opcode in this way.
*/
*opcode |= net_buf_simple_pull_le16(buf);
return 0;
}
CODE_UNREACHABLE;
}
static int element_model_recv(struct bt_mesh_msg_ctx *ctx,
struct net_buf_simple *buf, uint16_t addr,
struct bt_mesh_elem *elem, uint32_t opcode)
{
const struct bt_mesh_model_op *op;
struct bt_mesh_model *model;
struct net_buf_simple_state state;
int err;
op = find_op(elem, opcode, &model);
if (!op) {
LOG_ERR("No OpCode 0x%08x for elem 0x%02x", opcode, elem->addr);
return ACCESS_STATUS_WRONG_OPCODE;
}
if (!bt_mesh_model_has_key(model, ctx->app_idx)) {
LOG_ERR("Wrong key");
return ACCESS_STATUS_WRONG_KEY;
}
if (!model_has_dst(model, addr)) {
LOG_ERR("Invalid address 0x%02x", addr);
return ACCESS_STATUS_INVALID_ADDRESS;
}
if ((op->len >= 0) && (buf->len < (size_t)op->len)) {
LOG_ERR("Too short message for OpCode 0x%08x", opcode);
return ACCESS_STATUS_MESSAGE_NOT_UNDERSTOOD;
} else if ((op->len < 0) && (buf->len != (size_t)(-op->len))) {
LOG_ERR("Invalid message size for OpCode 0x%08x", opcode);
return ACCESS_STATUS_MESSAGE_NOT_UNDERSTOOD;
}
net_buf_simple_save(buf, &state);
err = op->func(model, ctx, buf);
net_buf_simple_restore(buf, &state);
if (err) {
return ACCESS_STATUS_MESSAGE_NOT_UNDERSTOOD;
}
return ACCESS_STATUS_SUCCESS;
}
int bt_mesh_model_recv(struct bt_mesh_msg_ctx *ctx, struct net_buf_simple *buf)
{
int err = ACCESS_STATUS_SUCCESS;
uint32_t opcode;
uint16_t index;
LOG_DBG("app_idx 0x%04x src 0x%04x dst 0x%04x", ctx->app_idx, ctx->addr,
ctx->recv_dst);
LOG_DBG("len %u: %s", buf->len, bt_hex(buf->data, buf->len));
if (IS_ENABLED(CONFIG_BT_TESTING)) {
bt_test_mesh_model_recv(ctx->addr, ctx->recv_dst, buf->data,
buf->len);
}
if (get_opcode(buf, &opcode) < 0) {
LOG_WRN("Unable to decode OpCode");
return ACCESS_STATUS_WRONG_OPCODE;
}
LOG_DBG("OpCode 0x%08x", opcode);
if (BT_MESH_ADDR_IS_UNICAST(ctx->recv_dst)) {
index = ctx->recv_dst - dev_comp->elem[0].addr;
if (index >= dev_comp->elem_count) {
LOG_ERR("Invalid address 0x%02x", ctx->recv_dst);
err = ACCESS_STATUS_INVALID_ADDRESS;
} else {
struct bt_mesh_elem *elem = &dev_comp->elem[index];
err = element_model_recv(ctx, buf, ctx->recv_dst, elem, opcode);
}
} else {
for (index = 0; index < dev_comp->elem_count; index++) {
struct bt_mesh_elem *elem = &dev_comp->elem[index];
(void)element_model_recv(ctx, buf, ctx->recv_dst, elem, opcode);
}
}
if (IS_ENABLED(CONFIG_BT_MESH_ACCESS_LAYER_MSG) && msg_cb) {
msg_cb(opcode, ctx, buf);
}
return err;
}
int bt_mesh_model_send(struct bt_mesh_model *model, struct bt_mesh_msg_ctx *ctx,
struct net_buf_simple *msg,
const struct bt_mesh_send_cb *cb, void *cb_data)
{
if (IS_ENABLED(CONFIG_BT_MESH_OP_AGG) && bt_mesh_op_agg_accept(ctx)) {
return bt_mesh_op_agg_send(model, ctx, msg, cb);
}
if (!bt_mesh_model_has_key(model, ctx->app_idx)) {
LOG_ERR("Model not bound to AppKey 0x%04x", ctx->app_idx);
return -EINVAL;
}
return bt_mesh_access_send(ctx, msg, bt_mesh_model_elem(model)->addr, cb, cb_data);
}
int bt_mesh_model_publish(struct bt_mesh_model *model)
{
struct bt_mesh_model_pub *pub = model->pub;
if (!pub) {
return -ENOTSUP;
}
LOG_DBG("");
if (pub->addr == BT_MESH_ADDR_UNASSIGNED) {
return -EADDRNOTAVAIL;
}
if (!pub->msg || !pub->msg->len) {
LOG_ERR("No publication message");
return -EINVAL;
}
if (pub->msg->len + BT_MESH_MIC_SHORT > BT_MESH_TX_SDU_MAX) {
LOG_ERR("Message does not fit maximum SDU size");
return -EMSGSIZE;
}
if (pub->count) {
LOG_WRN("Clearing publish retransmit timer");
}
/* Account for initial transmission */
pub->count = BT_MESH_PUB_MSG_TOTAL(pub);
pub->period_start = k_uptime_get_32();
LOG_DBG("Publish Retransmit Count %u Interval %ums", pub->count,
BT_MESH_PUB_TRANSMIT_INT(pub->retransmit));
k_work_reschedule(&pub->timer, K_NO_WAIT);
return 0;
}
struct bt_mesh_model *bt_mesh_model_find_vnd(const struct bt_mesh_elem *elem,
uint16_t company, uint16_t id)
{
uint8_t i;
for (i = 0U; i < elem->vnd_model_count; i++) {
if (elem->vnd_models[i].vnd.company == company &&
elem->vnd_models[i].vnd.id == id) {
return &elem->vnd_models[i];
}
}
return NULL;
}
struct bt_mesh_model *bt_mesh_model_find(const struct bt_mesh_elem *elem,
uint16_t id)
{
uint8_t i;
for (i = 0U; i < elem->model_count; i++) {
if (elem->models[i].id == id) {
return &elem->models[i];
}
}
return NULL;
}
const struct bt_mesh_comp *bt_mesh_comp_get(void)
{
return dev_comp;
}
void bt_mesh_model_extensions_walk(struct bt_mesh_model *model,
enum bt_mesh_walk (*cb)(struct bt_mesh_model *mod,
void *user_data),
void *user_data)
{
#ifndef CONFIG_BT_MESH_MODEL_EXTENSIONS
(void)cb(model, user_data);
return;
#else
struct bt_mesh_model *it;
if (cb(model, user_data) == BT_MESH_WALK_STOP || !model->next) {
return;
}
/* List is circular. Step through all models until we reach the start: */
for (it = model->next; it != model; it = it->next) {
if (cb(it, user_data) == BT_MESH_WALK_STOP) {
return;
}
}
#endif
}
#ifdef CONFIG_BT_MESH_MODEL_EXTENSIONS
static int mod_rel_register(struct bt_mesh_model *base,
struct bt_mesh_model *ext,
uint8_t type)
{
LOG_DBG("");
struct mod_relation extension = {
base->elem_idx,
base->mod_idx,
ext->elem_idx,
ext->mod_idx,
type,
};
int i;
for (i = 0; i < ARRAY_SIZE(mod_rel_list); i++) {
if (mod_rel_list[i].elem_base == 0 &&
mod_rel_list[i].idx_base == 0 &&
mod_rel_list[i].elem_ext == 0 &&
mod_rel_list[i].idx_ext == 0) {
memcpy(&mod_rel_list[i], &extension,
sizeof(extension));
return 0;
}
}
LOG_ERR("Failed to extend");
return -ENOMEM;
}
int bt_mesh_model_extend(struct bt_mesh_model *extending_mod, struct bt_mesh_model *base_mod)
{
struct bt_mesh_model *a = extending_mod;
struct bt_mesh_model *b = base_mod;
struct bt_mesh_model *a_next = a->next;
struct bt_mesh_model *b_next = b->next;
struct bt_mesh_model *it;
base_mod->flags |= BT_MESH_MOD_EXTENDED;
if (a == b) {
return 0;
}
/* Check if a's list contains b */
for (it = a; (it != NULL) && (it->next != a); it = it->next) {
if (it == b) {
return 0;
}
}
/* Merge lists */
if (a_next) {
b->next = a_next;
} else {
b->next = a;
}
if (b_next) {
a->next = b_next;
} else {
a->next = b;
}
if (IS_ENABLED(CONFIG_BT_MESH_COMP_PAGE_1)) {
return mod_rel_register(base_mod, extending_mod, RELATION_TYPE_EXT);
}
return 0;
}
int bt_mesh_model_correspond(struct bt_mesh_model *corresponding_mod,
struct bt_mesh_model *base_mod)
{
int i, err;
uint8_t cor_id = 0;
if (!IS_ENABLED(CONFIG_BT_MESH_COMP_PAGE_1)) {
return -ENOTSUP;
}
MOD_REL_LIST_FOR_EACH(i) {
if (mod_rel_list[i].type < RELATION_TYPE_EXT &&
mod_rel_list[i].type > cor_id) {
cor_id = mod_rel_list[i].type;
}
if ((IS_MOD_BASE(base_mod, i) ||
IS_MOD_EXTENSION(base_mod, i) ||
IS_MOD_BASE(corresponding_mod, i) ||
IS_MOD_EXTENSION(corresponding_mod, i)) &&
mod_rel_list[i].type < RELATION_TYPE_EXT) {
return mod_rel_register(base_mod, corresponding_mod, mod_rel_list[i].type);
}
}
err = mod_rel_register(base_mod, corresponding_mod, cor_id);
if (err) {
return err;
}
return 0;
}
#endif /* CONFIG_BT_MESH_MODEL_EXTENSIONS */
bool bt_mesh_model_is_extended(struct bt_mesh_model *model)
{
return model->flags & BT_MESH_MOD_EXTENDED;
}
static int mod_set_bind(struct bt_mesh_model *mod, size_t len_rd,
settings_read_cb read_cb, void *cb_arg)
{
ssize_t len;
int i;
/* Start with empty array regardless of cleared or set value */
for (i = 0; i < mod->keys_cnt; i++) {
mod->keys[i] = BT_MESH_KEY_UNUSED;
}
if (len_rd == 0) {
LOG_DBG("Cleared bindings for model");
return 0;
}
len = read_cb(cb_arg, mod->keys, mod->keys_cnt * sizeof(mod->keys[0]));
if (len < 0) {
LOG_ERR("Failed to read value (err %zd)", len);
return len;
}
LOG_HEXDUMP_DBG(mod->keys, len, "val");
LOG_DBG("Decoded %zu bound keys for model", len / sizeof(mod->keys[0]));
return 0;
}
static int mod_set_sub(struct bt_mesh_model *mod, size_t len_rd,
settings_read_cb read_cb, void *cb_arg)
{
size_t size = mod->groups_cnt * sizeof(mod->groups[0]);
ssize_t len;
/* Start with empty array regardless of cleared or set value */
(void)memset(mod->groups, 0, size);
if (len_rd == 0) {
LOG_DBG("Cleared subscriptions for model");
return 0;
}
len = read_cb(cb_arg, mod->groups, size);
if (len < 0) {
LOG_ERR("Failed to read value (err %zd)", len);
return len;
}
LOG_HEXDUMP_DBG(mod->groups, len, "val");
LOG_DBG("Decoded %zu subscribed group addresses for model", len / sizeof(mod->groups[0]));
return 0;
}
static int mod_set_pub(struct bt_mesh_model *mod, size_t len_rd,
settings_read_cb read_cb, void *cb_arg)
{
struct mod_pub_val pub;
int err;
if (!mod->pub) {
LOG_WRN("Model has no publication context!");
return -EINVAL;
}
if (len_rd == 0) {
mod->pub->addr = BT_MESH_ADDR_UNASSIGNED;
mod->pub->key = 0U;
mod->pub->cred = 0U;
mod->pub->ttl = 0U;
mod->pub->period = 0U;
mod->pub->retransmit = 0U;
mod->pub->count = 0U;
LOG_DBG("Cleared publication for model");
return 0;
}
if (!IS_ENABLED(CONFIG_BT_SETTINGS)) {
return 0;
}
err = bt_mesh_settings_set(read_cb, cb_arg, &pub, sizeof(pub));
if (err) {
LOG_ERR("Failed to set \'model-pub\'");
return err;
}
mod->pub->addr = pub.addr;
mod->pub->key = pub.key;
mod->pub->cred = pub.cred;
mod->pub->ttl = pub.ttl;
mod->pub->period = pub.period;
mod->pub->retransmit = pub.retransmit;
mod->pub->period_div = pub.period_div;
mod->pub->count = 0U;
LOG_DBG("Restored model publication, dst 0x%04x app_idx 0x%03x", pub.addr, pub.key);
return 0;
}
static int mod_data_set(struct bt_mesh_model *mod,
const char *name, size_t len_rd,
settings_read_cb read_cb, void *cb_arg)
{
const char *next;
settings_name_next(name, &next);
if (mod->cb && mod->cb->settings_set) {
return mod->cb->settings_set(mod, next, len_rd,
read_cb, cb_arg);
}
return 0;
}
static int mod_set(bool vnd, const char *name, size_t len_rd,
settings_read_cb read_cb, void *cb_arg)
{
struct bt_mesh_model *mod;
uint8_t elem_idx, mod_idx;
uint16_t mod_key;
int len;
const char *next;
if (!name) {
LOG_ERR("Insufficient number of arguments");
return -ENOENT;
}
mod_key = strtol(name, NULL, 16);
elem_idx = mod_key >> 8;
mod_idx = mod_key;
LOG_DBG("Decoded mod_key 0x%04x as elem_idx %u mod_idx %u", mod_key, elem_idx, mod_idx);
mod = bt_mesh_model_get(vnd, elem_idx, mod_idx);
if (!mod) {
LOG_ERR("Failed to get model for elem_idx %u mod_idx %u", elem_idx, mod_idx);
return -ENOENT;
}
len = settings_name_next(name, &next);
if (!next) {
LOG_ERR("Insufficient number of arguments");
return -ENOENT;
}
if (!strncmp(next, "bind", len)) {
return mod_set_bind(mod, len_rd, read_cb, cb_arg);
}
if (!strncmp(next, "sub", len)) {
return mod_set_sub(mod, len_rd, read_cb, cb_arg);
}
if (!strncmp(next, "pub", len)) {
return mod_set_pub(mod, len_rd, read_cb, cb_arg);
}
if (!strncmp(next, "data", len)) {
return mod_data_set(mod, next, len_rd, read_cb, cb_arg);
}
LOG_WRN("Unknown module key %s", next);
return -ENOENT;
}
static int sig_mod_set(const char *name, size_t len_rd,
settings_read_cb read_cb, void *cb_arg)
{
return mod_set(false, name, len_rd, read_cb, cb_arg);
}
BT_MESH_SETTINGS_DEFINE(sig_mod, "s", sig_mod_set);
static int vnd_mod_set(const char *name, size_t len_rd,
settings_read_cb read_cb, void *cb_arg)
{
return mod_set(true, name, len_rd, read_cb, cb_arg);
}
BT_MESH_SETTINGS_DEFINE(vnd_mod, "v", vnd_mod_set);
static int comp_set(const char *name, size_t len_rd, settings_read_cb read_cb,
void *cb_arg)
{
/* Only need to know that the entry exists. Will load the contents on
* demand.
*/
if (len_rd > 0) {
atomic_set_bit(bt_mesh.flags, BT_MESH_COMP_DIRTY);
}
return 0;
}
BT_MESH_SETTINGS_DEFINE(comp, "cmp", comp_set);
static void encode_mod_path(struct bt_mesh_model *mod, bool vnd,
const char *key, char *path, size_t path_len)
{
uint16_t mod_key = (((uint16_t)mod->elem_idx << 8) | mod->mod_idx);
if (vnd) {
snprintk(path, path_len, "bt/mesh/v/%x/%s", mod_key, key);
} else {
snprintk(path, path_len, "bt/mesh/s/%x/%s", mod_key, key);
}
}
static void store_pending_mod_bind(struct bt_mesh_model *mod, bool vnd)
{
uint16_t keys[CONFIG_BT_MESH_MODEL_KEY_COUNT];
char path[20];
int i, count, err;
for (i = 0, count = 0; i < mod->keys_cnt; i++) {
if (mod->keys[i] != BT_MESH_KEY_UNUSED) {
keys[count++] = mod->keys[i];
LOG_DBG("model key 0x%04x", mod->keys[i]);
}
}
encode_mod_path(mod, vnd, "bind", path, sizeof(path));
if (count) {
err = settings_save_one(path, keys, count * sizeof(keys[0]));
} else {
err = settings_delete(path);
}
if (err) {
LOG_ERR("Failed to store %s value", path);
} else {
LOG_DBG("Stored %s value", path);
}
}
static void store_pending_mod_sub(struct bt_mesh_model *mod, bool vnd)
{
uint16_t groups[CONFIG_BT_MESH_MODEL_GROUP_COUNT];
char path[20];
int i, count, err;
for (i = 0, count = 0; i < mod->groups_cnt; i++) {
if (mod->groups[i] != BT_MESH_ADDR_UNASSIGNED) {
groups[count++] = mod->groups[i];
}
}
encode_mod_path(mod, vnd, "sub", path, sizeof(path));
if (count) {
err = settings_save_one(path, groups,
count * sizeof(groups[0]));
} else {
err = settings_delete(path);
}
if (err) {
LOG_ERR("Failed to store %s value", path);
} else {
LOG_DBG("Stored %s value", path);
}
}
static void store_pending_mod_pub(struct bt_mesh_model *mod, bool vnd)
{
struct mod_pub_val pub = {0};
char path[20];
int err;
encode_mod_path(mod, vnd, "pub", path, sizeof(path));
if (!mod->pub || mod->pub->addr == BT_MESH_ADDR_UNASSIGNED) {
err = settings_delete(path);
} else {
pub.addr = mod->pub->addr;
pub.key = mod->pub->key;
pub.ttl = mod->pub->ttl;
pub.retransmit = mod->pub->retransmit;
pub.period = mod->pub->period;
pub.period_div = mod->pub->period_div;
pub.cred = mod->pub->cred;
err = settings_save_one(path, &pub, sizeof(pub));
}
if (err) {
LOG_ERR("Failed to store %s value", path);
} else {
LOG_DBG("Stored %s value", path);
}
}
static void store_pending_mod(struct bt_mesh_model *mod,
struct bt_mesh_elem *elem, bool vnd,
bool primary, void *user_data)
{
if (!mod->flags) {
return;
}
if (mod->flags & BT_MESH_MOD_BIND_PENDING) {
mod->flags &= ~BT_MESH_MOD_BIND_PENDING;
store_pending_mod_bind(mod, vnd);
}
if (mod->flags & BT_MESH_MOD_SUB_PENDING) {
mod->flags &= ~BT_MESH_MOD_SUB_PENDING;
store_pending_mod_sub(mod, vnd);
}
if (mod->flags & BT_MESH_MOD_PUB_PENDING) {
mod->flags &= ~BT_MESH_MOD_PUB_PENDING;
store_pending_mod_pub(mod, vnd);
}
if (mod->flags & BT_MESH_MOD_DATA_PENDING) {
mod->flags &= ~BT_MESH_MOD_DATA_PENDING;
mod->cb->pending_store(mod);
}
}
void bt_mesh_model_pending_store(void)
{
bt_mesh_model_foreach(store_pending_mod, NULL);
}
void bt_mesh_model_bind_store(struct bt_mesh_model *mod)
{
mod->flags |= BT_MESH_MOD_BIND_PENDING;
bt_mesh_settings_store_schedule(BT_MESH_SETTINGS_MOD_PENDING);
}
void bt_mesh_model_sub_store(struct bt_mesh_model *mod)
{
mod->flags |= BT_MESH_MOD_SUB_PENDING;
bt_mesh_settings_store_schedule(BT_MESH_SETTINGS_MOD_PENDING);
}
void bt_mesh_model_pub_store(struct bt_mesh_model *mod)
{
mod->flags |= BT_MESH_MOD_PUB_PENDING;
bt_mesh_settings_store_schedule(BT_MESH_SETTINGS_MOD_PENDING);
}
int bt_mesh_comp_store(void)
{
NET_BUF_SIMPLE_DEFINE(buf, BT_MESH_TX_SDU_MAX);
int err;
err = bt_mesh_comp_data_get_page_0(&buf, 0);
if (err) {
LOG_ERR("Failed to read composition data: %d", err);
return err;
}
err = settings_save_one("bt/mesh/cmp", buf.data, buf.len);
if (err) {
LOG_ERR("Failed to store composition data: %d", err);
} else {
LOG_DBG("Stored composition data");
}
return err;
}
int bt_mesh_comp_change_prepare(void)
{
if (!IS_ENABLED(CONFIG_BT_SETTINGS)) {
return -ENOTSUP;
}
return bt_mesh_comp_store();
}
static void comp_data_clear(void)
{
int err;
err = settings_delete("bt/mesh/cmp");
if (err) {
LOG_ERR("Failed to clear composition data: %d", err);
} else {
LOG_DBG("Cleared composition data page 128");
}
atomic_clear_bit(bt_mesh.flags, BT_MESH_COMP_DIRTY);
}
static int read_comp_cb(const char *key, size_t len, settings_read_cb read_cb,
void *cb_arg, void *param)
{
struct net_buf_simple *buf = param;
if (len > net_buf_simple_tailroom(buf)) {
return -ENOBUFS;
}
len = read_cb(cb_arg, net_buf_simple_tail(buf), len);
if (len > 0) {
net_buf_simple_add(buf, len);
}
return -EALREADY;
}
int bt_mesh_comp_read(struct net_buf_simple *buf)
{
size_t original_len = buf->len;
int err;
if (!IS_ENABLED(CONFIG_BT_SETTINGS)) {
return -ENOTSUP;
}
err = settings_load_subtree_direct("bt/mesh/cmp", read_comp_cb, buf);
if (err) {
LOG_ERR("Failed reading composition data: %d", err);
return err;
}
if (buf->len == original_len) {
return -ENOENT;
}
return 0;
}
int bt_mesh_model_data_store(struct bt_mesh_model *mod, bool vnd,
const char *name, const void *data,
size_t data_len)
{
char path[30];
int err;
encode_mod_path(mod, vnd, "data", path, sizeof(path));
if (name) {
strcat(path, "/");
strncat(path, name, SETTINGS_MAX_DIR_DEPTH);
}
if (data_len) {
err = settings_save_one(path, data, data_len);
} else {
err = settings_delete(path);
}
if (err) {
LOG_ERR("Failed to store %s value", path);
} else {
LOG_DBG("Stored %s value", path);
}
return err;
}
#if defined(CONFIG_BT_MESH_LARGE_COMP_DATA_SRV)
static int metadata_set(const char *name, size_t len_rd, settings_read_cb read_cb, void *cb_arg)
{
/* Only need to know that the entry exists. Will load the contents on
* demand.
*/
if (len_rd > 0) {
atomic_set_bit(bt_mesh.flags, BT_MESH_METADATA_DIRTY);
}
return 0;
}
BT_MESH_SETTINGS_DEFINE(metadata, "metadata", metadata_set);
int bt_mesh_models_metadata_store(void)
{
NET_BUF_SIMPLE_DEFINE(buf, CONFIG_BT_MESH_MODELS_METADATA_PAGE_LEN);
size_t total_size;
int err;
total_size = bt_mesh_metadata_page_0_size();
LOG_DBG("bt/mesh/metadata total %d", total_size);
net_buf_simple_init(&buf, 0);
net_buf_simple_add_le16(&buf, total_size);
err = bt_mesh_metadata_get_page_0(&buf, 0);
if (err == -E2BIG) {
LOG_ERR("Metadata too large");
return err;
}
if (err) {
LOG_ERR("Failed to read models metadata: %d", err);
return err;
}
LOG_DBG("bt/mesh/metadata len %d", buf.len);
err = settings_save_one("bt/mesh/metadata", buf.data, buf.len);
if (err) {
LOG_ERR("Failed to store models metadata: %d", err);
} else {
LOG_DBG("Stored models metadata");
}
return err;
}
int bt_mesh_models_metadata_read(struct net_buf_simple *buf, size_t offset)
{
NET_BUF_SIMPLE_DEFINE(stored_buf, CONFIG_BT_MESH_MODELS_METADATA_PAGE_LEN);
size_t original_len = buf->len;
int err;
if (!IS_ENABLED(CONFIG_BT_SETTINGS)) {
return -ENOTSUP;
}
net_buf_simple_init(&stored_buf, 0);
err = settings_load_subtree_direct("bt/mesh/metadata", read_comp_cb, &stored_buf);
if (err) {
LOG_ERR("Failed reading models metadata: %d", err);
return err;
}
/* First two bytes are total length */
offset += 2;
net_buf_simple_add_mem(buf, &stored_buf.data, MIN(net_buf_simple_tailroom(buf), 2));
if (offset >= stored_buf.len) {
return 0;
}
net_buf_simple_add_mem(buf, &stored_buf.data[offset],
MIN(net_buf_simple_tailroom(buf), stored_buf.len - offset));
LOG_DBG("metadata read %d", buf->len);
if (buf->len == original_len) {
return -ENOENT;
}
return 0;
}
#endif
static void models_metadata_clear(void)
{
int err;
err = settings_delete("bt/mesh/metadata");
if (err) {
LOG_ERR("Failed to clear models metadata: %d", err);
} else {
LOG_DBG("Cleared models metadata");
}
atomic_clear_bit(bt_mesh.flags, BT_MESH_METADATA_DIRTY);
}
void bt_mesh_comp_data_pending_clear(void)
{
comp_data_clear();
models_metadata_clear();
}
void bt_mesh_comp_data_clear(void)
{
bt_mesh_settings_store_schedule(BT_MESH_SETTINGS_COMP_PENDING);
}
int bt_mesh_models_metadata_change_prepare(void)
{
#if !IS_ENABLED(CONFIG_BT_MESH_LARGE_COMP_DATA_SRV)
return -ENOTSUP;
#endif
return bt_mesh_models_metadata_store();
}
static void commit_mod(struct bt_mesh_model *mod, struct bt_mesh_elem *elem,
bool vnd, bool primary, void *user_data)
{
if (mod->pub && mod->pub->update &&
mod->pub->addr != BT_MESH_ADDR_UNASSIGNED) {
int32_t ms = bt_mesh_model_pub_period_get(mod);
if (ms > 0) {
LOG_DBG("Starting publish timer (period %u ms)", ms);
k_work_schedule(&mod->pub->timer, K_MSEC(ms));
}
}
if (!IS_ENABLED(CONFIG_BT_MESH_LOW_POWER)) {
return;
}
for (int i = 0; i < mod->groups_cnt; i++) {
if (mod->groups[i] != BT_MESH_ADDR_UNASSIGNED) {
bt_mesh_lpn_group_add(mod->groups[i]);
}
}
}
void bt_mesh_model_settings_commit(void)
{
bt_mesh_model_foreach(commit_mod, NULL);
}
void bt_mesh_model_data_store_schedule(struct bt_mesh_model *mod)
{
mod->flags |= BT_MESH_MOD_DATA_PENDING;
bt_mesh_settings_store_schedule(BT_MESH_SETTINGS_MOD_PENDING);
}