Bluetooth mesh with several models on device.

Hi DevZone!

I'm currently developing a device that has several hardware actuators and I've implemented 2 OnOff Models each belonging to an element. But right now I'm having trouble in the nRF Mesh app when it requests composition data, during this process the device log is shown in the image below. 

My question is, can the device have several of the same model IDs like OnOff, Sensors server, and others? Is necessary a Kconfig configuration I'm missing to do it? I'm attaching here my model_handler.c and prj.conf to check if everything is fine. Also to mention, in this device, I have a custom battery model that is already working as I want to. 

I'm working on NCS v2.5.0 and two 52840 DK's ( 1 for the server models and 1 for the client models).

Best Regards, Caio.

#include <zephyr/bluetooth/bluetooth.h>
#include <bluetooth/mesh/models.h>
#include <dk_buttons_and_leds.h>
#include <float.h>
#include "model_handler.h"
#if defined(CONFIG_LOG)
#include <zephyr/logging/log.h>
#endif

LOG_MODULE_REGISTER(model_handler);

/*Company ID - Defined in prj.conf, TODO: Each vendor must have a CID registered in Bluetooth SIG, using Nordic CID for convinience here */


/* Creating custom model for server */
#define MY_BATTERY_MODEL_ID 0x0000 /*Generic Battery Server*/

//OPCODES for battery 
#define BATTERY_GET_OP                  BT_MESH_MODEL_OP_3(0x01, CONFIG_BT_COMPANY_ID) //GET (3 bytes for the opcode and 2 bytes for the voltage
#define BATTERY_TYPE_SET_UNACK_OP       BT_MESH_MODEL_OP_3(0x02, CONFIG_BT_COMPANY_ID) // SET (3 bytes for the opcode and 1 byte for the type)
#define BATTERY_STATUS_OP               BT_MESH_MODEL_OP_3(0x03, CONFIG_BT_COMPANY_ID) // STATUS OP MESSAGE, RESPONSE FROM GET MESSAGE. (3 bytes for the opcode and 1 byte for the type)

//OPCODES for auto_lock
#define LOCK_GET_OP                     BT_MESH_MODEL_OP_2(0x82, 0x01)
#define LOCK_SET_UNACK_OP               BT_MESH_MODEL_OP_2(0x82, 0x02)
#define LOCK_STATUS_OP                  BT_MESH_MODEL_OP_3(0x82, 0x03)

//OPCODES for auto_buzzer
#define BUZZER_GET_OP                   BT_MESH_MODEL_OP_2(0X82, 0x04)
#define BUZZER_SET_UNACK_OP             BT_MESH_MODEL_OP_2(0x82, 0x05)
#define BUZZER_STATUS_OP                BT_MESH_MODEL_OP_2(0x82, 0x06)

//OPCODES for auto_light
#define LIGHT_GET_OP                    BT_MESH_MODEL_OP_2(0x82, 0x07)
#define LIGHT_SET_UNACK_OP              BT_MESH_MODEL_OP_2(0x82, 0x08)
#define LIGHT_STATUS_OP                 BT_MESH_MODEL_OP_2(0x82, 0x09)

//Server model publication context:
//Battery
BT_MESH_MODEL_PUB_DEFINE(battery_srv_pub, NULL, 2 + 1); //2 bytes for the voltage and 1 byte for the type
//Lock
BT_MESH_MODEL_PUB_DEFINE(lock_srv_pub, NULL, 1 + 2); //1 Byte for the On-Off byte and 2 bytes for the auto lock delay.
//Buzzer
BT_MESH_MODEL_PUB_DEFINE(buzzer_srv_pub, NULL, 1); //1 Byte for the On|Off|Alarms
//Light
BT_MESH_MODEL_PUB_DEFINE(light_srv_pub, NULL, 1); //1 Byte for the On|Off|Alarms

