nRF Connect SDK:Directed Advertising完全版

# 前回までのあらすじ
こちらにあります。

https://qiita.com/yoshihiro0709/items/88c5596b1b2050e74a35

# 完全に理解した！
かどうかは分からないのですが、今回はCentral/Peripheral双方をNCSで記述しています。また、双方ともに「ボンディングがない状態」と「ボンディングがある状態」を使い分ける必要があります。
なお、このプロジェクトを実行するには評価ボードが2台必要です。
## Peripheral
以下、Peripheralのソースです。読むと非常に大変なのでとりあえず何も考えずにコピーして動かしてみるのがよいと思います(笑)。
拡張アドバタイズを使っているのと、標準サービスのうちBASとDISを持たせていてCentralと接続したら５秒毎にBASのNotifyでバッテリー残量(のエミュレーター)を送信します。

``` main.c
/*
 * Copyright (c) 2012-2014 Wind River Systems, Inc.
 *
 * SPDX-License-Identifier: Apache-2.0
 */

#include <stdio.h>
#include <dk_buttons_and_leds.h>
#include <zephyr/bluetooth/bluetooth.h>
#include <zephyr/bluetooth/gatt.h>
#include <zephyr/bluetooth/services/bas.h>
#include <zephyr/kernel.h>
#include <zephyr/logging/log.h>
#include <zephyr/settings/settings.h>

LOG_MODULE_REGISTER(main, LOG_LEVEL_INF);

// Threshold
#define BATTERY_THRESHOLD_LOW           30

// Extended advertising
static const struct bt_data ad[] = {
    BT_DATA_BYTES(BT_DATA_FLAGS, (BT_LE_AD_GENERAL | BT_LE_AD_NO_BREDR)),
    BT_DATA_BYTES(BT_DATA_UUID16_ALL, BT_UUID_16_ENCODE(BT_UUID_BAS_VAL), BT_UUID_16_ENCODE(BT_UUID_DIS_VAL)),
    BT_DATA(BT_DATA_NAME_COMPLETE, CONFIG_BT_DEVICE_NAME, (sizeof(CONFIG_BT_DEVICE_NAME) - 1)),
};
static const struct bt_data sd[] = {
};
// User queue
static struct k_work_q uq_button[4];
K_THREAD_STACK_DEFINE(us_button1, 2048);
K_THREAD_STACK_DEFINE(us_battery, 1024);
// Pairing
struct pairing_data_mitm {
	struct bt_conn *conn;
	unsigned int passkey;
};
K_MSGQ_DEFINE(mitm_queue, sizeof(struct pairing_data_mitm), 1, 4);

// Global
static struct bt_conn *conn_object = NULL;
static bt_addr_le_t bond_addr;

/* ----- Error Function Start ----- */
void fatal_error(void)
{
    uint32_t state = DK_ALL_LEDS_MSK;

    while (1) {
        dk_set_leds(state);
        if (state == DK_ALL_LEDS_MSK) {
            state = DK_NO_LEDS_MSK;
        } else {
            state = DK_ALL_LEDS_MSK;
        }
        k_sleep(K_MSEC(500));
    }
}
/* ----- Error Function End ----- */

/* ----- Battery Thread Start ----- */
// Update battery value
static uint16_t battery = 100;
static void update_battery_work_thread(void *p1, void *p2, void *p3)
{
    int err;

    while (1) {
        // Notify
        err = bt_bas_set_battery_level(battery);
        if (err) {
            LOG_ERR("Failed to update BAS.(%d)", err);
            return;
        }

        // Display Battery LED
        if (battery > BATTERY_THRESHOLD_LOW) {
            LOG_INF("Battery is good(%d%%)", battery);
        } else {
            LOG_INF("Battery is low(%d%%)", battery);
        }
        battery = ((battery + 5) % 100);

        // Wait for next
        k_sleep(K_SECONDS(3));
    }
}
K_THREAD_DEFINE(update_battery, 1024, update_battery_work_thread, NULL, NULL, NULL, K_PRIO_PREEMPT(15), 0, 0);
/* ----- Battery Thread End ----- */

/* ----- Advertising Function Start ----- */
static void copy_last_bonded_addr(const struct bt_bond_info *info, void *data)
{
	bt_addr_le_copy(&bond_addr, &info->addr);
}

static struct bt_le_ext_adv *ext_adv = NULL;
static void advertising_work_handler(struct k_work *work)
{
    int err;
    struct bt_le_adv_param adv_param;
	struct bt_le_ext_adv_start_param ext_adv_start_param = {0};
	char addr[BT_ADDR_LE_STR_LEN];

    if (ext_adv) {
        err = bt_le_ext_adv_stop(ext_adv);
        if (err) {
            LOG_ERR("Failed to stop extended advertising(%d)", err);
            return;
        }
        err = bt_le_ext_adv_delete(ext_adv);
        if (err) {
            LOG_ERR("Failed to delete advertising set(%d)", err);
            return;
        }
    }

	bt_addr_le_copy(&bond_addr, BT_ADDR_LE_NONE);
	bt_foreach_bond(BT_ID_DEFAULT, copy_last_bonded_addr, NULL);
	if (bt_addr_le_cmp(&bond_addr, BT_ADDR_LE_NONE) != 0) {
        // Directed Advertising
		bt_addr_le_to_str(&bond_addr, addr, sizeof(addr));
		LOG_INF("Direct advertising to %s", addr);

		adv_param = *BT_LE_ADV_CONN_DIR_LOW_DUTY(&bond_addr);
		adv_param.options |= BT_LE_ADV_OPT_DIR_ADDR_RPA;
		err = bt_le_adv_start(&adv_param, NULL, 0, NULL, 0);
        if (err) {
            LOG_ERR("Failed to start advertising.(%d)", err);
            return;
        }
        LOG_INF("Start directed advertising.");
	} else {
        // Undirected Extended Advertising
        LOG_INF("Start advertising with extension.");

        // Fast Advertising with extension
        adv_param = *BT_LE_ADV_PARAM(
            BT_LE_ADV_OPT_CONNECTABLE | BT_LE_ADV_OPT_ONE_TIME | BT_LE_ADV_OPT_EXT_ADV,
            0x0140,   // 0x140(320) * 0.625 = 200(msec)
            0x0140,   // 0x140(320) * 0.625 = 200(msec)
            NULL
        );

        err = bt_le_ext_adv_create(&adv_param, NULL, &ext_adv);
        if (err) {
            LOG_ERR("Failed to create advertiser set.(%d)", err);
            return;
        }
        err = bt_le_ext_adv_set_data(ext_adv, ad, ARRAY_SIZE(ad), sd, ARRAY_SIZE(sd));
        if (err) {
            LOG_ERR("Failed to set advertising data.(%d)", err);
            return;
        }
        err = bt_le_ext_adv_start(ext_adv, &ext_adv_start_param);
        if (err) {
            LOG_ERR("Failed to start advertising.(%d)", err);
            return;
        }
    }
}
K_WORK_DELAYABLE_DEFINE(advertising, advertising_work_handler);
/* ----- Advertising Function End ----- */

/* ----- Bluetooth Function Start ----- */
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) {
		LOG_INF("Security changed: %s level %u", addr, level);
	} else {
		LOG_ERR("Security failed: %s level %u err %d", addr, level, err);
	}
}

static void le_param_updated(struct bt_conn *conn, uint16_t interval, uint16_t latency, uint16_t timeout)
{
	char addr[BT_ADDR_LE_STR_LEN];

	bt_addr_le_to_str(bt_conn_get_dst(conn), addr, sizeof(addr));
	LOG_INF("LE conn param updated: %s int 0x%04x lat %d to %d", addr, interval, latency, timeout);
}

static bool le_param_req(struct bt_conn *conn, struct bt_le_conn_param *param)
{
	char addr[BT_ADDR_LE_STR_LEN];

	bt_addr_le_to_str(bt_conn_get_dst(conn), addr, sizeof(addr));
	LOG_INF("LE conn param req: %s int (0x%04x, 0x%04x) lat %d to %d", addr, param->interval_min, param->interval_max, param->latency, param->timeout);

	return true;
}

static void disconnected(struct bt_conn *conn, uint8_t reason)
{
    LOG_INF("Disconnected");

    conn_object = NULL;
    // Battery thread
    k_thread_suspend(update_battery);
    // Restart Advertising
    k_work_schedule(&advertising, K_NO_WAIT);
}

static void connected(struct bt_conn *conn, uint8_t err)
{
	if (err) {
		LOG_ERR("Failed to connect.(%d)", err);
	} else {
		LOG_INF("Connected");

        // Security Connection
        err = bt_conn_set_security(conn, BT_SECURITY_L2);
        if (err) {
            LOG_ERR("Failed to set security.(%d).", err);
            return;
        }

        conn_object = conn;

        // Battery thread
        k_thread_resume(update_battery);
	}
}

BT_CONN_CB_DEFINE(conn_callbacks) = {
	.connected    = connected,
	.disconnected = disconnected,
    .identity_resolved = NULL,
    .le_param_req = le_param_req,
    .le_param_updated = le_param_updated,
    .security_changed = security_changed,
};
/* ----- Bluetooth Function End ----- */

static void button_holding_work_handler(struct k_work *work)
{
    int err;

    // Erase
    LOG_INF("Erase Bond Information!");
    err = bt_unpair(BT_ID_DEFAULT, NULL);
    if (err) {
        LOG_ERR("Failed to erase bond(s).");
    } else {
        // Reboot
        NVIC_SystemReset();
    }
}
K_WORK_DELAYABLE_DEFINE(button_holding, button_holding_work_handler);

void button_changed(uint32_t button_state, uint32_t has_changed)
{
    if (has_changed & 0x01) {
        if (button_state & has_changed) {
            LOG_INF("button 0 changes to on");
            if (conn_object == NULL) {
                // Try to erase bond when no connection.
                k_work_schedule_for_queue(&uq_button[0], &button_holding, K_MSEC(2000));
            }
        } else {
            LOG_INF("button 0 changes to off.");
            k_work_cancel_delayable(&button_holding);
        }
    }

#if DT_NODE_EXISTS(DT_ALIAS(sw1))
    if (has_changed & 0x02) {
        if (button_state & has_changed) {
            LOG_INF("button 1 changes to on");
        } else {
            LOG_INF("button 1 changes to off.");
        }
    }
#endif

#if DT_NODE_EXISTS(DT_ALIAS(sw2))
    if (has_changed & 0x04) {
        if (button_state & has_changed) {
            LOG_INF("button 2 changes to on");
        } else {
            LOG_INF("button 2 changes to off.");
        }
    }
#endif

#if DT_NODE_EXISTS(DT_ALIAS(sw3))
    if (has_changed & 0x08) {
        if (button_state & has_changed) {
            LOG_INF("button 3 changes to on");
        } else {
            LOG_INF("button 3 changes to off.");
        }
    }
#endif
}

void main(void)
{
    int err;
    int state = 0;

    // Initialize Buttons and LEDs
    err = dk_buttons_init(button_changed);
    if (err) {
        LOG_ERR("Failed to initialize button[s](%d)", err);
        fatal_error();
    }
    err = dk_leds_init();
    if (err) {
        LOG_ERR("Failed to initialize led[s](%d)", err);
        fatal_error();
    }

	// User queue for button(s) and led(s)
    k_work_queue_start(&uq_button[0], us_button1, K_THREAD_STACK_SIZEOF(us_button1), K_PRIO_PREEMPT(0), NULL);

    // Initialize Bluetooth
    err = bt_enable(NULL);
    if (err) {
        LOG_ERR("Failed to start blutooth.(%d)", err);
        fatal_error();
    }

    if (IS_ENABLED(CONFIG_SETTINGS)) {
        LOG_INF("Settings have loaded.");
        settings_load();
    }

    // Battery thread
    k_thread_suspend(update_battery);

    // Start Advertising
    k_work_schedule(&advertising, K_NO_WAIT);

	while (1) {
        if (state || conn_object) {
            dk_set_led_on(0);
            state = 0;
        } else {
            dk_set_led_off(0);
            state = 1;
        }
		k_sleep(K_MSEC(500));
	}
}
```

