nRF connect SDK:カスタムサービスの追加

#カスタムサービス(UUID)を追加する
Bluetooth Classicでは用意されたプロファイルから選ぶことしかできませんでしたが、Bluetooth Low Energy(BLE)では独自のUUIDを使ったサービスを追加することができます。
##カスタムサービス用ファイルの生成
nRF SDKでも同じような感じでカスタムUUID用のファイルを作る必要がありました。(記述内容は違いますが)そのあたりは同じです。
ここではベタにservicesという名称でフォルダを作成しています。別にフォルダである必要はありませんが、リソースの共通化という点ではフォルダ化しておいたほうがよい気がします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/340602/9ce54725-a3f7-74e8-1038-0fad419dcf52.png)
この下にはサービスの内容を記述したmy_service.c / my_service.hの2つのファイルがあります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/340602/d9571b15-8cb8-d8c5-c159-6aa0e95876e3.png)
#CMakeLists.txtの編集
カスタムサービスを追加するにはCMakeLists.txtを編集する必要があります。具体的にはtarget_sourcesでmain.cしか読み込んでいないところに先ほどのmy_service.cを追加するというものです。

```CMakeLists.txt
# SPDX-License-Identifier: Apache-2.0

cmake_minimum_required(VERSION 3.13.1)

find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE})
project(advertising)

target_sources(app PRIVATE src/main.c services/my_service.c)
```

##prj.confの編集
カスタムサービスを追加するために必要な編集はありません。

##ソースコード
###main.c
main.cは以前に作ったアドバタイズをする、をベースにしています。

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

#include <zephyr.h>
#include <sys/printk.h>
#include <bluetooth/bluetooth.h>
#include <bluetooth/gatt.h>

#include "../services/my_service.h"

#define DEVICE_NAME CONFIG_BT_DEVICE_NAME
#define DEVICE_NAME_LEN (sizeof(DEVICE_NAME) - 1)

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),
};

struct bt_conn *my_connection;

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

    my_connection = conn;
    if (err) 
    {
        printk("Connection failed (err %u)\n", err);
        return;
    }
    else if(bt_conn_get_info(conn, &info))
    {
        printk("Could not parse connection info\n");
    }
    else
    {
        bt_addr_le_to_str(bt_conn_get_dst(conn), addr, sizeof(addr));
        
        printk("Connection established!\n");
        printk("Connected to: %s\n", addr);
        printk("Role: %u\n", info.role);
        printk("Connection interval: %u\n", info.le.interval);
        printk("Slave latency: %u\n", info.le.latency);
        printk("Connection supervisory timeout: %u\n", info.le.timeout);
    }
}

static void disconnected(struct bt_conn *conn, uint8_t reason)
{
    printk("Disconnected\n");
}

static bool le_param_req(struct bt_conn *conn, struct bt_le_conn_param *param)
{
    //If acceptable params, return true, otherwise return false.
    return true; 
}

static void le_param_updated(struct bt_conn *conn, uint16_t interval, uint16_t latency, uint16_t timeout)
{
    struct bt_conn_info info; 
    char addr[BT_ADDR_LE_STR_LEN];
    
    if(bt_conn_get_info(conn, &info))
    {
        printk("Could not parse connection info\n");
    }
    else
    {
        bt_addr_le_to_str(bt_conn_get_dst(conn), addr, sizeof(addr));

        printk("Connection parameters updated!\n");
        printk("Connected to: %s\n", addr);
        printk("New Connection Interval: %u\n", info.le.interval);
        printk("New Slave Latency: %u\n", info.le.latency);
        printk("New Connection Supervisory Timeout: %u\n", info.le.timeout);
    }
}

static struct bt_conn_cb conn_callbacks = {
    .connected    = connected,
    .disconnected = disconnected,
    .le_param_req = le_param_req,
    .le_param_updated = le_param_updated,
};