//Battery type
enum battery_type_t
{
    BATTERY_TYPE_UNKNOWN    = 0,
    BATTERY_TYPE_LEAN_ACID  = 1,
    BATTERY_TYPE_LITHIUM    = 2,
    BATTERY_TYPE_DIRECT_POWER = 3,
    BATTERY_TYPE_DRY        = 4,
};

//Lock Type
enum lock_onoff_t
{
    LOCK_OFF = 0,
    LOCK_ON  = 1,
};

//Buzzer and Light Type
enum onoffalarm_t
{
    OFF         = 0,
    ON          = 1,
    ALARMS_ONLY = 2,
};

//Structure to keep the battery data
typedef struct battery_data_t
{
    uint16_t voltage_mv;
    enum battery_type_t type;

}battery_data_t;

//Lock Structure
typedef struct lock_data_t
{
    enum lock_onoff_t status;
    uint16_t lock_delay;

}lock_data_t;

//Buzzer
typedef struct buzzer_data_t
{
    enum onoffalarm_t status;

}buzzer_data_t;

//Light
typedef struct light_data_t
{
    enum onoffalarm_t status;
}light_data_t;

//Battery data Initial
//TODO: Implement a function that saves the battery data in the flash memory, so it can be restored after a reboot.
static battery_data_t battery_data = {
    .voltage_mv = 0,
    .type = BATTERY_TYPE_UNKNOWN,
};

//Auto lock data initial
static lock_data_t lock_data = {
    .status = LOCK_OFF,
    .lock_delay = 10,
};

//Auto Buzzer data initial
static buzzer_data_t buzzer_data = {
    .status = OFF,
};

//Auto Light data initial
static light_data_t light_data = {
    .status = OFF,
};

//Functions prototypes, to be used in the custom model

//Handlers - Functions that will be called when a message is received
static void battery_get(struct bt_mesh_model *model, struct bt_mesh_msg_ctx *ctx, struct net_buf_simple *buf);
static void battery_set_unack(struct bt_mesh_model *model, struct bt_mesh_msg_ctx *ctx, struct net_buf_simple *buf);
static void battery_status(struct bt_mesh_model *model, struct bt_mesh_msg_ctx *ctx, struct net_buf_simple *buf);

static void lock_onoff_get(struct bt_mesh_model *model, struct bt_mesh_msg_ctx *ctn, struct net_buf_simple *buf);
static void lock_onoff_set_unack(struct bt_mesh_model *model, struct bt_mesh_msg_ctx *ctn, struct net_buf_simple *buf);
static void lock_onoff_status(struct bt_mesh_model *model, struct bt_mesh_msg_ctx *ctn, struct net_buf_simple *buf);

static void buzzer_onoff_get(struct bt_mesh_model *model, struct bt_mesh_msg_ctx *ctn, struct net_buf_simple *buf);
static void buzzer_onoff_set_unack(struct bt_mesh_model *model, struct bt_mesh_msg_ctx *ctn, struct net_buf_simple *buf);
static void buzzer_onoff_status(struct bt_mesh_model *model, struct bt_mesh_msg_ctx *ctn, struct net_buf_simple *buf);

static void light_onoff_get(struct bt_mesh_model *model, struct bt_mesh_msg_ctx *ctn, struct net_buf_simple *buf);
static void light_onoff_set_unack(struct bt_mesh_model *model, struct bt_mesh_msg_ctx *ctn, struct net_buf_simple *buf);
static void light_onoff_status(struct bt_mesh_model *model, struct bt_mesh_msg_ctx *ctn, struct net_buf_simple *buf);

//Model Opcodes
//Battery
static const struct bt_mesh_model_op battery_op_list[];
//Lock
static const struct bt_mesh_model_op lock_op_list[];
//Buzzer
static const struct bt_mesh_model_op buzzer_op_list[];
//Light
static const struct bt_mesh_model_op light_op_list[];


