BLE Nano を mbed で iBeacon 化する

 前回 BLE Nano で Lチカまでやったので、今回は BLE Nano を iBeacon 化して、iOS アプリから検知してみたいと思います。

 また、開発環境としては前回の記事で、 Vagrant で構築した VM からだと mbed import の途中で止まってしまったと書きましたが、 Mac 上の環境と同様で時間がかかるものの、待てば正常に進んだので、今回は Vagrant で構築した CentOS 環境から mbed cli で実行します。

サンプルコードを動かす

 ひとまず動かしてみるだけであれば公開されているサンプルコードをインポートしてそのままコンパイルすれば動かせてしまいます。まずはインポート。

[vagrant@localhost vagrant]$ mbed import https://mbed.org/teams/Bluetooth-Low-Energy/code/BLE_iBeacon/
[mbed] Importing program "BLE_iBeacon" from "https://mbed.org/teams/Bluetooth-Low-Energy/code/BLE_iBeacon" at latest revision in the current branch
[mbed] Adding library "BLE_API" from "https://developer.mbed.org/teams/Bluetooth-Low-Energy/code/BLE_API" at rev #65474dc93927
[mbed] Adding library "mbed" from "https://mbed.org/users/mbed_official/code/mbed/builds" at rev #abea610beb85
[mbed] Downloading library build "abea610beb85" (might take a minute)
[mbed] Unpacking library build "abea610beb85" in "/vagrant/BLE_iBeacon/mbed"
[mbed] Adding library "nRF51822" from "https://mbed.org/teams/Nordic-Semiconductor/code/nRF51822" at rev #c90ae1400bf2
[mbed] Adding library "shields/TARGET_ST_BLUENRG" from "https://developer.mbed.org/teams/ST/code/X_NUCLEO_IDB0XA1" at rev #fa98703ece8e
[mbed] Couldn't find build tools in your program. Downloading the mbed 2.0 SDK tools...

 続けて toolchain と target を指定してからコンパイルしてみます。

[vagrant@localhost vagrant]$ cd BLE_iBeacon/                                        
[vagrant@localhost BLE_iBeacon]$                                                    
[vagrant@localhost BLE_iBeacon]$ mbed detect                                        
                                                                                    
[mbed] Detected RBLAB_BLENANO, port /dev/ttyACM0, mounted /run/media/vagrant/DAPLINK
[mbed] Supported toolchains for RBLAB_BLENANO                                       
+--------+-----------+-----------+-----+---------+-----+                            
| Target | mbed OS 2 | mbed OS 5 | ARM | GCC_ARM | IAR |                            
+--------+-----------+-----------+-----+---------+-----+                            
+--------+-----------+-----------+-----+---------+-----+                            
Supported targets: 0                                                                
                                                                                    
[vagrant@localhost BLE_iBeacon]$                                                    
[vagrant@localhost BLE_iBeacon]$ mbed toolchain GCC_ARM                             
[mbed] GCC_ARM now set as default toolchain in program "BLE_iBeacon"                
[vagrant@localhost BLE_iBeacon]$ mbed target RBLAB_BLENANO                          
[mbed] RBLAB_BLENANO now set as default target in program "BLE_iBeacon"             
[vagrant@localhost BLE_iBeacon]$                                                    
[vagrant@localhost BLE_iBeacon]$ mbed compile                                       
Building project BLE_iBeacon (RBLAB_BLENANO, GCC_ARM)                               
Scan: .                                                                             
Scan: mbed                                                                          
Scan: env                                                                           
Compile [  1.8%]: BLE.cpp                                                           
〜〜〜中略〜〜〜
Compile [100.0%]: nRF5xn.cpp
Link: BLE_iBeacon
/usr/local/lib/gcc-arm-none-eabi-4_9-2015q3/bin/../lib/gcc/arm-none-eabi/4.9.3/../../../../arm-none-eabi/lib/armv6-m/crt0.o: In function `_start':
(.text+0x52): undefined reference to `__wrap_exit'
./mbed/abea610beb85/TARGET_RBLAB_BLENANO/TOOLCHAIN_GCC_ARM/retarget.o: In function `__cxa_pure_virtual':
retarget.cpp:(.text.__cxa_pure_virtual+0x4): undefined reference to `__wrap_exit'
./mbed/abea610beb85/TARGET_RBLAB_BLENANO/TOOLCHAIN_GCC_ARM/libmbed.a(mbed_error.o): In function `error':
mbed_error.c:(.text.error+0x10): undefined reference to `__wrap_exit'
collect2: error: ld returned 1 exit status
[ERROR] /usr/local/lib/gcc-arm-none-eabi-4_9-2015q3/bin/../lib/gcc/arm-none-eabi/4.9.3/../../../../arm-none-eabi/lib/armv6-m/crt0.o: In function `_start':
(.text+0x52): undefined reference to `__wrap_exit'
./mbed/abea610beb85/TARGET_RBLAB_BLENANO/TOOLCHAIN_GCC_ARM/retarget.o: In function `__cxa_pure_virtual':
retarget.cpp:(.text.__cxa_pure_virtual+0x4): undefined reference to `__wrap_exit'
./mbed/abea610beb85/TARGET_RBLAB_BLENANO/TOOLCHAIN_GCC_ARM/libmbed.a(mbed_error.o): In function `error':
mbed_error.c:(.text.error+0x10): undefined reference to `__wrap_exit'
collect2: error: ld returned 1 exit status

