概要
nRF51822 で BLE アプリを開発する際に独自の Service を定義する方法を紹介します。
Bluetooth SIG 定める標準の Services に関してはこちらを御覧ください。
また、今回は規定番号として Bluetooth SIG への申請や登録はしません。
説明は SDK のサンプルに含まれる app_s130_demo
をベースに進めます。
環境
- Windows7 64bit
- nRF51822
- nRF51 DK
- nRF51 SDK 9.0.0 (SoftDevice: s130)
- nRFgo Studio 1.21.0
- Keil uVersion 5.17.0
UUID を決定する
一番簡単なのは https://www.uuidgenerator.net/ とかで生成する方法です。
まず被ることはないと思いますが標準の UUID と被った場合は別の UUID を生成してください。
独自サービスを定義する
独自サービスを定義するためのメソッドを用意します。
メソッド名は own_service_setup
とします
static void own_service_setup(void)
{
uint32_t error_code = NRF_ERROR_NOT_FOUND;
ble_uuid_t own_service_uuid = {0};
ble_uuid_t attr_uuid = {0};
ble_uuid_t desc_uuid = {0};
ble_gatts_char_md_t char_md = {0};
ble_gatts_attr_t attr = {0};
ble_gatts_attr_md_t attr_md = {0};
ble_gatts_attr_md_t cccd_md = {0};
uint8_t characteristic_value[] = SERVICE_CHARACTERISTIC_VALUE;
ble_gatts_attr_md_t desc_md = {0};
ble_gatts_attr_t desc = {0};
uint8_t uuid_type = 0;
ble_uuid128_t uuid128 = {SERVICE_UUID128};
uint16_t own_desc_handle = 0;
uint8_t own_desc_value[] = SERVICE_CHARACTERISTIC_DESC_VALUE;
uint16_t own_service_handle = 0;
if ((error_code = sd_ble_uuid_vs_add(&uuid128, &uuid_type)) != NRF_SUCCESS)
{
LOG_DEBUG("sd_ble_uuid_vs_add() error code 0x%x", error_code);
APP_ASSERT (false);
}
own_service_uuid.type = uuid_type;
if ((error_code = sd_ble_gatts_service_add(BLE_GATTS_SRVC_TYPE_PRIMARY, &own_service_uuid, &own_service_handle)) != NRF_SUCCESS)
{
LOG_DEBUG("sd_ble_gatts_service_add() error code 0x%x", error_code);
APP_ASSERT (false);
}
memset(&attr_md, 0, sizeof(ble_gatts_attr_md_t));
memset(&cccd_md, 0, sizeof(ble_gatts_attr_md_t));
memset(&char_md, 0, sizeof(ble_gatts_char_md_t));
memset(&gs_own_char_handle, 0, sizeof(ble_gatts_char_handles_t));
memset(&attr, 0, sizeof(ble_gatts_attr_t));
attr_uuid.uuid = SERVICE_CHARACTERISTIC_UUID;
attr_uuid.type = BLE_UUID_TYPE_BLE;
attr_md.vloc = BLE_GATTS_VLOC_STACK;
BLE_GAP_CONN_SEC_MODE_SET_OPEN(&attr_md.read_perm);
BLE_GAP_CONN_SEC_MODE_SET_OPEN(&attr_md.write_perm);
cccd_md.vloc = BLE_GATTS_VLOC_STACK;
BLE_GAP_CONN_SEC_MODE_SET_OPEN(&cccd_md.read_perm);
BLE_GAP_CONN_SEC_MODE_SET_OPEN(&cccd_md.write_perm);
char_md.char_props.read = 1; /*Reading value permitted*/
char_md.char_props.write = 1; /*Writing value with Write Request permitted*/
char_md.char_props.write_wo_resp = 1; /*Writing value with Write Command permitted*/
char_md.char_props.auth_signed_wr = 0; /*Writing value with Signed Write Command not permitted*/
char_md.char_props.notify = 1; /*Notications of value permitted*/
char_md.char_props.indicate = 0; /*Indications of value not permitted*/
char_md.p_cccd_md = &cccd_md;
attr.p_uuid = &attr_uuid;
attr.p_attr_md = &attr_md;
attr.max_len = sizeof(characteristic_value);
attr.init_len = sizeof(characteristic_value);
attr.init_offs = 0;
attr.p_value = characteristic_value;
if ((error_code = sd_ble_gatts_characteristic_add(own_service_handle, &char_md, &attr, &gs_own_char_handle)) != NRF_SUCCESS)
{
LOG_DEBUG("sd_ble_gatts_characteristic_add() error code 0x%x", error_code);
APP_ASSERT (false);
}
/*Setting up a descriptor*/
desc_uuid.uuid = SERVICE_CHARACTERISTIC_DESCRIPTOR_UUID;
desc_uuid.type = BLE_UUID_TYPE_BLE;
memset(&desc_md, 0, sizeof(ble_gatts_attr_md_t));
desc_md.vloc = BLE_GATTS_VLOC_STACK;
desc_md.vlen = 0;
BLE_GAP_CONN_SEC_MODE_SET_OPEN(&desc_md.read_perm);
BLE_GAP_CONN_SEC_MODE_SET_NO_ACCESS(&desc_md.write_perm);
memset(&desc, 0, sizeof(ble_gatts_attr_t));
desc.p_value = own_desc_value;
desc.init_len = sizeof(own_desc_value);
desc.max_len = sizeof(own_desc_value);
desc.p_uuid = &desc_uuid;
desc.p_attr_md = &desc_md;
if ((error_code = sd_ble_gatts_descriptor_add(gs_own_char_handle.value_handle, &desc, &own_desc_handle)) != NRF_SUCCESS)
{
LOG_DEBUG("sd_ble_gatts_descriptor_add() error code 0x%x", error_code);
APP_ASSERT (false);
};
}
簡単にポイントを説明します。
まず冒頭に作成した UUID は SERVICE_UUID128
というマクロによって定義されています。
#define SERVICE_UUID128 0x1b, 0xc5, 0xd5, 0xa5, 0x02, 0x00, 0xbe, 0xa1, 0xe3, 0x11, 0x6b, 0xed, 0x40, 0xfe, 0x0b, 0x31 /* UUID 128: 310bfe40-ed6b-11e3-a1be-0002a5d5c51b */
UUID は LSB 形式 (下位ビットから順番に記載する形式 ) で定義します。
次に sd_ble_uuid_vs_add
で UUID を使用することを定義します。
if ((error_code = sd_ble_uuid_vs_add(&uuid128, &uuid_type)) != NRF_SUCCESS)
{
LOG_DEBUG("sd_ble_uuid_vs_add() error code 0x%x", error_code);
APP_ASSERT (false);
}
2 つ目の引数の uuid_type
は UUID のタイプを指定する引数で、BLE_UUID_TYPE_UNKNOWN
, BLE_UUID_TYPE_BLE
, BLE_UUID_TYPE_VENDOR_BEGIN
があり、今回は独自で定義した UUID なので BLE_UUID_TYPE_UNKNOWN
= 0x00 を指定します。
定義した UUID が持つサービスを追加します。
if ((error_code = sd_ble_gatts_service_add(BLE_GATTS_SRVC_TYPE_PRIMARY, &own_service_uuid, &own_service_handle)) != NRF_SUCCESS)
{
LOG_DEBUG("sd_ble_gatts_service_add() error code 0x%x", error_code);
APP_ASSERT (false);
}
引数にポインタを渡すことで割り当てられたサービスの UUID と handle の値が格納されます。
その後で、登録したサービスが持つ Characteristics と Descriptor を登録します。
if ((error_code = sd_ble_gatts_characteristic_add(own_service_handle, &char_md, &attr, &gs_own_char_handle)) != NRF_SUCCESS)
{
LOG_DEBUG("sd_ble_gatts_characteristic_add() error code 0x%x", error_code);
APP_ASSERT (false);
}
if ((error_code = sd_ble_gatts_descriptor_add(gs_own_char_handle.value_handle, &desc, &own_desc_handle)) != NRF_SUCCESS)
{
LOG_DEBUG("sd_ble_gatts_descriptor_add() error code 0x%x", error_code);
APP_ASSERT (false);
};
Characteristics は保存したいデータを格納する領域になります。
Descriptor は Characteristics に対する追加情報として位置づけられているので、無駄な容量を使いたくない場合は定義しなくても大丈夫です。
Characteristics の定義するときのパラメータで、対象の Characteristics が読み/書き/通知きるのか、設定できるので必要に合わせて権限を変更してください。
char_md.char_props.read = 1; /*Reading value permitted*/
char_md.char_props.auth_signed_wr = 0; /*Writing value with Signed Write Command not permitted*/
char_md.char_props.notify = 1; /*Notications of value permitted*/
char_md.char_props.indicate = 0; /*Indications of value not permitted
独自サービスを初期化する
作成した独自サービスを定義するメソッドをコールすることでサービスを初期化します。
メソッドのコールは main メソッド内で実施すれば OK です。
int main(void)
{
//...
own_service_setup();
//...
}
main メソッド内で他の初期化処理も行っていると思います。
タイミングとしては BatteryLevel や HeartRateService など標準のサービスを初期化したあと、Advertising や Scan をスタートする前のタイミングで own_service_setup
をコールすれば OK です。
Characteristics の値を更新する
デバイス名や企業番号等は固定なので一度設定したら書き換えることはないですが、Battery Level や Heart Rate Service は基本的に動的に変化する値です。
そのような動的に変化する Characteristics の場合には sd_ble_gatts_value_set
というメソッドをコールしてください。
uint32_t ble_own_characteristics_update(ble_own_service_t * p_os, uint8_t *update_value, uint16_t array_size) {
uint32_t err_code = NRF_SUCCESS;
ble_gatts_value_t gatts_value;
// Initialize value struct.
memset(&gatts_value, 0, sizeof(gatts_value));
gatts_value.len = array_size;
gatts_value.offset = 0;
gatts_value.p_value = update_value;
// Update database.
err_code = sd_ble_gatts_value_set(p_os->conn_handle,
p_os->own_service_handles.value_handle,
&gatts_value);
if (err_code != NRF_SUCCESS) {
return err_code;
}
return err_code;
}
ble_own_service_t
は独自で定義した構造体です。
typedef struct {
uint16_t conn_handle; /**< Handle of the current connection (as provided by the BLE stack, is BLE_CONN_HANDLE_INVALID if not in a connection). */
ble_gatts_char_handles_t own_service_handles; /**< Handles related to the Battery Level characteristic. */
uint8_t value_last[]; /**< Last value passed to the original Service. */
}
ble_own_service_t;
Characteristics を更新する値は uint8_t のポインタ (配列) で渡してください。
まだ、その配列のサイズも同時に渡しください、理由はポインタから配列のサイズを求められないためです。
例えばこのメソッドをあるイベントが発生したタイミングやタイマーを使ってある一定間隔でコールすれば動的に変化する Characteristics を実現することができます。
確認する
コードが書けたら Keil でビルドして hex ファイルを作成しましょう。
作成した hex ファイルは nRFgo Studio で評価ボードの nRF51 DK に焼き込めば OK です。
nRF51 DK に焼き込めばすぐに BLE アプリが起動するので iPhone などの Scan アプリでアドバタイジングをスキャンしてみてください。
その後見つけたデバイスにコネクトしてみて、独自で定義したサービスが定義されていることを確認しましょう。
最後に
BLE デバイス上に独自のサービスを持たせる方法を紹介しました。
Texas instruments 社の SensorTag などは独自のサービスがかなりたくさん実装されているデバイスの 1 つになります。
nRF51822 の SDK は今回 9.0.0 で紹介しましたが、すでに 10.0.0 というバージョンが出ておりそちらで実装している方も多いかと思います。
試してはいませんが今回の方法は 10.0.0 系の SDK でも動作すると思われます。
ただ、最近 nRF52 シリーズという更に性能のいい SoC が発売になったのでそちらで動くかは分かりません。
nRF51 系のアプリおよびデバイスを作成している方は開発者専用のポータルサイトがあるので、そこで検索したり質問すると結構早く解決します。
https://devzone.nordicsemi.com/questions/