/**
 * Device server model handlers Functions =================================================================================================
*/
/* Battery Handlers Functions */
static void battery_get(struct bt_mesh_model *model, struct bt_mesh_msg_ctx *ctx, struct net_buf_simple *buf)
{
    LOG_INF("Battery Get message received\n");
    //LOGGING SOME INFO
    LOG_INF("ctx->addr: 0x%04x\n", ctx->addr);
    LOG_INF("ctx->net_idx: 0x%04x\n", ctx->net_idx);
    LOG_INF("ctx->app_idx: 0x%04x\n", ctx->app_idx);
    LOG_INF("ctx->recv_dst: 0x%04x\n", ctx->recv_dst);

    //TODO: Implement a function that reads the battery voltage from serial interface
    //For now, we will use a random value
    battery_data.voltage_mv = 1000 + (rand() % 1000);
    LOG_INF("Battery voltage: %d\n", battery_data.voltage_mv);
    LOG_INF("Battery type: %d\n", battery_data.type);


    //Send the battery voltage
    publish_battery_status(model, ctx, battery_data.voltage_mv, battery_data.type);
}

static void battery_set_unack(struct bt_mesh_model *model, struct bt_mesh_msg_ctx *ctx, struct net_buf_simple *buf)
{
    LOG_INF("Battery Set Unack message received\n");
    //Set the battery type
    battery_data.type = net_buf_simple_pull_u8(buf);
    LOG_INF("Battery type set to: %d\n", battery_data.type);
}

static void battery_status(struct bt_mesh_model *model, struct bt_mesh_msg_ctx *ctx, struct net_buf_simple *buf)
{
    LOG_INF("Replying with STATUS message\n");
    //Send the battery status

}
/* END - Battery Functions Handlers */

/* Lock Functions Handlers */
static void lock_onoff_get(struct bt_mesh_model *model, struct bt_mesh_msg_ctx *ctx, struct net_buf_simple *buf)
{
    LOG_INF("Lock Get message received\n");
    //TODO: Implement the function that set on off in the lock
    LOG_INF("Lock Status: %d | Lock Delay: %d", lock_data.status, lock_data.lock_delay);

    //Send the lock status
    publish_lock_status(model, ctx, lock_data.status, lock_data.lock_delay);
}

static void lock_onoff_set_unack(struct bt_mesh_model *model, struct bt_mesh_msg_ctx *ctx, struct net_buf_simple *buf)
{
    LOG_INF("Lock SET message received!");
    uint16_t delay = net_buf_simple_pull_le16(buf); /*delay*/
    uint8_t status = net_buf_simple_pull_u8(buf); /*OnOFF*/

    if(status)
    {
        LOG_INF("LOCK ON, with delay of %d", delay);
        lock_data.status = LOCK_ON;
        lock_data.lock_delay = delay;
    }
    else
    {
        LOG_INF("LOCK OFF, with delay of %d", delay);
        lock_data.status = LOCK_OFF;
        lock_data.lock_delay = delay;
    }
}

static void lock_onoff_status(struct bt_mesh_model *model, struct bt_mesh_msg_ctx *ctx, struct net_buf_simple *buf)
{
    LOG_INF("Lock STATUS message received!");
}
/* END - Lock Functions Handlers */

/* Buzzer Functions Handlers */
static void buzzer_onoff_get(struct bt_mesh_model *model, struct bt_mesh_msg_ctx *ctx, struct net_buf_simple *buf)
{
    LOG_INF("Buzzer GET Message Received");
    LOG_INF("Buzzer Status: %d", buzzer_data.status);

    //Send buzzer data
    publish_buzzer_status(model, ctx, buzzer_data.status);
}

static void buzzer_onoff_set_unack(struct bt_mesh_model *model, struct bt_mesh_msg_ctx *ctx, struct net_buf_simple *buf)
{
    LOG_INF("Buzzer SET Message Received");
    uint8_t status = net_buf_simple_pull_u8(buf);

    if(status == 0)
    {
        LOG_INF("Buzzer OFF");
        buzzer_data.status = OFF;
    }
    else if(status == 1)
    {
        LOG_INF("Buzzer ON");
        buzzer_data.status = ON;
    }
    else if(status == 2)
    {
        LOG_INF("Buzzer ALARMS ONLY");
        buzzer_data.status = ALARMS_ONLY;
    }
    else
    {
        LOG_INF("Invalid status");
    }

}