void main(void)
{
    int err = 0;
    uint32_t number = 0;

    err = bt_enable(NULL);
    if (err) {
        printk("Blutooth failed to start (err %d)\n", err);
        return;
    }

    bt_conn_cb_register(&conn_callbacks);

    //Initalize services
    err = my_service_init();
    if (err) 
    {
        printk("Failed to init LBS (err:%d)\n", err);
        return;
    }

    err = bt_le_adv_start(BT_LE_ADV_CONN, ad, ARRAY_SIZE(ad), NULL, 0);
    if (err) {
        printk("Advertising failed to start (err %d)\n", err);
        return;
    }

    // Main loop
    for (;;) 
    {
        my_service_send(my_connection, (uint8_t *)&number, sizeof(number));
        number++;
        k_sleep(K_MSEC(1000));
    }
}
```

###my_service.c

```my_service.c
#include <errno.h>
#include <soc.h>
#include <stddef.h>
#include <string.h>
#include <zephyr.h>
#include <sys/printk.h>
#include <sys/byteorder.h>
#include <zephyr/types.h>

#include <bluetooth/bluetooth.h>
#include <bluetooth/hci.h>
#include <bluetooth/conn.h>
#include <bluetooth/uuid.h>
#include <bluetooth/addr.h>
#include <bluetooth/gatt.h>

#include "my_service.h"

#define BT_UUID_MY_SERVICE      BT_UUID_DECLARE_128(MY_SERVICE_UUID)
#define BT_UUID_MY_SERVICE_RX   BT_UUID_DECLARE_128(RX_CHARACTERISTIC_UUID)
#define BT_UUID_MY_SERVICE_TX   BT_UUID_DECLARE_128(TX_CHARACTERISTIC_UUID)

#define MAX_TRANSMIT_SIZE 240
uint8_t data_rx[MAX_TRANSMIT_SIZE];
uint8_t data_tx[MAX_TRANSMIT_SIZE];

int my_service_init(void)
{
    int err = 0;

    memset(&data_rx, 0, MAX_TRANSMIT_SIZE);
    memset(&data_tx, 0, MAX_TRANSMIT_SIZE);

    return err;
}

/* This function is called whenever the RX Characteristic has been written to by a Client */
static ssize_t on_receive(struct bt_conn *conn,
                          const struct bt_gatt_attr *attr,
			  const void *buf,
			  uint16_t len,
			  uint16_t offset,
			  uint8_t flags)
{
    const uint8_t * buffer = buf;
    
    printk("Received data, handle %d, conn %p, data: 0x", attr->handle, conn);
    for(uint8_t i = 0; i < len; i++)
    {
        printk("%02X", buffer[i]);
    }
    printk("\n");

    return len;
}

/* This function is called whenever a Notification has been sent by the TX Characteristic */
static void on_sent(struct bt_conn *conn, void *user_data)
{
    ARG_UNUSED(user_data);

    const bt_addr_le_t * addr = bt_conn_get_dst(conn);

    printk("Data sent to Address 0x %02X %02X %02X %02X %02X %02X \n",
        addr->a.val[0],
        addr->a.val[1],
        addr->a.val[2],
        addr->a.val[3],
        addr->a.val[4],
        addr->a.val[5]
    );
}

/* This function is called whenever the CCCD register has been changed by the client*/
void on_cccd_changed(const struct bt_gatt_attr *attr, uint16_t value)
{
    ARG_UNUSED(attr);
    switch(value)
    {
        case BT_GATT_CCC_NOTIFY:
            // Start sending stuff!
            printk("Enable notification\n");
            break;

        case BT_GATT_CCC_INDICATE: 
            // Start sending stuff via indications
            printk("Enable indication\n");
            break;

        case 0: 
            // Stop sending stuff
            printk("Disable notification/indication\n");
            break;
        
        default: 
            printk("Error, CCCD has been set to an invalid value");     
    }
}
                        

