Device information service multi-instances

Hi,

I am using NC9 2.9.0 with VSCode and NRF extension. 

I have seen that since version 1.2 of Device Information Service (DIS) spec, it is theorically possible to have multiple instances of this service, by respecting the following rules:

  • No more than one instance of the Device Information Service may be exposed as a «Primary Service» on a device. One or more instances of the Device Information Service may be exposed as a «Secondary Service» on the same device.
  • A Device Information Service instance that is exposed as a «Secondary Service» must be included in another service

So, I have developed some code to do that: here is the declaration of the services

/********************DIS MULTI BEGIN******************* */

/* ---- UUIDs standard (Assigned Numbers)
 * Service DIS:     0x180A
 * Model Number:    0x2A24
 * Manufacturer:    0x2A29
 * PnP ID:          0x2A50
 */
#define BT_UUID16_DIS                 BT_UUID_DECLARE_16(0x180A)
#define BT_UUID16_DIS_MODEL_NUMBER    BT_UUID_DECLARE_16(0x2A24)
#define BT_UUID16_DIS_MANUF_NAME      BT_UUID_DECLARE_16(0x2A29)
#define BT_UUID16_DIS_PNP_ID          BT_UUID_DECLARE_16(0x2A50)

/* PnP ID format (7 bytes) : 
 * [0] VendorID_Source (0x01 = Bluetooth SIG, 0x02 = USB)
 * [1..2] VendorID (Little Endian)
 * [3..4] ProductID (LE)
 * [5..6] ProductVersion (LE)
 * Cf. DIS spec. */
struct __packed pnp_id_t {
    uint8_t  vid_source;
    uint16_t vid;
    uint16_t pid;
    uint16_t ver;
};


/* DIS primary */
static const char dis_primary_manufacturer[] = "WIKA";
static const char dis_primary_model[]        = "Nordic";
static const struct pnp_id_t dis_primary_pnp = {
    .vid_source = 0x02, .vid = 0x1915, .pid = 0x1337, .ver = 0x0102
};

/* DIS secondary */
static const char dis_module_manufacturer[] = "WIKA";
static const char dis_module_model[]        = "STM";
static const struct pnp_id_t dis_module_pnp = {
    .vid_source = 0x01, .vid = 0x05F9, .pid = 0x0001, .ver = 0x0001
};

static ssize_t read_str(struct bt_conn *conn, const struct bt_gatt_attr *attr,
                        void *buf, uint16_t len, uint16_t offset)
{
    const char *str = attr->user_data;
    size_t slen = strlen(str);
    return bt_gatt_attr_read(conn, attr, buf, len, offset, str, slen);
}

static ssize_t read_pnpid(struct bt_conn *conn, const struct bt_gatt_attr *attr,
                          void *buf, uint16_t len, uint16_t offset)
{
    const struct pnp_id_t *pnp = attr->user_data;
    return bt_gatt_attr_read(conn, attr, buf, len, offset, pnp, sizeof(*pnp));
}

/* ===== 1) DIS PRIMARY  ===== */
BT_GATT_SERVICE_DEFINE(dis_primary_svc,
    BT_GATT_PRIMARY_SERVICE(BT_UUID16_DIS),

    BT_GATT_CHARACTERISTIC(BT_UUID16_DIS_MANUF_NAME, BT_GATT_CHRC_READ,
                           BT_GATT_PERM_READ, read_str, NULL, (void *)dis_primary_manufacturer),

    BT_GATT_CHARACTERISTIC(BT_UUID16_DIS_MODEL_NUMBER, BT_GATT_CHRC_READ,
                           BT_GATT_PERM_READ, read_str, NULL, (void *)dis_primary_model),

    BT_GATT_CHARACTERISTIC(BT_UUID16_DIS_PNP_ID, BT_GATT_CHRC_READ,
                           BT_GATT_PERM_READ, read_pnpid, NULL, (void *)&dis_primary_pnp),
	// BT_GATT_INCLUDE_SERVICE(&dis_module_svc)
);

/* ===== 2) DIS SECONDARY  ===== */
// BT_GATT_SERVICE_DEFINE(dis_module_svc,
//     BT_GATT_SECONDARY_SERVICE(BT_UUID16_DIS),

//     BT_GATT_CHARACTERISTIC(BT_UUID16_DIS_MANUF_NAME, BT_GATT_CHRC_READ,
//                            BT_GATT_PERM_READ, read_str, NULL, (void *)dis_module_manufacturer),