static void buzzer_onoff_status(struct bt_mesh_model *model, struct bt_mesh_msg_ctx *ctx, struct net_buf_simple *buf)
{
    LOG_INF("Buzzer STATUS message received!");
}
/* END - Buzzer Functions Handlers */

/* Light Functions Handlers */
static void light_onoff_get(struct bt_mesh_model *model, struct bt_mesh_msg_ctx *ctx, struct net_buf_simple *buf)
{
    LOG_INF("Light GET Message Received");
    LOG_INF("Light Status: %d", light_data.status);

    //Send light data
    publish_light_status(model, ctx, light_data.status);
}

static void light_onoff_set_unack(struct bt_mesh_model *model, struct bt_mesh_msg_ctx *ctx, struct net_buf_simple *buf)
{
    LOG_INF("Light SET Message Received");
    uint8_t status = net_buf_simple_pull_u8(buf);

    if(status == 0)
    {
        LOG_INF("Light OFF");
        light_data.status = OFF;
    }
    else if(status == 1)
    {
        LOG_INF("Light ON");
        light_data.status = ON;
    }
    else if(status == 2)
    {
        LOG_INF("Light ALARMS ONLY");
        light_data.status = ALARMS_ONLY;
    }
    else
    {
        LOG_INF("Invalid status");
    }

}

static void light_onoff_status(struct bt_mesh_model *model, struct bt_mesh_msg_ctx *ctx, struct net_buf_simple *buf)
{
    LOG_INF("Light STATUS message received!");
}

/* END - Light Functions Handlers */
/**
 * Device server model Publication =================================================================================================
*/
//Publish the battery voltage and type
void publish_battery_status(struct bt_mesh_model *model, struct bt_mesh_msg_ctx *ctx, uint16_t voltage_mv, enum battery_type_t type)
{
    int err;
    //Check if the model has a address assigned
    if(model->pub->addr == BT_MESH_ADDR_UNASSIGNED)
    {
        LOG_INF("No publish address associated with the model\n");
        return;
    }

    struct net_buf_simple *msg = model->pub->msg;
    bt_mesh_model_msg_init(msg, BATTERY_STATUS_OP);
    net_buf_simple_add_le16(msg, voltage_mv);
    net_buf_simple_add_u8(msg, type);
    LOG_INF("Publishing battery status\n");
    //Publish the message
    err = bt_mesh_model_publish(model);
    if(err)
    {
        LOG_INF("bt_mesh_model_publish err: %d\n", err);
    }
}

//Publish the lock status
void publish_lock_status(struct bt_mesh_model *model, struct bt_mesh_msg_ctx *ctx, enum lock_onoff_t status, uint16_t delay)
{
    int err; 
    if(model->pub->addr == BT_MESH_ADDR_UNASSIGNED)
    {
        LOG_INF("No publish address associated with the model\n");
        return;
    }

    struct net_buf_simple *msg = model->pub->msg;
    bt_mesh_model_msg_init(msg, LOCK_STATUS_OP);
    net_buf_simple_add_le16(msg, delay);
    net_buf_simple_add_u8(msg, status);
    LOG_INF("Publishing LOCK status\n");
    //Publish the message
    err = bt_mesh_model_publish(model);
    if(err)
    {
        LOG_INF("bt_mesh_model_publish err: %d\n", err);
    }
}

