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,

    I think there were some problems with my code, here is corrected main.c:

    /*
     * Copyright (c) 2018 Nordic Semiconductor ASA
     *
     * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause
     */
    
    #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/drivers/gpio.h>
    #include <soc.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/settings/settings.h>
    
    #include <dk_buttons_and_leds.h>
    
    #define DEVICE_NAME             CONFIG_BT_DEVICE_NAME
    #define DEVICE_NAME_LEN         (sizeof(DEVICE_NAME) - 1)
    
    
    #define RUN_STATUS_LED          DK_LED1
    #define CON_STATUS_LED          DK_LED2
    #define RUN_LED_BLINK_INTERVAL  1000
    
    #define USER_LED                DK_LED3
    
    #define USER_BUTTON             DK_BTN1_MSK
    
    static bool app_button_state;
    
    static const struct bt_data ad[] = {
    	BT_DATA_BYTES(BT_DATA_FLAGS, (BT_LE_AD_GENERAL | BT_LE_AD_NO_BREDR)),
    	BT_DATA(BT_DATA_NAME_COMPLETE, DEVICE_NAME, DEVICE_NAME_LEN),
    };
    
    static const struct bt_data sd[] = {
    	BT_DATA_BYTES(BT_DATA_UUID128_ALL, BT_UUID_LBS_VAL),
    };
    
    static void connected(struct bt_conn *conn, uint8_t err)
    {
    	if (err) {
    		printk("Connection failed, err 0x%02x %s\n", err, bt_hci_err_to_str(err));
    		return;
    	}
    
    	printk("Connected\n");
    
    	dk_set_led_on(CON_STATUS_LED);
    }
    
    static void disconnected(struct bt_conn *conn, uint8_t reason)
    {
    	printk("Disconnected, reason 0x%02x %s\n", reason, bt_hci_err_to_str(reason));
    
    	dk_set_led_off(CON_STATUS_LED);
    }
    
    #ifdef CONFIG_BT_LBS_SECURITY_ENABLED
    static void security_changed(struct bt_conn *conn, bt_security_t level,
    			     enum bt_security_err err)
    {
    	char addr[BT_ADDR_LE_STR_LEN];
    
    	bt_addr_le_to_str(bt_conn_get_dst(conn), addr, sizeof(addr));
    
    	if (!err) {
    		printk("Security changed: %s level %u\n", addr, level);
    	} else {
    		printk("Security failed: %s level %u err %d %s\n", addr, level, err,
    		       bt_security_err_to_str(err));
    	}
    }
    #endif
    
    BT_CONN_CB_DEFINE(conn_callbacks) = {
    	.connected        = connected,
    	.disconnected     = disconnected,
    #ifdef CONFIG_BT_LBS_SECURITY_ENABLED
    	.security_changed = security_changed,
    #endif
    };
    
    #if defined(CONFIG_BT_LBS_SECURITY_ENABLED)
    static void auth_passkey_display(struct bt_conn *conn, unsigned int passkey)
    {
    	char addr[BT_ADDR_LE_STR_LEN];
    
    	bt_addr_le_to_str(bt_conn_get_dst(conn), addr, sizeof(addr));
    
    	printk("Passkey for %s: %06u\n", addr, passkey);
    }
    
    static void auth_cancel(struct bt_conn *conn)
    {
    	char addr[BT_ADDR_LE_STR_LEN];
    
    	bt_addr_le_to_str(bt_conn_get_dst(conn), addr, sizeof(addr));
    
    	printk("Pairing cancelled: %s\n", addr);
    }
    
    static void pairing_complete(struct bt_conn *conn, bool bonded)
    {
    	char addr[BT_ADDR_LE_STR_LEN];
    
    	bt_addr_le_to_str(bt_conn_get_dst(conn), addr, sizeof(addr));
    
    	printk("Pairing completed: %s, bonded: %d\n", addr, bonded);
    }
    
    static void pairing_failed(struct bt_conn *conn, enum bt_security_err reason)
    {
    	char addr[BT_ADDR_LE_STR_LEN];
    
    	bt_addr_le_to_str(bt_conn_get_dst(conn), addr, sizeof(addr));
    
    	printk("Pairing failed conn: %s, reason %d %s\n", addr, reason,
    	       bt_security_err_to_str(reason));
    }
    
    static struct bt_conn_auth_cb conn_auth_callbacks = {
    	.passkey_display = auth_passkey_display,
    	.cancel = auth_cancel,
    };
    
    static struct bt_conn_auth_info_cb conn_auth_info_callbacks = {
    	.pairing_complete = pairing_complete,
    	.pairing_failed = pairing_failed
    };
    #else
    static struct bt_conn_auth_cb conn_auth_callbacks;
    static struct bt_conn_auth_info_cb conn_auth_info_callbacks;
    #endif
    
    static void app_led_cb(bool led_state)
    {
    	dk_set_led(USER_LED, led_state);
    }
    
    static bool app_button_cb(void)
    {
    	return app_button_state;
    }
    
    static struct bt_lbs_cb lbs_callbacs = {
    	.led_cb    = app_led_cb,
    	.button_cb = app_button_cb,
    };
    
    static void button_changed(uint32_t button_state, uint32_t has_changed)
    {
    	if (has_changed & USER_BUTTON) {
    		uint32_t user_button_state = button_state & USER_BUTTON;
    
    		bt_lbs_send_button_state(user_button_state);
    		app_button_state = user_button_state ? true : false;
    	}
    }
    
    static int init_button(void)
    {
    	int err;
    
    	err = dk_buttons_init(button_changed);
    	if (err) {
    		printk("Cannot init buttons (err: %d)\n", err);
    	}
    
    	return err;
    }
    
    /***************** multiple DIS begin *****************/
    /* Helpers de lecture (string et PnP) */
    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);
    }
    
    /* DIS primaire (appareil) : objets à adresse connue à la compilation */
    static char dis_primary_manufacturer[] = "WIKA";
    static char dis_primary_model[]        = "Nordic";
    
    /* DIS secondaire (module) */
    static char dis_module_manufacturer[] = "WIKA";
    static char dis_module_model[]        = "STM";
    
    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 dis_attrs[] = {
        BT_GATT_PRIMARY_SERVICE(BT_UUID_DIS),
    
        BT_GATT_CHARACTERISTIC(BT_UUID_DIS_MANUFACTURER_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),
    };
    
    static struct bt_gatt_service dis_svc = {
    	.attrs = dis_attrs,
    	.attr_count = ARRAY_SIZE(dis_attrs),
    };
    
    static struct bt_gatt_attr dis2_attrs[] = {
        BT_GATT_SECONDARY_SERVICE(BT_UUID_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_UUID_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);
    
    static struct bt_gatt_attr modx_attrs[] = {
        BT_GATT_PRIMARY_SERVICE(&module_x_uuid.uuid),
        // BT_GATT_CHARACTERISTIC(&modx_ver_uuid.uuid,
        //                        BT_GATT_CHRC_READ, BT_GATT_PERM_READ,
        //                        read_modx_ver, NULL, &modx_version),
    	BT_GATT_INCLUDE_SERVICE(dis2_attrs),    /* include runtime service */
    };
    static struct bt_gatt_service modx_svc = BT_GATT_SERVICE(modx_attrs);
    /***************** multiple DIS end *******************/
    
    int main(void)
    {
    	int blink_status = 0;
    	int err;
    
    /***************** multiple DIS begin *****************/
    	printk("Starting Bluetooth Peripheral LBS example\n");
    
    	err = bt_gatt_service_register(&dis_svc);
    	if (err) {
    		printk("Bluetooth secondary service init failed (err %d)\n", err);
    	}
    	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);
    	}
    /***************** multiple DIS end *******************/	
    
    	err = dk_leds_init();
    	if (err) {
    		printk("LEDs init failed (err %d)\n", err);
    		return 0;
    	}
    
    	err = init_button();
    	if (err) {
    		printk("Button init failed (err %d)\n", err);
    		return 0;
    	}
    
    	if (IS_ENABLED(CONFIG_BT_LBS_SECURITY_ENABLED)) {
    		err = bt_conn_auth_cb_register(&conn_auth_callbacks);
    		if (err) {
    			printk("Failed to register authorization callbacks.\n");
    			return 0;
    		}
    
    		err = bt_conn_auth_info_cb_register(&conn_auth_info_callbacks);
    		if (err) {
    			printk("Failed to register authorization info callbacks.\n");
    			return 0;
    		}
    	}
    
    	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_lbs_init(&lbs_callbacs);
    	if (err) {
    		printk("Failed to init LBS (err:%d)\n", err);
    		return 0;
    	}
    
    	err = bt_le_adv_start(BT_LE_ADV_CONN, ad, ARRAY_SIZE(ad),
    			      sd, ARRAY_SIZE(sd));
    	if (err) {
    		printk("Advertising failed to start (err %d)\n", err);
    		return 0;
    	}
    
    	printk("Advertising successfully started\n");
    
    	for (;;) {
    		dk_set_led(RUN_STATUS_LED, (++blink_status) % 2);
    		k_sleep(K_MSEC(RUN_LED_BLINK_INTERVAL));
    	}
    }
    

    We have to add it also in prj.conf: CONFIG_BT_GATT_DYNAMIC_DB=y

    Now I can see my secondary service DIS, included by my custom service:

    One question i have is : how can a user know that this secondary service DIS is linked to the custom service ? in the NRFConnect app, we don't really see the link