//     BT_GATT_CHARACTERISTIC(BT_UUID16_DIS_MODEL_NUMBER, BT_GATT_CHRC_READ,
//                            BT_GATT_PERM_READ, read_str, NULL, (void *)dis_module_model),

//     BT_GATT_CHARACTERISTIC(BT_UUID16_DIS_PNP_ID, BT_GATT_CHRC_READ,
//                            BT_GATT_PERM_READ, read_pnpid, NULL,(void *)&dis_module_pnp)
// );

/* 1) Secondary DIS as a runtime (non-const) service */
static struct bt_gatt_attr dis2_attrs[] = {
    BT_GATT_SECONDARY_SERVICE(BT_UUID16_DIS),

    BT_GATT_CHARACTERISTIC(BT_UUID16_DIS_MANUF_NAME,   BT_GATT_CHRC_READ,
                           BT_GATT_PERM_READ, read_str, NULL, (void *)dis_module_manufacturer),

    BT_GATT_CHARACTERISTIC(BT_UUID16_DIS_MODEL_NUMBER, BT_GATT_CHRC_READ,
                           BT_GATT_PERM_READ, read_str, NULL, (void *)dis_module_model),

    BT_GATT_CHARACTERISTIC(BT_UUID16_DIS_PNP_ID,       BT_GATT_CHRC_READ,
                           BT_GATT_PERM_READ, read_pnpid, NULL, (void *)&dis_module_pnp),
};

static struct bt_gatt_service dis2_svc = BT_GATT_SERVICE(dis2_attrs);

/* ===== 3) Service "Module X" including secondary DIS ===== */

/* UUID 128-bit for characteristic of module X */
static struct bt_uuid_128 modx_ver_uuid =
    BT_UUID_INIT_128(BT_UUID_128_ENCODE(0xaaaaaaaa,0xbbbb,0xcccc,0xdddd,0xeeeeffffffff));

static uint8_t modx_version = 0x01;

static ssize_t read_modx_ver(struct bt_conn *conn,
                             const struct bt_gatt_attr *attr,
                             void *buf, uint16_t len, uint16_t offset)
{
    const uint8_t *val = attr->user_data;
    return bt_gatt_attr_read(conn, attr, buf, len, offset, val, sizeof(*val));
}

static struct bt_uuid_128 module_x_uuid =
    BT_UUID_INIT_128(0x23,0x01,0x45,0x67, 0x89,0xab,0xcd,0xef, 0x01,0x23,0x45,0x67, 0x89,0xab,0xcd,0xef);

static struct bt_gatt_attr modx_attrs[] = {
    BT_GATT_PRIMARY_SERVICE(&module_x_uuid.uuid),
    BT_GATT_INCLUDE_SERVICE(&dis2_svc),    /* include runtime service */
    BT_GATT_CHARACTERISTIC(&modx_ver_uuid.uuid,
                           BT_GATT_CHRC_READ, BT_GATT_PERM_READ,
                           read_modx_ver, NULL, &modx_version),
};
static struct bt_gatt_service modx_svc = BT_GATT_SERVICE(modx_attrs);


/********************DIS MULTI END******************* */

and here is related code in main

	err = bt_enable(NULL);
	if (err) {
		printk("Bluetooth init failed (err %d)\n", err);
		return 0;
	}

	printk("Bluetooth initialized\n");

	if (IS_ENABLED(CONFIG_SETTINGS)) {
		settings_load();
	}

	err = bt_gatt_service_register(&dis2_svc);
	if (err) {
		printk("Bluetooth secondary service init failed (err %d)\n", err);
	}
	err = bt_gatt_service_register(&modx_svc);
	if (err) {
		printk("Bluetooth primary service init failed (err %d)\n", err);
	}

So I have a DIS declared as Primary service, and a second Primary service, which includes my secondary service, second instance of DIS.

After compiling flashing, I can see my Primary service DIS, my custom primary Service, and I see also a secondary service, but it is empty and has UUID 0x2801 as you can in picture:

Do you know what is the problem and how I can resolve this ?