[mbed] ERROR: "/usr/local/pyenv/versions/anaconda2-4.4.0/bin/python" returned error code 1.
[mbed] ERROR: Command "/usr/local/pyenv/versions/anaconda2-4.4.0/bin/python -u /vagrant/BLE_iBeacon/.temp/tools/make.py -t GCC_ARM -m RBLAB_BLENANO --source . --build ./BUILD/RBLAB_BLENANO/GCC_ARM" in "/vagrant/BLE_iBeacon"
---

 コンパイル自体は成功しましたが、ライブラリをリンクするところでエラーになってしまいました。調べたところ下記のページの情報をみつけました。

https://developer.mbed.org/forum/bugs-suggestions/topic/27426/?page=1#comment-52739

 mbed.bld で指定されているビルドが古いということなので、上記ページにあるように変更してみます。

[vagrant@localhost BLE_iBeacon]$ cat mbed.bld 
https://mbed.org/users/mbed_official/code/mbed/builds/abea610beb85
 ↓
[vagrant@localhost BLE_iBeacon]$ cat mbed.bld
https://mbed.org/users/mbed_official/code/mbed/builds/4eea097334d6

 そして mbed deploy で更新します。

[vagrant@localhost BLE_iBeacon]$ mbed deploy
[mbed] Updating library "BLE_API" to rev #65474dc93927
[mbed] Updating library "mbed" to rev #4eea097334d6
[mbed] Downloading library build "4eea097334d6" (might take a minute)
[mbed] Unpacking library build "4eea097334d6" in "/vagrant/BLE_iBeacon/mbed"
[mbed] Updating library "nRF51822" to rev #c90ae1400bf2
[mbed] Updating library "shields/TARGET_ST_BLUENRG" to rev #fa98703ece8e
[mbed] Updating the mbed 2.0 SDK tools...

 そしてコンパイル。

[vagrant@localhost BLE_iBeacon]$ mbed compile
Building project BLE_iBeacon (RBLAB_BLENANO, GCC_ARM)
Scan: .
Scan: mbed
Scan: env
Compile [  1.8%]: BLE.cpp
〜〜〜中略〜〜〜
Compile [100.0%]: nRF5xn.cpp
[Warning] nRF5xGap.h@223,93: 'void mbed::Ticker::attach_us(T*, M, timestamp_t) [with T = nRF5xGap; M = void (nRF5xGap::*)(); timestamp_t = long unsigned int]' is deprecated (declared at ./mbed/4eea097334d6/drivers/Ticker.h:121): The attach_us function does not support cv-qualifiers. Replaced by attach_us(callback(obj, method), t). [since mbed-os-5.1] [-Wdeprecated-declarations]
Link: BLE_iBeacon
Elf2Bin: BLE_iBeacon
+-----------+-------+-------+------+
| Module    | .text | .data | .bss |
+-----------+-------+-------+------+
| Fill      |   111 |     3 |   28 |
| Misc      | 32560 |   141 | 1124 |
| Subtotals | 32671 |   144 | 1152 |
+-----------+-------+-------+------+
Allocated Heap: 2800 bytes
Allocated Stack: 2048 bytes
Total Static RAM memory (data + bss): 1296 bytes
Total RAM memory (data + bss + heap + stack): 6144 bytes
Total Flash memory (text + data + misc): 32815 bytes

Image: ./BUILD/RBLAB_BLENANO/GCC_ARM/BLE_iBeacon.hex

 成功して hex ファイルが作成されました。ちなみにこの時点での最新のビルドは a330f0fddbec でした。

https://developer.mbed.org/users/mbed_official/code/mbed//builds/a330f0fddbec

 ですがこのビルドを指定すると下記のようなエラーになってしまったので、 4eea097334d6 で実行しています。

[vagrant@localhost BLE_iBeacon]$ mbed compile
Building project BLE_iBeacon (RBLAB_BLENANO, GCC_ARM)
Scan: .
Scan: mbed
Scan: env
Compile [  1.8%]: BLE.cpp
[Warning] toolchain.h@23,2: #warning toolchain.h has been replaced by mbed_toolchain.h, please update to mbed_toolchain.h [since mbed-os-5.3] [-Wcpp]
Compile [  3.6%]: BLEInstanceBase.cpp
Compile [  5.5%]: DiscoveredCharacteristic.cpp
Compile [  7.3%]: GapScanningParams.cpp
Compile [  9.1%]: DFUService.cpp
Compile [ 10.9%]: UARTService.cpp
Compile [ 12.7%]: URIBeaconConfigService.cpp
Compile [ 14.5%]: main.cpp
[Fatal Error] iBeacon.h@19,26: core_cmInstr.h: No such file or directory
[ERROR] In file included from ./main.cpp:18:0:
./BLE_API/ble/services/iBeacon.h:19:26: fatal error: core_cmInstr.h: No such file or directory
 #include "core_cmInstr.h"
                          ^