Reply
  • Hi,

    I think there were some problems with my code, here is corrected main.c:

    /*
     * Copyright (c) 2018 Nordic Semiconductor ASA
     *
     * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause
     */
    
    #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/drivers/gpio.h>
    #include <soc.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/settings/settings.h>
    
    #include <dk_buttons_and_leds.h>
    
    #define DEVICE_NAME             CONFIG_BT_DEVICE_NAME
    #define DEVICE_NAME_LEN         (sizeof(DEVICE_NAME) - 1)
    
    
    #define RUN_STATUS_LED          DK_LED1
    #define CON_STATUS_LED          DK_LED2
    #define RUN_LED_BLINK_INTERVAL  1000
    
    #define USER_LED                DK_LED3
    
    #define USER_BUTTON             DK_BTN1_MSK
    
    static bool app_button_state;
    
    static const struct bt_data ad[] = {
    	BT_DATA_BYTES(BT_DATA_FLAGS, (BT_LE_AD_GENERAL | BT_LE_AD_NO_BREDR)),
    	BT_DATA(BT_DATA_NAME_COMPLETE, DEVICE_NAME, DEVICE_NAME_LEN),
    };
    
    static const struct bt_data sd[] = {
    	BT_DATA_BYTES(BT_DATA_UUID128_ALL, BT_UUID_LBS_VAL),
    };
    
    static void connected(struct bt_conn *conn, uint8_t err)
    {
    	if (err) {
    		printk("Connection failed, err 0x%02x %s\n", err, bt_hci_err_to_str(err));
    		return;
    	}
    
    	printk("Connected\n");
    
    	dk_set_led_on(CON_STATUS_LED);
    }
    
    static void disconnected(struct bt_conn *conn, uint8_t reason)
    {
    	printk("Disconnected, reason 0x%02x %s\n", reason, bt_hci_err_to_str(reason));
    
    	dk_set_led_off(CON_STATUS_LED);
    }
    
    #ifdef CONFIG_BT_LBS_SECURITY_ENABLED
    static void security_changed(struct bt_conn *conn, bt_security_t level,
    			     enum bt_security_err err)
    {
    	char addr[BT_ADDR_LE_STR_LEN];
    
    	bt_addr_le_to_str(bt_conn_get_dst(conn), addr, sizeof(addr));
    
    	if (!err) {
    		printk("Security changed: %s level %u\n", addr, level);
    	} else {
    		printk("Security failed: %s level %u err %d %s\n", addr, level, err,
    		       bt_security_err_to_str(err));
    	}
    }
    #endif
    
    BT_CONN_CB_DEFINE(conn_callbacks) = {
    	.connected        = connected,
    	.disconnected     = disconnected,
    #ifdef CONFIG_BT_LBS_SECURITY_ENABLED
    	.security_changed = security_changed,
    #endif
    };
    
    #if defined(CONFIG_BT_LBS_SECURITY_ENABLED)
    static void auth_passkey_display(struct bt_conn *conn, unsigned int passkey)
    {
    	char addr[BT_ADDR_LE_STR_LEN];
    
    	bt_addr_le_to_str(bt_conn_get_dst(conn), addr, sizeof(addr));
    
    	printk("Passkey for %s: %06u\n", addr, passkey);
    }
    
    static void auth_cancel(struct bt_conn *conn)
    {
    	char addr[BT_ADDR_LE_STR_LEN];
    
    	bt_addr_le_to_str(bt_conn_get_dst(conn), addr, sizeof(addr));
    
    	printk("Pairing cancelled: %s\n", addr);
    }
    
    static void pairing_complete(struct bt_conn *conn, bool bonded)
    {
    	char addr[BT_ADDR_LE_STR_LEN];
    
    	bt_addr_le_to_str(bt_conn_get_dst(conn), addr, sizeof(addr));
    
    	printk("Pairing completed: %s, bonded: %d\n", addr, bonded);
    }
    
    static void pairing_failed(struct bt_conn *conn, enum bt_security_err reason)
    {
    	char addr[BT_ADDR_LE_STR_LEN];
    
    	bt_addr_le_to_str(bt_conn_get_dst(conn), addr, sizeof(addr));
    
    	printk("Pairing failed conn: %s, reason %d %s\n", addr, reason,
    	       bt_security_err_to_str(reason));
    }
    
    static struct bt_conn_auth_cb conn_auth_callbacks = {
    	.passkey_display = auth_passkey_display,
    	.cancel = auth_cancel,
    };
    
    static struct bt_conn_auth_info_cb conn_auth_info_callbacks = {
    	.pairing_complete = pairing_complete,
    	.pairing_failed = pairing_failed
    };
    #else
    static struct bt_conn_auth_cb conn_auth_callbacks;
    static struct bt_conn_auth_info_cb conn_auth_info_callbacks;
    #endif
    
    static void app_led_cb(bool led_state)
    {
    	dk_set_led(USER_LED, led_state);
    }
    
    static bool app_button_cb(void)
    {
    	return app_button_state;
    }
    
    static struct bt_lbs_cb lbs_callbacs = {
    	.led_cb    = app_led_cb,
    	.button_cb = app_button_cb,
    };
    
    static void button_changed(uint32_t button_state, uint32_t has_changed)
    {
    	if (has_changed & USER_BUTTON) {
    		uint32_t user_button_state = button_state & USER_BUTTON;
    
    		bt_lbs_send_button_state(user_button_state);
    		app_button_state = user_button_state ? true : false;
    	}
    }
    
    static int init_button(void)
    {
    	int err;
    
    	err = dk_buttons_init(button_changed);
    	if (err) {
    		printk("Cannot init buttons (err: %d)\n", err);
    	}
    
    	return err;
    }
    
    /***************** multiple DIS begin *****************/
    /* Helpers de lecture (string et PnP) */
    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);
    }
    
    /* DIS primaire (appareil) : objets à adresse connue à la compilation */
    static char dis_primary_manufacturer[] = "WIKA";
    static char dis_primary_model[]        = "Nordic";
    
    /* DIS secondaire (module) */
    static char dis_module_manufacturer[] = "WIKA";
    static char dis_module_model[]        = "STM";
    
    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 dis_attrs[] = {
        BT_GATT_PRIMARY_SERVICE(BT_UUID_DIS),
    
        BT_GATT_CHARACTERISTIC(BT_UUID_DIS_MANUFACTURER_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),
    };
    
    static struct bt_gatt_service dis_svc = {
    	.attrs = dis_attrs,
    	.attr_count = ARRAY_SIZE(dis_attrs),
    };
    
    static struct bt_gatt_attr dis2_attrs[] = {
        BT_GATT_SECONDARY_SERVICE(BT_UUID_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_UUID_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);
    
    static struct bt_gatt_attr modx_attrs[] = {
        BT_GATT_PRIMARY_SERVICE(&module_x_uuid.uuid),
        // BT_GATT_CHARACTERISTIC(&modx_ver_uuid.uuid,
        //                        BT_GATT_CHRC_READ, BT_GATT_PERM_READ,
        //                        read_modx_ver, NULL, &modx_version),
    	BT_GATT_INCLUDE_SERVICE(dis2_attrs),    /* include runtime service */
    };
    static struct bt_gatt_service modx_svc = BT_GATT_SERVICE(modx_attrs);
    /***************** multiple DIS end *******************/
    
    int main(void)
    {
    	int blink_status = 0;
    	int err;
    
    /***************** multiple DIS begin *****************/
    	printk("Starting Bluetooth Peripheral LBS example\n");
    
    	err = bt_gatt_service_register(&dis_svc);
    	if (err) {
    		printk("Bluetooth secondary service init failed (err %d)\n", err);
    	}
    	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);
    	}
    /***************** multiple DIS end *******************/	
    
    	err = dk_leds_init();
    	if (err) {
    		printk("LEDs init failed (err %d)\n", err);
    		return 0;
    	}
    
    	err = init_button();
    	if (err) {
    		printk("Button init failed (err %d)\n", err);
    		return 0;
    	}
    
    	if (IS_ENABLED(CONFIG_BT_LBS_SECURITY_ENABLED)) {
    		err = bt_conn_auth_cb_register(&conn_auth_callbacks);
    		if (err) {
    			printk("Failed to register authorization callbacks.\n");
    			return 0;
    		}
    
    		err = bt_conn_auth_info_cb_register(&conn_auth_info_callbacks);
    		if (err) {
    			printk("Failed to register authorization info callbacks.\n");
    			return 0;
    		}
    	}
    
    	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_lbs_init(&lbs_callbacs);
    	if (err) {
    		printk("Failed to init LBS (err:%d)\n", err);
    		return 0;
    	}
    
    	err = bt_le_adv_start(BT_LE_ADV_CONN, ad, ARRAY_SIZE(ad),
    			      sd, ARRAY_SIZE(sd));
    	if (err) {
    		printk("Advertising failed to start (err %d)\n", err);
    		return 0;
    	}
    
    	printk("Advertising successfully started\n");
    
    	for (;;) {
    		dk_set_led(RUN_STATUS_LED, (++blink_status) % 2);
    		k_sleep(K_MSEC(RUN_LED_BLINK_INTERVAL));
    	}
    }
    

    We have to add it also in prj.conf: CONFIG_BT_GATT_DYNAMIC_DB=y

    Now I can see my secondary service DIS, included by my custom service:

    One question i have is : how can a user know that this secondary service DIS is linked to the custom service ? in the NRFConnect app, we don't really see the link

Children
  • Hi Antoine, 
    When you include a secondary service into the primary service, there will be an entry in the primary service called "Included service" as show below. When you do service discovery you should be able to read it. It contain the start and end handle value of the secondary service: 


    I don't think we have good support for this secondary support feature in nRF Connect app. That's why you don't see the nested hierarchy in the app. We barely have customer using this feature actually. Most of the applications can be solved with just primary service. 

Related