Parents
  • Hi Antoine,
    I haven't tried your code but this seems to work for me. I modified lbs.c to add a secondary service in, I can see the characteristic of the secondary service.
    There is some warning but at least it built.

    /*
     * Copyright (c) 2018 Nordic Semiconductor ASA
     *
     * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause
     */
    
    /** @file
     *  @brief LED Button Service (LBS) sample
     */
    
    #include <zephyr/types.h>
    #include <stddef.h>
    #include <string.h>
    #include <errno.h>
    #include <zephyr/sys/printk.h>
    #include <zephyr/sys/byteorder.h>
    #include <zephyr/kernel.h>
    
    #include <zephyr/bluetooth/bluetooth.h>
    #include <zephyr/bluetooth/hci.h>
    #include <zephyr/bluetooth/conn.h>
    #include <zephyr/bluetooth/uuid.h>
    #include <zephyr/bluetooth/gatt.h>
    
    #include <bluetooth/services/lbs.h>
    
    #include <zephyr/logging/log.h>
    
    LOG_MODULE_REGISTER(bt_lbs, CONFIG_BT_LBS_LOG_LEVEL);
    
    static bool                   notify_enabled;
    #ifdef CONFIG_BT_LBS_POLL_BUTTON
    static bool                   button_state;
    #endif
    static struct bt_lbs_cb       lbs_cb;
    
    static void lbslc_ccc_cfg_changed(const struct bt_gatt_attr *attr,
    				  uint16_t value)
    {
    	notify_enabled = (value == BT_GATT_CCC_NOTIFY);
    }
    
    static ssize_t write_led(struct bt_conn *conn,
    			 const struct bt_gatt_attr *attr,
    			 const void *buf,
    			 uint16_t len, uint16_t offset, uint8_t flags)
    {
    	LOG_DBG("Attribute write, handle: %u, conn: %p", attr->handle,
    		(void *)conn);
    
    	if (len != 1U) {
    		LOG_DBG("Write led: Incorrect data length");
    		return BT_GATT_ERR(BT_ATT_ERR_INVALID_ATTRIBUTE_LEN);
    	}
    
    	if (offset != 0) {
    		LOG_DBG("Write led: Incorrect data offset");
    		return BT_GATT_ERR(BT_ATT_ERR_INVALID_OFFSET);
    	}
    
    	if (lbs_cb.led_cb) {
    		uint8_t val = *((uint8_t *)buf);
    
    		if (val == 0x00 || val == 0x01) {
    			lbs_cb.led_cb(val ? true : false);
    		} else {
    			LOG_DBG("Write led: Incorrect value");
    			return BT_GATT_ERR(BT_ATT_ERR_VALUE_NOT_ALLOWED);
    		}
    	}
    
    	return len;
    }
    
    #ifdef CONFIG_BT_LBS_POLL_BUTTON
    static ssize_t read_button(struct bt_conn *conn,
    			  const struct bt_gatt_attr *attr,
    			  void *buf,
    			  uint16_t len,
    			  uint16_t offset)
    {
    	const char *value = attr->user_data;
    
    	LOG_DBG("Attribute read, handle: %u, conn: %p", attr->handle,
    		(void *)conn);
    
    	if (lbs_cb.button_cb) {
    		button_state = lbs_cb.button_cb();
    		return bt_gatt_attr_read(conn, attr, buf, len, offset, value,
    					 sizeof(*value));
    	}
    
    	return 0;
    }
    #endif
    
    #define BT_UUID_CUSTOM_SECONDARY_VAL \
    	BT_UUID_128_ENCODE(0x12345678, 0x1234, 0x5678, 0x1234, 0x56789abcdef0)
    
    static struct bt_uuid_128 my_secondary_uuid = BT_UUID_INIT_128(BT_UUID_CUSTOM_SECONDARY_VAL);
    
    /* A characteristic within the secondary service */
    static uint8_t sec_value = 0x42;
    
    static ssize_t read_sec_value(struct bt_conn *conn,
    			      const struct bt_gatt_attr *attr,
    			      void *buf, uint16_t len, uint16_t offset)
    {
    	const uint8_t *value = attr->user_data;
    	return bt_gatt_attr_read(conn, attr, buf, len, offset, value,
    				 sizeof(*value));
    }
    BT_GATT_SERVICE_DEFINE(my_secondary_svc,
    	BT_GATT_SECONDARY_SERVICE(&my_secondary_uuid),
    	BT_GATT_CHARACTERISTIC(BT_UUID_DECLARE_16(0x2A19),
    			       BT_GATT_CHRC_READ,
    			       BT_GATT_PERM_READ,
    			       read_sec_value, NULL, &sec_value),
    );
    
    
    /* LED Button Service Declaration */
    BT_GATT_SERVICE_DEFINE(lbs_svc,
    BT_GATT_PRIMARY_SERVICE(BT_UUID_LBS),
    #ifdef CONFIG_BT_LBS_POLL_BUTTON
    	BT_GATT_CHARACTERISTIC(BT_UUID_LBS_BUTTON,
    			       BT_GATT_CHRC_READ | BT_GATT_CHRC_NOTIFY,
    			       BT_GATT_PERM_READ, read_button, NULL,
    			       &button_state),
    #else
    	BT_GATT_CHARACTERISTIC(BT_UUID_LBS_BUTTON,
    			       BT_GATT_CHRC_READ | BT_GATT_CHRC_NOTIFY,
    			       BT_GATT_PERM_READ, NULL, NULL, NULL),
    #endif
    	BT_GATT_CCC(lbslc_ccc_cfg_changed,
    		    BT_GATT_PERM_READ | BT_GATT_PERM_WRITE),
    	BT_GATT_CHARACTERISTIC(BT_UUID_LBS_LED,
    			       BT_GATT_CHRC_WRITE,
    			       BT_GATT_PERM_WRITE,
    			       NULL, write_led, NULL),
    BT_GATT_INCLUDE_SERVICE(my_secondary_svc.attrs),
    );
    
    int bt_lbs_init(struct bt_lbs_cb *callbacks)
    {
    	if (callbacks) {
    		lbs_cb.led_cb    = callbacks->led_cb;
    		lbs_cb.button_cb = callbacks->button_cb;
    	}
    
    	return 0;
    }
    
    int bt_lbs_send_button_state(bool button_state)
    {
    	if (!notify_enabled) {
    		return -EACCES;
    	}
    
    	return bt_gatt_notify(NULL, &lbs_svc.attrs[2],
    			      &button_state,
    			      sizeof(button_state));
    }
    