``` prj.conf
CONFIG_BT_DIS=y
CONFIG_BT_DIS_FW_REV=y
CONFIG_BT_DIS_FW_REV_STR="1.0.0"
CONFIG_BT_PERIPHERAL=y
CONFIG_BT_EXT_ADV=y
CONFIG_BT_DIS_SETTINGS=y
CONFIG_BT_DIS_MODEL="."
CONFIG_BT_DIS_MANUF="."
CONFIG_BT_DIS_HW_REV=y
CONFIG_BT_DIS_HW_REV_STR="nRF52840 DK"
CONFIG_BT_DIS_SW_REV=y
CONFIG_BT_DIS_SW_REV_STR="nRF Connect SDK 2.4.0"
CONFIG_BT_BAS=y
CONFIG_BT_SMP=y
CONFIG_BT_PRIVACY=y
CONFIG_BT_PRIVACY_RANDOMIZE_IR=y
CONFIG_BT_DEVICE_NAME="nRF52840 DK"
CONFIG_DK_LIBRARY=y
CONFIG_FLASH=y
CONFIG_BT=y
CONFIG_BT_CTLR_ADV_DATA_LEN_MAX=255
CONFIG_DEBUG_THREAD_INFO=y
CONFIG_NVS=y
CONFIG_LOG=y
CONFIG_FLASH_MAP=y
CONFIG_DEBUG_OPTIMIZATIONS=y
```