//Publish the buzzer status
void publish_buzzer_status(struct bt_mesh_model *model, struct bt_mesh_msg_ctx *ctx, enum onoffalarm_t status)
{
    int err;
    if(model->pub->addr == BT_MESH_ADDR_UNASSIGNED)
    {
        LOG_INF("No publish address associated with the model\n");
        return;
    }

    struct net_buf_simple *msg = model->pub->msg;
    bt_mesh_model_msg_init(msg, BUZZER_STATUS_OP);
    net_buf_simple_add_u8(msg, status);
    LOG_INF("Publishing BUZZER status\n");
    //Publish the message
    err = bt_mesh_model_publish(model);
    if(err)
    {
        LOG_INF("bt_mesh_model_publish err: %d\n", err);
    }
}

//Publish the light status
void publish_light_status(struct bt_mesh_model *model, struct bt_mesh_msg_ctx *ctx, enum onoffalarm_t status)
{
    int err;
    if(model->pub->addr == BT_MESH_ADDR_UNASSIGNED)
    {
        LOG_INF("No publish address associated with the model\n");
        return;
    }

    struct net_buf_simple *msg = model->pub->msg;
    bt_mesh_model_msg_init(msg, LIGHT_STATUS_OP);
    net_buf_simple_add_u8(msg, status);
    LOG_INF("Publishing LIGHT status\n");
    //Publish the message
    err = bt_mesh_model_publish(model);
    if(err)
    {
        LOG_INF("bt_mesh_model_publish err: %d\n", err);
    }
}

/**
 * Server Model Entries =================================================================================================
*/

static const struct bt_mesh_model_op battery_op_list[] = {
    {BATTERY_GET_OP,            BT_MESH_LEN_MIN(0), battery_get},
    {BATTERY_TYPE_SET_UNACK_OP, BT_MESH_LEN_MIN(1), battery_set_unack},
    {BATTERY_STATUS_OP,         BT_MESH_LEN_MIN(1), battery_status},
    BT_MESH_MODEL_OP_END,
};

static const struct bt_mesh_model_op lock_op_list[] = {
    {LOCK_GET_OP,               BT_MESH_LEN_MIN(0), lock_onoff_get},
    {LOCK_SET_UNACK_OP,         BT_MESH_LEN_MIN(0), lock_onoff_set_unack},
    {LOCK_STATUS_OP,            BT_MESH_LEN_MIN(0), lock_onoff_status},
    BT_MESH_MODEL_OP_END,
};

static const struct bt_mesh_model_op buzzer_op_list[] = {
    {BUZZER_GET_OP,             BT_MESH_LEN_MIN(0), buzzer_onoff_get},
    {BUZZER_SET_UNACK_OP,       BT_MESH_LEN_MIN(0), buzzer_onoff_set_unack},
    {BUZZER_STATUS_OP,          BT_MESH_LEN_MIN(0), buzzer_onoff_status},
    BT_MESH_MODEL_OP_END,
};

static const struct bt_mesh_model_op light_op_list[] = {
    {LIGHT_GET_OP,              BT_MESH_LEN_MIN(0), light_onoff_get},
    {LIGHT_SET_UNACK_OP,        BT_MESH_LEN_MIN(0), light_onoff_set_unack},
    {LIGHT_STATUS_OP,           BT_MESH_LEN_MIN(0), light_onoff_status},
    BT_MESH_MODEL_OP_END,
};

/**
 * Health Server =================================================================================================
*/
static bool attention;

static void attention_on(struct bt_mesh_model *mod)
{
	attention = true;
	LOG_INF("attention_on\n");
}

static void attention_off(struct bt_mesh_model *mod)
{
	attention = false;
    LOG_INF("attention_off\n");
}

static const struct bt_mesh_health_srv_cb health_srv_cb = {
	.attn_on = attention_on,
	.attn_off = attention_off,
};

static struct bt_mesh_health_srv health_srv = {
	.cb = &health_srv_cb,
};

BT_MESH_HEALTH_PUB_DEFINE(health_pub, 0);