Reply
  • Hi Antoine,
    I haven't tried your code but this seems to work for me. I modified lbs.c to add a secondary service in, I can see the characteristic of the secondary service.
    There is some warning but at least it built.

    /*
     * Copyright (c) 2018 Nordic Semiconductor ASA
     *
     * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause
     */
    
    /** @file
     *  @brief LED Button Service (LBS) sample
     */
    
    #include <zephyr/types.h>
    #include <stddef.h>
    #include <string.h>
    #include <errno.h>
    #include <zephyr/sys/printk.h>
    #include <zephyr/sys/byteorder.h>
    #include <zephyr/kernel.h>
    
    #include <zephyr/bluetooth/bluetooth.h>
    #include <zephyr/bluetooth/hci.h>
    #include <zephyr/bluetooth/conn.h>
    #include <zephyr/bluetooth/uuid.h>
    #include <zephyr/bluetooth/gatt.h>
    
    #include <bluetooth/services/lbs.h>
    
    #include <zephyr/logging/log.h>
    
    LOG_MODULE_REGISTER(bt_lbs, CONFIG_BT_LBS_LOG_LEVEL);
    
    static bool                   notify_enabled;
    #ifdef CONFIG_BT_LBS_POLL_BUTTON
    static bool                   button_state;
    #endif
    static struct bt_lbs_cb       lbs_cb;
    
    static void lbslc_ccc_cfg_changed(const struct bt_gatt_attr *attr,
    				  uint16_t value)
    {
    	notify_enabled = (value == BT_GATT_CCC_NOTIFY);
    }
    
    static ssize_t write_led(struct bt_conn *conn,
    			 const struct bt_gatt_attr *attr,
    			 const void *buf,
    			 uint16_t len, uint16_t offset, uint8_t flags)
    {
    	LOG_DBG("Attribute write, handle: %u, conn: %p", attr->handle,
    		(void *)conn);
    
    	if (len != 1U) {
    		LOG_DBG("Write led: Incorrect data length");
    		return BT_GATT_ERR(BT_ATT_ERR_INVALID_ATTRIBUTE_LEN);
    	}
    
    	if (offset != 0) {
    		LOG_DBG("Write led: Incorrect data offset");
    		return BT_GATT_ERR(BT_ATT_ERR_INVALID_OFFSET);
    	}
    
    	if (lbs_cb.led_cb) {
    		uint8_t val = *((uint8_t *)buf);
    
    		if (val == 0x00 || val == 0x01) {
    			lbs_cb.led_cb(val ? true : false);
    		} else {
    			LOG_DBG("Write led: Incorrect value");
    			return BT_GATT_ERR(BT_ATT_ERR_VALUE_NOT_ALLOWED);
    		}
    	}
    
    	return len;
    }
    
    #ifdef CONFIG_BT_LBS_POLL_BUTTON
    static ssize_t read_button(struct bt_conn *conn,
    			  const struct bt_gatt_attr *attr,
    			  void *buf,
    			  uint16_t len,
    			  uint16_t offset)
    {
    	const char *value = attr->user_data;
    
    	LOG_DBG("Attribute read, handle: %u, conn: %p", attr->handle,
    		(void *)conn);
    
    	if (lbs_cb.button_cb) {
    		button_state = lbs_cb.button_cb();
    		return bt_gatt_attr_read(conn, attr, buf, len, offset, value,
    					 sizeof(*value));
    	}
    
    	return 0;
    }
    #endif
    
    #define BT_UUID_CUSTOM_SECONDARY_VAL \
    	BT_UUID_128_ENCODE(0x12345678, 0x1234, 0x5678, 0x1234, 0x56789abcdef0)
    
    static struct bt_uuid_128 my_secondary_uuid = BT_UUID_INIT_128(BT_UUID_CUSTOM_SECONDARY_VAL);
    
    /* A characteristic within the secondary service */
    static uint8_t sec_value = 0x42;
    
    static ssize_t read_sec_value(struct bt_conn *conn,
    			      const struct bt_gatt_attr *attr,
    			      void *buf, uint16_t len, uint16_t offset)
    {
    	const uint8_t *value = attr->user_data;
    	return bt_gatt_attr_read(conn, attr, buf, len, offset, value,
    				 sizeof(*value));
    }
    BT_GATT_SERVICE_DEFINE(my_secondary_svc,
    	BT_GATT_SECONDARY_SERVICE(&my_secondary_uuid),
    	BT_GATT_CHARACTERISTIC(BT_UUID_DECLARE_16(0x2A19),
    			       BT_GATT_CHRC_READ,
    			       BT_GATT_PERM_READ,
    			       read_sec_value, NULL, &sec_value),
    );
    
    
    /* LED Button Service Declaration */
    BT_GATT_SERVICE_DEFINE(lbs_svc,
    BT_GATT_PRIMARY_SERVICE(BT_UUID_LBS),
    #ifdef CONFIG_BT_LBS_POLL_BUTTON
    	BT_GATT_CHARACTERISTIC(BT_UUID_LBS_BUTTON,
    			       BT_GATT_CHRC_READ | BT_GATT_CHRC_NOTIFY,
    			       BT_GATT_PERM_READ, read_button, NULL,
    			       &button_state),
    #else
    	BT_GATT_CHARACTERISTIC(BT_UUID_LBS_BUTTON,
    			       BT_GATT_CHRC_READ | BT_GATT_CHRC_NOTIFY,
    			       BT_GATT_PERM_READ, NULL, NULL, NULL),
    #endif
    	BT_GATT_CCC(lbslc_ccc_cfg_changed,
    		    BT_GATT_PERM_READ | BT_GATT_PERM_WRITE),
    	BT_GATT_CHARACTERISTIC(BT_UUID_LBS_LED,
    			       BT_GATT_CHRC_WRITE,
    			       BT_GATT_PERM_WRITE,
    			       NULL, write_led, NULL),
    BT_GATT_INCLUDE_SERVICE(my_secondary_svc.attrs),
    );
    
    int bt_lbs_init(struct bt_lbs_cb *callbacks)
    {
    	if (callbacks) {
    		lbs_cb.led_cb    = callbacks->led_cb;
    		lbs_cb.button_cb = callbacks->button_cb;
    	}
    
    	return 0;
    }
    
    int bt_lbs_send_button_state(bool button_state)
    {
    	if (!notify_enabled) {
    		return -EACCES;
    	}
    
    	return bt_gatt_notify(NULL, &lbs_svc.attrs[2],
    			      &button_state,
    			      sizeof(button_state));
    }
    

Children
No Data
Related