## Central
続いてCentral側のソースコードです。こちらも何も考えずにコピーして動かしてみるのがよいと思います。だって僕も説明するのめんどくさいですし(笑)。
Peripheral側がBASを持っている(と分かっている)ので接続と同時にBAS Clientとしてサービスを検索して接続します。なお、サービス検索のところでセマフォを使っていますが、複数のサービスを検索するときは１個ずつ接続する必要があるのでこのようになっています。

::: note
要するにここで公開しているソースの元になっているものはすでにあるということです、ええ
:::

ちなみにこのあとUSBにデータを流すといわゆるドングルってやつが作れるのですが、ここではそこまでのサポートはしません、というか今までの記事に出てきた内容をガッチャンコ(おっさんビジネス用語[死語])すればきっと作れます。

``` main.c
/*
 * Copyright (c) 2019 Nordic Semiconductor ASA
 *
 * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause
 */

#include <stdio.h>
#include <string.h>
#include <bluetooth/scan.h>
#include <bluetooth/services/bas_client.h>
#include <dk_buttons_and_leds.h>
#include <zephyr/bluetooth/bluetooth.h>
#include <zephyr/bluetooth/conn.h>
#include <zephyr/bluetooth/gatt.h>
#include <zephyr/bluetooth/hci.h>
#include <zephyr/drivers/gpio.h>
#include <zephyr/kernel.h>
#include <zephyr/logging/log.h>
#include <zephyr/settings/settings.h>

LOG_MODULE_REGISTER(main, LOG_LEVEL_INF);

#define BAS_READ_VALUE_INTERVAL (10 * MSEC_PER_SEC)
#define SCAN_INTERVAL 0x00A0 /* 0x00A0(160) * 0.625 = 100 msec */
#define SCAN_WINDOW   0x0050 /* 0x0050( 80) * 0.625 =  50 msec */

const struct bt_le_scan_param SCAN_PARAM_FILTER = {
	.interval = SCAN_INTERVAL,
	.interval_coded = 0,
	.options = BT_LE_SCAN_OPT_FILTER_ACCEPT_LIST,
	.timeout = 0,
	.type = BT_HCI_LE_SCAN_PASSIVE,
	.window = SCAN_WINDOW,
	.window_coded = 0,
};
const struct bt_le_scan_param SCAN_PARAM_NO_FILTER = {
	.interval = SCAN_INTERVAL,
	.interval_coded = 0,
	.options = BT_LE_SCAN_OPT_NONE,
	.timeout = 0,
	.type = BT_HCI_LE_SCAN_PASSIVE,
	.window = SCAN_WINDOW,
	.window_coded = 0,
};
const struct bt_le_conn_param COMM_PARAM = {
	.interval_min = BT_GAP_INIT_CONN_INT_MIN,
	.interval_max = BT_GAP_INIT_CONN_INT_MAX,
	.latency = 0,
	.timeout = 300,
};
const struct bt_scan_init_param SCAN_INIT_FILTER = {
	.connect_if_match = true,
	.conn_param = &COMM_PARAM,
	.scan_param = &SCAN_PARAM_FILTER,
};
const struct bt_scan_init_param SCAN_INIT_NO_FILTER = {
	.connect_if_match = true,
	.conn_param = &COMM_PARAM,
	.scan_param = &SCAN_PARAM_NO_FILTER,
};

// User queue
static struct k_work_q uq_button[4], uq_disc;
K_THREAD_STACK_DEFINE(us_button1, 2048);
K_THREAD_STACK_DEFINE(us_disc, 2048);
// for Semaphore
K_SEM_DEFINE(sem_discover, 0, 1);

static int16_t count_bond;
static struct bt_conn *conn_object;
static struct bt_bas_client bas_client;

/* ----- Error Function Start ----- */
void fatal_error(void)
{
    uint32_t state = DK_ALL_LEDS_MSK;

    while (1) {
        dk_set_leds(state);
        if (state == DK_ALL_LEDS_MSK) {
            state = DK_NO_LEDS_MSK;
        } else {
            state = DK_ALL_LEDS_MSK;
        }
        k_sleep(K_MSEC(500));
    }
}
/* ----- Error Function End ----- */

/* ----- Bluetooth Function Start ----- */
/* ----- BAS Callback Function Start ----- */
static void notify_battery_level_cb(struct bt_bas_client *bas, uint8_t battery_level)
{
	char addr[BT_ADDR_LE_STR_LEN];

	bt_addr_le_to_str(bt_conn_get_dst(bt_bas_conn(bas)), addr, sizeof(addr));
	if (battery_level == BT_BAS_VAL_INVALID) {
		LOG_INF("[%s] Battery notification aborted", addr);
	} else {
		LOG_INF("[%s] Battery notification: %"PRIu8"%%", addr, battery_level);
	}
}

static void read_battery_level_cb(struct bt_bas_client *bas, uint8_t battery_level, int err)
{
	char addr[BT_ADDR_LE_STR_LEN];

	bt_addr_le_to_str(bt_conn_get_dst(bt_bas_conn(bas)), addr, sizeof(addr));
	if (err) {
		LOG_ERR("Failed to read battery from %s.(%d)", addr, err);
		return;
	}

	LOG_INF("[%s] Battery read: %"PRIu8"%%", addr, battery_level);
}
/* ----- BAS Callback Function End ----- */

/* ----- BAS Discover Function Start ----- */
static void bas_discovery_completed_cb(struct bt_gatt_dm *dm, void *context)
{
	int err;
	struct bt_bas_client *bas = context;

	LOG_INF("The BAS discovery procedure succeeded.");

	bt_gatt_dm_data_print(dm);

	err = bt_bas_handles_assign(dm, bas);
	if (err) {
		LOG_ERR("Failed to init BAS client object.(%d)", err);
	}

	if (bt_bas_notify_supported(bas)) {
		err = bt_bas_subscribe_battery_level(bas, notify_battery_level_cb);
		if (err) {
			LOG_ERR("Failed to subscribe to BAS value notification.(%d)", err);
			/* Continue anyway */
		}
	} else {
		err = bt_bas_start_per_read_battery_level(bas, BAS_READ_VALUE_INTERVAL, notify_battery_level_cb);
		if (err) {
			LOG_ERR("Failed to start periodic read of BAS value.(%d)", err);
		}
	}

	err = bt_gatt_dm_data_release(dm);
	if (err) {
		LOG_ERR("Failed to release the discovery data.(%d)", err);
	}
	k_sem_give(&sem_discover);
}

static void bas_discovery_not_found_cb(struct bt_conn *conn, void *context)
{
	LOG_ERR("BAS Service not found.");
	k_sem_give(&sem_discover);
}

static void bas_discovery_error_found_cb(struct bt_conn *conn, int err, void *context)
{
	LOG_ERR("The BAS discovery procedure failed.(%d)", err);
	k_sem_give(&sem_discover);
}

static struct bt_gatt_dm_cb bas_discovery_cb = {
	.completed         = bas_discovery_completed_cb,
	.service_not_found = bas_discovery_not_found_cb,
	.error_found       = bas_discovery_error_found_cb,
};
/* ----- BAS Discover Function End ----- */

static void scan_start(void)
{
    int err;

	err = bt_scan_start(BT_SCAN_TYPE_SCAN_PASSIVE);
	if (err) {
		LOG_ERR("Failed to start scanning.(%d)", err);
		fatal_error();
	} else {
		LOG_INF("Start scanning.");
	}
}

static void scan_filter_match(struct bt_scan_device_info *device_info, struct bt_scan_filter_match *filter_match, bool connectable)
{
	char addr[BT_ADDR_LE_STR_LEN];

	// For debug info.
	bt_addr_le_to_str(device_info->recv_info->addr, addr, sizeof(addr));
	LOG_INF("Filters matched. Address: %s connectable: %s", addr, connectable ? "yes" : "no");
	LOG_HEXDUMP_DBG(device_info->adv_data->data, device_info->adv_data->len, "ADV:");
}

static void scan_connecting_error(struct bt_scan_device_info *device_info)
{
	LOG_ERR("Connecting failed.");
}

static void scan_connecting(struct bt_scan_device_info *device_info, struct bt_conn *conn)
{
	LOG_INF("Scan connecting.");
}

static void scan_filter_no_match(struct bt_scan_device_info *device_info, bool connectable)
{
	int err;
	char addr[BT_ADDR_LE_STR_LEN];

	bt_addr_le_to_str(device_info->recv_info->addr, addr, sizeof(addr));
	LOG_DBG("Filters no match. Address: %s connectable: %s", addr, connectable ? "yes" : "no");

	if (device_info->recv_info->adv_type == BT_GAP_ADV_TYPE_ADV_DIRECT_IND) {
		bt_addr_le_to_str(device_info->recv_info->addr, addr, sizeof(addr));
		LOG_INF("Direct advertising received from %s", addr);
		bt_scan_stop();

		err = bt_conn_le_create(device_info->recv_info->addr, BT_CONN_LE_CREATE_CONN, device_info->conn_param, &conn_object);
		if (err) {
			LOG_ERR("An error has occur!(%d)", err);
			fatal_error();
		}
	}
}
BT_SCAN_CB_INIT(scan_cb, scan_filter_match, scan_filter_no_match, scan_connecting_error, scan_connecting);

// GATT Discover procedure
static void gatt_discover_work_handler(struct k_work *work)
{
	int err;

	// BAS
	err = bt_gatt_dm_start(conn_object, BT_UUID_BAS, &bas_discovery_cb, &bas_client);
	if (err) {
		LOG_ERR("Could not start the discovery procedure for BAS.(%d)", err);
        fatal_error();
	}

    // Wait for semaphore
	// You can delete this semaphore if you use only one DM.
    err = k_sem_take(&sem_discover, K_MSEC(5000));
    if (err) {
        LOG_ERR("Semaphore timeout.(%d)", err);
        fatal_error();
    }
}
K_WORK_DELAYABLE_DEFINE(gatt_discover, gatt_discover_work_handler);

static void setup_accept_list_cb(const struct bt_bond_info *info, void *data)
{
	int err;

	err = bt_le_filter_accept_list_add(&info->addr);
	if (err) {
		LOG_ERR("Cannot add peer to Filter Accept List.(%d)", err);
        fatal_error();
	}
	LOG_INF("Added following peer to whitelist: %02X%02X%02X%02X%02X%02X", info->addr.a.val[0], info->addr.a.val[1], info->addr.a.val[2], info->addr.a.val[3], info->addr.a.val[4], info->addr.a.val[5]);
}

static void count_bond_cb(const struct bt_bond_info *info, void *data)
{
	int *count = data;

	// Count up
	(*count)++;
}

static void connected(struct bt_conn *conn, uint8_t conn_err)
{
	int err;
	char addr[BT_ADDR_LE_STR_LEN];

	bt_addr_le_to_str(bt_conn_get_dst(conn), addr, sizeof(addr));

	if (conn_err) {
		LOG_ERR("Failed to connect to %s.(%d)", addr, conn_err);

		// Restart scanning
		scan_start();
	} else if (conn == conn_object) {
		LOG_INF("Connected with directed advertising: %s", addr);

		// Security
		err = bt_conn_set_security(conn, BT_SECURITY_L2);
		if (err) {
			LOG_ERR("Failed to set security.(%d)", err);
			fatal_error();
		}
	} else {
		LOG_INF("Connected: %s", addr);
		conn_object = bt_conn_ref(conn);

		// Security
		err = bt_conn_set_security(conn, BT_SECURITY_L2);
		if (err) {
			LOG_ERR("Failed to set security.(%d)", err);
			fatal_error();
		}
	}
}

static void disconnected(struct bt_conn *conn, uint8_t reason)
{
	char addr[BT_ADDR_LE_STR_LEN];
	int err;

	bt_addr_le_to_str(bt_conn_get_dst(conn), addr, sizeof(addr));
	LOG_INF("Disconnected: %s (reason %u)", addr, reason);

	bt_conn_unref(conn_object);
	conn_object = NULL;

	// Stop scanning
	bt_scan_stop();

	// Apply bonding information
	if (count_bond == 0) {
		bt_foreach_bond(BT_ID_DEFAULT, count_bond_cb, &count_bond);
		LOG_INF("Scanning with bonds.");

		// Initialize
		bt_scan_params_set(&SCAN_PARAM_FILTER);

		// Filter for directed advertising
		err = bt_le_filter_accept_list_clear();
		if (err) {
			LOG_ERR("Failed to clear Filter Accept List.(%d)", err);
			fatal_error();
		}
		bt_foreach_bond(BT_ID_DEFAULT, setup_accept_list_cb, &count_bond);
	}

	// Restart scanning
	scan_start();
}

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) {
		LOG_INF("Security changed: %s level %u", addr, level);
	} else {
		LOG_ERR("Security failed: %s level %u err %d", addr, level, err);
		bt_conn_disconnect(conn, BT_HCI_ERR_AUTH_FAIL);
	}

	k_work_schedule_for_queue(&uq_disc, &gatt_discover, K_NO_WAIT);
}

BT_CONN_CB_DEFINE(conn_callbacks) = {
	.connected = connected,
	.disconnected = disconnected,
	.security_changed = security_changed,
};
/* ----- Bluetooth Function End ----- */

static void button_holding_work_handler(struct k_work *work)
{
    int err;

    // Erase
    LOG_INF("Erase Bond Information!");
    err = bt_unpair(BT_ID_DEFAULT, NULL);
    if (err) {
        LOG_ERR("Failed to erase bond(s).");
    } else {
        // Reboot
        NVIC_SystemReset();
    }
}
K_WORK_DELAYABLE_DEFINE(button_holding, button_holding_work_handler);

void button_changed(uint32_t button_state, uint32_t has_changed)
{
	int err;

    if (has_changed & 0x01) {
        if (button_state & has_changed) {
            LOG_INF("button 0 changes to on");
            if (conn_object == NULL) {
                // Try to erase bond when no connection.
                k_work_schedule_for_queue(&uq_button[0], &button_holding, K_MSEC(2000));
            } else {
				LOG_INF("Reading BAS value:");
				err = bt_bas_read_battery_level(&bas_client, read_battery_level_cb);
				if (err) {
					LOG_ERR("BAS read call error.(%d)", err);
				}
			}
        } else {
            LOG_INF("button 0 changes to off.");
            k_work_cancel_delayable(&button_holding);
        }
    }

#if DT_NODE_EXISTS(DT_ALIAS(sw1))
    if (has_changed & 0x02) {
        if (button_state & has_changed) {
            LOG_INF("button 1 changes to on");
        } else {
            LOG_INF("button 1 changes to off.");
        }
    }
#endif

#if DT_NODE_EXISTS(DT_ALIAS(sw2))
    if (has_changed & 0x04) {
        if (button_state & has_changed) {
            LOG_INF("button 2 changes to on");
        } else {
            LOG_INF("button 2 changes to off.");
        }
    }
#endif

#if DT_NODE_EXISTS(DT_ALIAS(sw3))
    if (has_changed & 0x08) {
        if (button_state & has_changed) {
            LOG_INF("button 3 changes to on");
        } else {
            LOG_INF("button 3 changes to off.");
        }
    }
#endif
}

void main(void)
{
	int err;
	int state = 0;

    // Initialize variable(s)
	count_bond = 0;
	conn_object = NULL;

    // Initialize Buttons and LEDs
    err = dk_buttons_init(button_changed);
    if (err) {
        LOG_ERR("Failed to initialize button[s](%d)", err);
        fatal_error();
    }
    err = dk_leds_init();
    if (err) {
        LOG_ERR("Failed to initialize led[s](%d)", err);
        fatal_error();
    }

	// User queue for button(s) and led(s)
    k_work_queue_start(&uq_button[0], us_button1, K_THREAD_STACK_SIZEOF(us_button1), K_PRIO_PREEMPT(0), NULL);
    k_work_queue_start(&uq_disc, us_disc, K_THREAD_STACK_SIZEOF(us_disc), K_PRIO_PREEMPT(0), NULL);

    // Initialize Bluetooth
	err = bt_enable(NULL);
	if (err) {
		LOG_ERR("Bluetooth init failed.(%d)", err);
		fatal_error();
	}
	LOG_INF("Bluetooth initialized.");

	// Settings
	err = settings_load();
	if (err) {
		LOG_ERR("Failed to load settings.(%d)", err);
		fatal_error();
	}
	LOG_INF("Settings has been loaded.");

	// BAS
	bt_bas_client_init(&bas_client);

	// Initialize for scanning
	bt_foreach_bond(BT_ID_DEFAULT, count_bond_cb, &count_bond);
	if (count_bond) {
		LOG_INF("Scanning with bonds.");

		// Initialize
		bt_scan_init(&SCAN_INIT_FILTER);
		bt_scan_cb_register(&scan_cb);

		// Filter for directed advertising
		err = bt_le_filter_accept_list_clear();
		if (err) {
			LOG_ERR("Cannot clear Filter Accept List.(%d)", err);
			fatal_error();
		}
		bt_foreach_bond(BT_ID_DEFAULT, setup_accept_list_cb, &count_bond);
	} else {
		LOG_INF("Scanning without bonds.");

		// Initialize
		bt_scan_init(&SCAN_INIT_NO_FILTER);
		bt_scan_cb_register(&scan_cb);

		// Filter for undirected advertising
		err = bt_scan_filter_add(BT_SCAN_FILTER_TYPE_UUID, BT_UUID_BAS);
		if (err) {
			LOG_ERR("Scanning filters for pants cannot be set.(%d)", err);
			fatal_error();
		}
		err = bt_scan_filter_add(BT_SCAN_FILTER_TYPE_UUID, BT_UUID_DIS);
		if (err) {
			LOG_ERR("Scanning filters for pants cannot be set.(%d)", err);
			fatal_error();
		}
		err = bt_scan_filter_enable(BT_SCAN_UUID_FILTER, true);
		if (err) {
			LOG_ERR("Filters cannot be turned on.(%d)", err);
			fatal_error();
		}
	}

	// Scan
	scan_start();

	// Main loop
	while (1) {
        if (state || conn_object) {
            dk_set_led_on(0);
            state = 0;
        } else {
            dk_set_led_off(0);
            state = 1;
        }
		k_sleep(K_MSEC(300));
	}
}
```