compilation terminated.

[mbed] ERROR: "/usr/local/pyenv/versions/anaconda2-4.4.0/bin/python" returned error code 1.
[mbed] ERROR: Command "/usr/local/pyenv/versions/anaconda2-4.4.0/bin/python -u /vagrant/BLE_iBeacon/.temp/tools/make.py -t GCC_ARM -m RBLAB_BLENANO --source . --build ./BUILD/RBLAB_BLENANO/GCC_ARM" in "/vagrant/BLE_iBeacon"
---

 それではBLE Nano に hex ファイルをコピーして動作確認してみます。

[vagrant@localhost BLE_iBeacon]$ mbed detect

[mbed] Detected RBLAB_BLENANO, port /dev/ttyACM0, mounted /run/media/vagrant/DAPLINK
[mbed] Supported toolchains for RBLAB_BLENANO
+--------+-----------+-----------+-----+---------+-----+
| Target | mbed OS 2 | mbed OS 5 | ARM | GCC_ARM | IAR |
+--------+-----------+-----------+-----+---------+-----+
+--------+-----------+-----------+-----+---------+-----+
Supported targets: 0

[vagrant@localhost BLE_iBeacon]$ 
[vagrant@localhost BLE_iBeacon]$ cp ./BUILD/RBLAB_BLENANO/GCC_ARM/BLE_iBeacon.hex /run/media/vagrant/DAPLINK/
[vagrant@localhost BLE_iBeacon]$ 

 BLE Nano 側での作業は一旦終了で、 iPhone 側で iBeacon を検知するために下記アプリをインストールします。

Locate Beacon

Locate Beacon

  • Radius Networks
  • ユーティリティ
  • 無料

 アプリを起動して下記のような内容で検知対象UUIDを登録します。

f:id:akanuma-hiroaki:20170915092148p:plain:w300

 すると下記のように iBeacon が検知されます。

f:id:akanuma-hiroaki:20170915092349p:plain:w300f:id:akanuma-hiroaki:20170915092356p:plain:w300

サンプルコードの内容

 サンプルコードの内容について自分が理解した範囲で説明してみます。ちなみに mbed の BLE API のリファレンスは下記にあります。

BLE_API | Mbed

 まずは main() の中で ble.init() で BLE Controller を初期化します。 BLE API を使う場合にはまずこれをやります。引数には初期化完了時に実行されるコールバックメソッドを指定します。

ble.init(bleInitComplete);

 コールバックメソッドには BLE::InitializationCompleteCallbackContex のインスタンスが渡されます。

void bleInitComplete(BLE::InitializationCompleteCallbackContext *params)

 コールバックメソッドの中で iBeacon クラスを new することで BLE Advertisement を iBeacon として送るためのセットアップが行われます。uuid, majorNumber, minorNumber, txPower はとりあえずサンプルコードのデフォルト値をそのまま使います。

const uint8_t uuid[] = {0xE2, 0x0A, 0x39, 0xF4, 0x73, 0xF5, 0x4B, 0xC4,
                        0xA1, 0x2F, 0x17, 0xD1, 0xAD, 0x07, 0xA9, 0x61};
uint16_t majorNumber = 1122;
uint16_t minorNumber = 3344;
uint16_t txPower     = 0xC8;
iBeacon *ibeacon = new iBeacon(ble, uuid, majorNumber, minorNumber, txPower);

 Advertisement の送信間隔を設定し、送信を開始します。

ble.gap().setAdvertisingInterval(1000); /* 1000ms. */
ble.gap().startAdvertising();

 main() の中では BLE Controller の初期化が終わるまで空のループを回して待ちます。

while (!ble.hasInitialized()) { /* spin loop */ }

 初期化が終わったら無限ループを回し、イベントが発生するまで待機を繰り返します。

while (true) {
    ble.waitForEvent(); // allows or low power operation
}

まとめ

 とりあえず iBeacon の Advertisement を送信するだけならサンプルコードをそのまま使ってとても簡単に実行することができてしまいました。次は色々なセンサー類と組み合わせるためにも、Peripheralとして動作できるように挑戦してみたいと思います。

 また、環境面では mbed cli だと Web IDE と比べてやはりライブラリとの依存関係などで悩まされることは多くなりそうです。すぐ解決できる場合は良いのですが、困った時はあまり悩まず Web IDE に切り替えて使うのが良さそうです。