/* Service Declaration and Registration */
BT_GATT_SERVICE_DEFINE(my_service,
    BT_GATT_PRIMARY_SERVICE(BT_UUID_MY_SERVICE),
    BT_GATT_CHARACTERISTIC(
        BT_UUID_MY_SERVICE_RX,
        BT_GATT_CHRC_WRITE | BT_GATT_CHRC_WRITE_WITHOUT_RESP,
        BT_GATT_PERM_READ | BT_GATT_PERM_WRITE,
        NULL,
        on_receive,
        NULL),
    BT_GATT_CHARACTERISTIC(
        BT_UUID_MY_SERVICE_TX,
        BT_GATT_CHRC_READ | BT_GATT_CHRC_NOTIFY,
        BT_GATT_PERM_READ,
        NULL,
        NULL,
        NULL),
    BT_GATT_CCC(
        on_cccd_changed,
        BT_GATT_PERM_READ | BT_GATT_PERM_WRITE),
);

/*
This function sends a notification to a Client with the provided data,
given that the Client Characteristic Control Descripter has been set to Notify (0x1).
It also calls the on_sent() callback if successful
*/
void my_service_send(struct bt_conn *conn, const uint8_t *data, uint16_t len)
{
    /* 
    The attribute for the TX characteristic is used with bt_gatt_is_subscribed 
    to check whether notification has been enabled by the peer or not.
    Attribute table: 0 = Service, 1 = Primary service, 2 = RX, 3 = TX, 4 = CCC.
    */
    const struct bt_gatt_attr *attr = &my_service.attrs[3]; 
    struct bt_gatt_notify_params params = 
    {
        .uuid   = BT_UUID_MY_SERVICE_TX,
        .attr   = attr,
        .data   = data,
        .len    = len,
        .func   = on_sent,
    };

    // Check whether notifications are enabled or not
    if(bt_gatt_is_subscribed(conn, attr, BT_GATT_CCC_NOTIFY))
    {
        // Send the notification
        if(bt_gatt_notify_cb(conn, &params))
        {
            printk("Error, unable to send notification\n");
        }
    }
    else
    {
        printk("Warning, notification not enabled\n");
    }
}
```

###my_service.h

```my_service.h
#include <zephyr/types.h>
#include <stddef.h>
#include <string.h>
#include <errno.h>
#include <zephyr.h>
#include <soc.h>

#include <bluetooth/bluetooth.h>
#include <bluetooth/hci.h>
#include <bluetooth/conn.h>
#include <bluetooth/uuid.h>
#include <bluetooth/gatt.h>

#define MY_SERVICE_UUID         0xD4, 0x86, 0x48, 0x24, 0x54, 0xB3, 0x43, 0xA1, 0xBC, 0x20, 0x97, 0x8F, 0xC3, 0x76, 0xC2, 0x75
#define RX_CHARACTERISTIC_UUID  0xA6, 0xE8, 0xC4, 0x60, 0x7E, 0xAA, 0x41, 0x6B, 0x95, 0xD4, 0x9D, 0xCC, 0x08, 0x4F, 0xCF, 0x6A
#define TX_CHARACTERISTIC_UUID  0xED, 0xAA, 0x20, 0x11, 0x92, 0xE7, 0x43, 0x5A, 0xAA, 0xE9, 0x94, 0x43, 0x35, 0x6A, 0xD4, 0xD3

/** @brief Callback type for when new data is received. */
typedef void (*data_rx_cb_t)(uint8_t *data, uint8_t length);

/** @brief Callback struct used by the my_service Service. */
struct my_service_cb 
{
	/** Data received callback. */
	data_rx_cb_t    data_rx_cb;
};

int my_service_init(void);

void my_service_send(struct bt_conn *conn, const uint8_t *data, uint16_t len);
```

#参考スレッド

https://devzone.nordicsemi.com/nordic/nrf-connect-sdk-guides/b/getting-started/posts/ncs-ble-tutorial-part-1-custom-service-in-peripheral-role