``` prj.conf
CONFIG_HEAP_MEM_POOL_SIZE=1024
CONFIG_BT_USER_PHY_UPDATE=y
CONFIG_BT_GATT_DM=y
CONFIG_BT_SCAN=y
CONFIG_BT_SCAN_FILTER_ENABLE=y
CONFIG_BT_SCAN_UUID_CNT=2
CONFIG_BT_BAS_CLIENT=y
CONFIG_BT_CENTRAL=y
CONFIG_BT_EXT_ADV=y
CONFIG_BT_SETTINGS=y
CONFIG_BT_FILTER_ACCEPT_LIST=y
CONFIG_BT_USER_DATA_LEN_UPDATE=y
CONFIG_BT_SMP=y
CONFIG_BT_PRIVACY=y
CONFIG_BT_GATT_CLIENT=y
CONFIG_DK_LIBRARY=y
CONFIG_FLASH=y
CONFIG_BT=y
CONFIG_DEBUG_THREAD_INFO=y
CONFIG_NVS=y
CONFIG_LOG=y
CONFIG_SETTINGS=y
CONFIG_FLASH_MAP=y
CONFIG_DEBUG_OPTIMIZATIONS=y
```

## 接続できるデバイス
スキャナー側のCentralはフィルターをかけていますが、UUIDにBASとDISがあれば何でも繋がってしまいます。市販のBLEデバイスでも普通に持っていそうなUUIDですので、もし繋がらない場合は意図していないデバイスが繋がっていないかだけ気を付けましょう。

# 余談
この解説を作成したのは自分自身の備忘録はもちろんなのですが、DevZoneでシェアしてほしいという書き込みがあったからです。DevZoneへのレスポンスで説明するにはあまりにもコードが長すぎるので…。
日本語でページを作っていますがコードそのものは読めるはずですし、今は翻訳ツールもいっぱいあるのできっと大丈夫だと思います。

https://devzone.nordicsemi.com/f/nordic-q-a/98960/how-to-connect-with-direct-advertising