struct bt_mesh_model root_models[] = {
    BT_MESH_MODEL_CFG_SRV,
    BT_MESH_MODEL_HEALTH_SRV(&health_srv, &health_pub),
    BT_MESH_MODEL(BT_MESH_MODEL_ID_GEN_ONOFF_SRV, lock_op_list, &lock_srv_pub, &lock_data),
};

/**
 * Device server Model =================================================================================================
*/
struct bt_mesh_model s0_models[] = {    
    BT_MESH_MODEL(BT_MESH_MODEL_ID_GEN_ONOFF_SRV, buzzer_op_list, &buzzer_srv_pub, &buzzer_data),
};

struct bt_mesh_model s1_models[] = {
    BT_MESH_MODEL(BT_MESH_MODEL_ID_GEN_ONOFF_SRV, light_op_list, &light_srv_pub, &light_data),
};

struct bt_mesh_model parking_device_server_model [] = {
   BT_MESH_MODEL_VND(CONFIG_BT_COMPANY_ID, MY_BATTERY_MODEL_ID, battery_op_list, &battery_srv_pub, &battery_data),
};

static struct bt_mesh_elem elements[] = {
  BT_MESH_ELEM(0, root_models, parking_device_server_model),
  BT_MESH_ELEM(1, s0_models, BT_MESH_MODEL_NONE),
  BT_MESH_ELEM(2, s1_models, BT_MESH_MODEL_NONE),
};

static const struct bt_mesh_comp comp = {
    .cid = CONFIG_BT_COMPANY_ID,
    .elem = elements,
    .elem_count = ARRAY_SIZE(elements),
};

const struct bt_mesh_comp *model_handler_init(void)
{
    if(IS_ENABLED(CONFIG_BT_SETTINGS))
    {
        settings_subsys_init();
        settings_load();
    }

    return &comp;
}

#General settings
CONFIG_MAIN_STACK_SIZE=4096
CONFIG_SYSTEM_WORKQUEUE_STACK_SIZE=4096
CONFIG_FLASH=y
CONFIG_FLASH_PAGE_LAYOUT=y
CONFIG_FLASH_MAP=y
CONFIG_FCB=y
CONFIG_SETTINGS=y
CONFIG_SETTINGS_FCB=y
CONFIG_HWINFO=y
CONFIG_DK_LIBRARY=y


#Bluetooth settings
CONFIG_BT=y
#Company ID used by the Bluetooth SIG, for testing purposes we will use nordic semi ID.
CONFIG_BT_COMPANY_ID=0X0059
CONFIG_BT_OBSERVER=y
CONFIG_BT_SETTINGS=y
CONFIG_BT_PERIPHERAL=y
CONFIG_BT_DEVICE_NAME="Parking Device"

#Mesh settings
CONFIG_BT_MESH=y
CONFIG_BT_MESH_APP_KEY_COUNT=1
CONFIG_BT_MESH_RELAY=y
CONFIG_BT_MESH_FRIEND=y
CONFIG_BT_MESH_GATT_PROXY=y
CONFIG_BT_MESH_PROXY_USE_DEVICE_NAME=y
CONFIG_BT_MESH_BEACON_ENABLED=n
CONFIG_BT_MESH_GATT_PROXY=y
CONFIG_BT_MESH_PB_GATT=y
CONFIG_BT_MESH_PB_ADV=y
CONFIG_BT_MESH_CRPL=32
CONFIG_BT_MESH_MSG_CACHE_SIZE=64
CONFIG_BT_MESH_SHELL=y
CONFIG_BT_MESH_DK_PROV=y

#Mesh models settings


#Logging settings
CONFIG_LOG=y
CONFIG_BT_MESH_LOG_LEVEL_DBG=y
CONFIG_BT_MESH_LOG_LEVEL_INF=y

#J-Link LOG via RTT
CONFIG_UART_CONSOLE=n
CONFIG_USE_SEGGER_RTT=y
CONFIG_RTT_CONSOLE=y
CONFIG_LOG_BACKEND_RTT=y

Related