BLE Nano + 焦電センサーで人感センサーを作ってみる

 今回は BLE Nano と焦電センサーを使って人感センサーを作ってみました。焦電センサーは人間等が放出する熱エネルギーに変化が生じた時に発生する赤外線を検知するため、人感センサーとして使うことができます。ただし熱エネルギーの「変化」を検知するので、人がいても動かずじっとしている時には検知されませんので、ずっとそこにいることを検知するというよりは、入室や通過等を検知するのに向いているようです。

DAPLink と接続して配線する

 今回使用している焦電センサーは、焦電型赤外線センサー PaPIRs 5m EKMC1601111 です。

akizukidenshi.com

 また、今回下記サイトを参考に BLE Nano と EKMC1601111 の Fritzing パーツを自作してみました。

Fritzingパーツ作成方法 | Home Made Garbage

Fritzing カスタムパーツの作り方 – jumbleat

そして配線図はこちらです。

f:id:akanuma-hiroaki:20171014115114p:plain

 プログラムの書き込みとシリアル接続でのデバック用に DAPLink と BLE Nano を接続しているので、電源も DAPLink から VIN ピンで取り、VDD ピンから LED と焦電センサーに供給しています。 LED は動作確認用で、焦電センサーが人の動きを感知しているときは点灯させます。 EKMC1601111 には 3本のピンがあり、上記配線図では左から VDD, OUT, GND となっていますので、 OUT は抵抗を噛ませて BLE Nano の P0_5 ピンと接続しています。

ソースコード

 今回実装したコードの全文は下記の通りです。以前の照度センサーの時と大体同じで、焦電センサーの値を扱うカスタムサービスを PyroelectricService.h として実装しています。

#ifndef __BLE_PYROELECTRIC_SERVICE_H__
#define __BLE_PYROELECTRIC_SERVICE_H__

class PyroelectricService {
public:
  const static uint16_t PYROELECTRIC_SERVICE_UUID = 0xA000;
  const static uint16_t PYROELECTRIC_VALUE_CHARACTERISTIC_UUID = 0xA001;

  PyroelectricService(BLE &_ble, bool initialValueForPyroelectricCharacteristic) :
    ble(_ble), pyroelectricState(PYROELECTRIC_VALUE_CHARACTERISTIC_UUID, &initialValueForPyroelectricCharacteristic, GattCharacteristic::BLE_GATT_CHAR_PROPERTIES_NOTIFY)
  {
    GattCharacteristic *charTable[] = {&pyroelectricState};
    GattService pyroelectricService(PYROELECTRIC_SERVICE_UUID, charTable, sizeof(charTable) / sizeof(GattCharacteristic *));
    ble.gattServer().addService(pyroelectricService);
  }

  void updatePyroelectricState(bool newState) {
    ble.gattServer().write(pyroelectricState.getValueHandle(), (uint8_t *)&newState, sizeof(bool));
  }

private:
  BLE &ble;
  ReadOnlyGattCharacteristic<bool> pyroelectricState;
};

#endif

 そして main.cpp から PyroelectricSerivce.h を使用しています。

#include "mbed.h"
#include "ble/BLE.h"
#include "PyroelectricService.h"

Serial pc(USBTX, USBRX);
DigitalOut led(P0_4, 0);
AnalogIn pyro(P0_5);

const static char DEVICE_NAME[] = "PyroSample";
static const uint16_t uuid16_list[] = {PyroelectricService::PYROELECTRIC_SERVICE_UUID};

static volatile bool triggerSensorPolling = false;

unsigned short pyroValue;
const int threshold = 500;

bool pyroelectricState;

PyroelectricService *pyroelectricServicePtr;

Ticker ticker;

BLE ble;

void disconnectionCallback(const Gap::DisconnectionCallbackParams_t *params)
{
  BLE::Instance().gap().startAdvertising();
}

void periodicCallback(void)
{
  pyroValue = pyro.read_u16();
  pyroelectricState = pyroValue >= threshold;
  if (pyroelectricState) {
    led = 1;
  } else {
    led = 0;
  }

  triggerSensorPolling = true;
}

void onBleInitError(BLE &ble, ble_error_t error)
{
}

void bleInitComplete(BLE::InitializationCompleteCallbackContext *params)
{
  BLE& ble = params->ble;
  ble_error_t error = params->error;

  if (error != BLE_ERROR_NONE) {
    onBleInitError(ble, error);
    return;
  }

  if (ble.getInstanceID() != BLE::DEFAULT_INSTANCE) {
    return;
  }

  ble.gap().setDeviceName((const uint8_t *) DEVICE_NAME);
  ble.gap().onDisconnection(disconnectionCallback);

  bool initialValueForPyroelectricCharacteristic = false;
  pyroelectricServicePtr = new PyroelectricService(ble, initialValueForPyroelectricCharacteristic);

  ble.gap().accumulateAdvertisingPayload(GapAdvertisingData::BREDR_NOT_SUPPORTED | GapAdvertisingData::LE_GENERAL_DISCOVERABLE);
  ble.gap().accumulateAdvertisingPayload(GapAdvertisingData::COMPLETE_LIST_16BIT_SERVICE_IDS, (uint8_t *)uuid16_list, sizeof(uuid16_list));
  ble.gap().accumulateAdvertisingPayload(GapAdvertisingData::COMPLETE_LOCAL_NAME, (uint8_t *)DEVICE_NAME, sizeof(DEVICE_NAME));
  ble.gap().setAdvertisingType(GapAdvertisingParams::ADV_CONNECTABLE_UNDIRECTED);
  ble.gap().setAdvertisingInterval(1000);
  ble.gap().startAdvertising();
}

int main(void)
{
  pc.printf("Starting BLE_PYROELECTRIC...\r\n");

  ticker.attach(periodicCallback, 1);

  pc.printf("Initializing BLE Controller...\r\n");
  ble.init(bleInitComplete);
  pc.printf("Initialized.\r\n");

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

  pc.printf("Starting loop...\r\n");
  while(true) {
    if (triggerSensorPolling) {
      triggerSensorPolling = false;
      pc.printf("%d\r\n", pyroelectricState);
      pyroelectricServicePtr->updatePyroelectricState(pyroelectricState);
    } else {
      ble.waitForEvent();
    }
  }
}

 焦電センサーからの出力はアナログ入力として受け取り、入力値が閾値を上回ったかどうかでステータスを切り替えています。

AnalogIn pyro(P0_5);
void periodicCallback(void)
{
  pyroValue = pyro.read_u16();
  pyroelectricState = pyroValue >= threshold;
  if (pyroelectricState) {
    led = 1;
  } else {
    led = 0;
  }

  triggerSensorPolling = true;
}

 最初はデジタル入力として試していたのですが、一度検知して true を返すようになると、その後正しく false に戻らなかったため、アナログ入力の値を使うようにしてみました。 BLE のカスタムサービスで扱う値が bool になったという以外は、基本的には照度センサーを使用した時と同様の構成です。

動作確認

 ではプログラムをコンパイルして書き込んで、動作確認してみます。

[vagrant@localhost BLE_PYROELECTRIC]$ mbed compile
Building project BLE_PYROELECTRIC (RBLAB_BLENANO, GCC_ARM)
Scan: .
Scan: mbed
Scan: env
Compile [100.0%]: main.cpp
Link: BLE_PYROELECTRIC
Elf2Bin: BLE_PYROELECTRIC
+-----------+-------+-------+------+
| Module    | .text | .data | .bss |
+-----------+-------+-------+------+
| Fill      |   127 |     3 |   47 |
| Misc      | 41520 |   141 | 1649 |
| Subtotals | 41647 |   144 | 1696 |
+-----------+-------+-------+------+
Allocated Heap: 2256 bytes
Allocated Stack: 2048 bytes
Total Static RAM memory (data + bss): 1840 bytes
Total RAM memory (data + bss + heap + stack): 6144 bytes
Total Flash memory (text + data + misc): 41791 bytes

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

 するとシリアル接続していると下記のように、人の動きを検知しているときは 1 を、検知していないときは 0 を出力します。

Starting BLE_PYROELECTRIC...
Initializing BLE Controller...
Initialized.
Starting loop...
0
0
0
0
0
0
0
0
1
1
1
1
1
0
0

 また、 LightBlue でも下記のように Peripheral のリストに PyroSample が表示され、接続して Notification を Listen すると値が更新されていくのが確認できます。

f:id:akanuma-hiroaki:20171009235322p:plain:w300 f:id:akanuma-hiroaki:20171009235347p:plain:w300

ボタン電池で動かす

 動作確認ができたので、実際にセンサーとして使う場合を想定して、DAPLink から切り離してボタン電池で動かしてみます。配線は下記のようにしました。

f:id:akanuma-hiroaki:20171014133706p:plain

 CR2032 のボタン電池ホルダーを配置し、+側から BLE Nano の VIN ピンに給電しています。ボタン電池をはめればすぐに起動して動き始めます。ちなみに「人感」センサーと言ってますが猫でもちゃんと検知します。

[f:id:akanuma-hiroaki:20171014135212j:plain:450]

まとめ

 冒頭でも書きましたが、焦電センサーは動きを検知することになるため、そこに人間や動物がいても、ほとんど動いてなければ検知されません。猫が昼寝スポットにいるかどうかを検知するものでも作ってみようかと思ってたのですが、焦電センサーだとその用途にはマッチしないようです。

f:id:akanuma-hiroaki:20171014140347j:plain:w300:left

 また、動作確認をしていると、実際はセンサー前で動き続けていても時々検知結果が false になるので、在室検知のようなことをするとしたら、状態の管理としてはある程度 false の時間が続いたら false と判定するのが良さそうです。

Raspberry Pi とモーターを繋げて Ruby で動かす

 今までは Raspberry Pi と他のセンサー類を組み合わせてセンサー値を読み取り、データとして使える形にはしていましたが、ハードウェアのアウトプットとしては LED を光らせるぐらいでした。今回はモーターを動かすことに挑戦してみたいと思います。

電池ボックスとモーターの配線準備

 今回は下記のモーターと電池ボックスを使用します。

akizukidenshi.com

akizukidenshi.com

 いずれもリード線はついていますが、そのままではブレッドボードにさすには不便なので、下記サイトを参考にリード線を加工しました。

ブレッドボードで電子回路の実験をするために乾電池ホルダーのリード線をジャンパワイヤのように端子化するmatsumotoyoshio.wordpress.com

 加工に使った材料は下記の二つです。

f:id:akanuma-hiroaki:20171001150412j:plain:w450

 最終的に下記のようにリード線の先端にピンがついた形になりました。

f:id:akanuma-hiroaki:20170930084942j:plain:w300

ブレッドボードで配線する

 今回モーターを回すためにさらに必要なものとして、下記のモータードライバ(TA7291P)を使用しました。

f:id:akanuma-hiroaki:20170930085035j:plain:w300

 これらを動作確認用の LED と組み合わせて、下記の図のように配線します。ちなみに TA7291P の Fritzing 用の画像は下記サイトのものを使用させていただきました。

無料ダウンロード:Fritzing 部品一覧

f:id:akanuma-hiroaki:20171001203824p:plain

 モータードライバの各PINは下記のように接続しています。

  1. GND
  2. モーターの片方の端子
  3. 未接続
  4. Vref. Raspberry Pi の 18番PIN
  5. IN1. Raspberry pi の 27番PIN
  6. IN2. Raspberry Pi の 4番PIN
  7. Raspberry Pi の 5V 電源出力
  8. 電池ボックスの+側
  9. 未接続
  10. モーターのもう一方の端子

 TA7291P の4番PINはモーターへの出力調整用なので、電圧を調整できるように Raspberry Pi の PWM対応ピンに接続しています。

モーターを回すサンプルコード

 Ruby で GPIO を操作するために pi_piper を使っていますが、 PWM を使う場合は pi_piper のPwmクラスを使用します。

http://www.rubydoc.info/github/jwhitehorn/pi_piper/PiPiper/Pwm

 まずはサンプルコードの全体を掲載します。

require 'bundler/setup'
require 'pi_piper'

PIN_VREF = 18
PIN_IN_1 = 27
PIN_IN_2 =  4
PIN_LED  = 17 

class MotorSample
  def initialize
    puts 'Initializing MotorSample...'
    @pin_led  = PiPiper::Pin.new(pin: PIN_LED,  direction: :out)
    @pin_in_1 = PiPiper::Pin.new(pin: PIN_IN_1, direction: :out)
    @pin_in_2 = PiPiper::Pin.new(pin: PIN_IN_2, direction: :out)
    @pin_vref = PiPiper::Pwm.new(pin: PIN_VREF)
    @pin_vref.value = 0
  end

  def stop_motor
    puts 'Stopping.'
    @pin_led.off
    @pin_in_1.off
    @pin_in_2.off
    sleep 1
  end

  def toggle_led(i)
    i % 2 == 0 ? @pin_led.on : @pin_led.off
  end

  def speeding_up
    puts 'Speeding Up...'
    0.step(1.0, 0.1) do |v|
      toggle_led((v * 10).to_i)
      @pin_vref.value = v
      sleep 2
    end
  end

  def slowing_down
    puts 'Slowing Down...'
    1.0.step(0, -0.1) do |v|
      toggle_led((v * 10).to_i)
      @pin_vref.value = v
      sleep 2
    end
  end

  def execute
    puts 'Rolling Forward.'
    @pin_in_1.on
    @pin_in_2.off
    speeding_up
    slowing_down

    stop_motor

    puts 'Rolling Backward.'
    @pin_in_1.off
    @pin_in_2.on
    speeding_up
    slowing_down
  end
end

if $0 == __FILE__
  motor_sample = MotorSample.new
  motor_sample.execute
end

 上記のサンプルコードを動かすと、モーターが順方向へ回転し始めて徐々に速度を上げ、最高速度に到達すると徐々に速度を下げていきます。そして今度は逆方向へ同様のことを行って終了します。

 ON/OFF の切り替えで良いピンは PiPiper::Pin を new していますが、 PWM を使いたいピンについては PiPiper::Pwm を new しています。

  def initialize
    puts 'Initializing MotorSample...'
    @pin_led  = PiPiper::Pin.new(pin: PIN_LED,  direction: :out)
    @pin_in_1 = PiPiper::Pin.new(pin: PIN_IN_1, direction: :out)
    @pin_in_2 = PiPiper::Pin.new(pin: PIN_IN_2, direction: :out)
    @pin_vref = PiPiper::Pwm.new(pin: PIN_VREF)
    @pin_vref.value = 0
  end

 TA7291P ではモーターの ON/OFF/回転方向 の切り替えは IN1 と IN2 の ON/OFF の組み合わせて決定します。

  • IN1: OFF IN2: OFF => 停止
  • IN1: ON IN2: OFF => 順方向へ回転
  • IN1: OFF IN2: ON => 逆方向へ回転

 例えば順方向へ回転させるには下記のように指定しています。

    @pin_in_1.on
    @pin_in_2.off

 PWM を使用しているピンについてはその値を 0 〜 1.0 で指定することで出力を調整できます。今回は例えば下記のように徐々に値を増加させ、モーターの回転速度を上げていったりしています。

  def speeding_up
    puts 'Speeding Up...'
    0.step(1.0, 0.1) do |v|
      toggle_led((v * 10).to_i)
      @pin_vref.value = v
      sleep 2
    end
  end

Beacon の電波強度に応じてモーターの回転速度を調節する

 指定した速度で動かすだけでは面白くないので、今度は BLE と組み合わせ、 Beacon の電波強度(RSSI)に応じてモーターの回転速度を変化させてみたいと思います。

 まずはモーターの動作をまとめた Motor クラスを作成します。内容としては単純に各ピンの初期化と、 ON/OFF、速度調節の値を設定する機能をまとめたものです。

require 'bundler/setup'
require 'pi_piper'

class Motor
  def initialize(pin_vref, pin_in_1, pin_in_2)
    @pin_in_1 = PiPiper::Pin.new(pin: pin_in_1, direction: :out)
    @pin_in_2 = PiPiper::Pin.new(pin: pin_in_2, direction: :out)
    @pin_vref = PiPiper::Pwm.new(pin: pin_vref)
    @pin_vref.value = 0
  end

  def stop
    @pin_in_1.off
    @pin_in_2.off
  end

  def forward
    @pin_in_1.on
    @pin_in_2.off
  end

  def backward
    @pin_in_1.off
    @pin_in_2.on
  end

  def adjust(value)
    @pin_vref.value = value
  end
end

 BLE で Beacon の Advertisement を受け取って Motor を操作するコードは下記のようにしました。今回は Beacon として以前にも使ったことのある、 Texas Instruments の SensorTag を使用しています。

require 'bundler/setup'
require 'dbus'
require './motor.rb'

class MotorTest
  SERVICE_NAME = 'org.bluez'
  SERVICE_PATH = '/org/bluez'
  ADAPTER = 'hci0'
  DEVICE_IF = 'org.bluez.Device1'
  DBUS_PROPERTIES_IF = 'org.freedesktop.DBus.Properties'
  PROPERTIES_CHANGED_SIGNAL = 'PropertiesChanged'
  DEVICE_NAME = 'CC2650'

  PIN_VREF = 18
  PIN_IN_1 = 27
  PIN_IN_2 =  4
  PIN_LED  = 17

  def initialize
    @bus = DBus::system_bus
    @bluez = @bus.service(SERVICE_NAME)

    @adapter = @bluez.object("#{SERVICE_PATH}/#{ADAPTER}")
    @adapter.introspect

    @motor = Motor.new(PIN_VREF, PIN_IN_1, PIN_IN_2)
    @motor.forward

    @led = PiPiper::Pin.new(pin: PIN_LED, direction: :out)
  end

  def execute
    @adapter.SetDiscoveryFilter({'Transport' => 'le'})
    @adapter.StartDiscovery

    @led.on

    target_device = nil

    while(target_device.nil?)
      @adapter.introspect
      @adapter.subnodes.each do |node|
        device = @bluez.object("#{SERVICE_PATH}/#{ADAPTER}/#{node}")
        device.introspect

        next unless device.respond_to?(:GetAll)

        properties = device.GetAll(DEVICE_IF).first
        name = properties['Name']
        rssi = properties['RSSI']

        next if name.nil?
        next unless name.downcase.include?(DEVICE_NAME.downcase)

        target_device = device
        break
      end

      sleep(0.1)
    end

    target_device.default_iface = DBUS_PROPERTIES_IF
    target_device.on_signal(PROPERTIES_CHANGED_SIGNAL) do |_, v|
      rssi = v['RSSI']
      vref_value = [[(100 + rssi).to_f / 100, 1.0].min, 0].max
      puts "RSSI: #{rssi} VREF: #{vref_value}"
      @motor.forward
      @motor.adjust(vref_value)
    end

    main = DBus::Main.new
    main << @bus

    main.run
  end
end

if $0 == __FILE__
  motor_test = MotorTest.new
  motor_test.execute
end

 BLE の操作については DBus と BlueZ を使い、 initialize で Bluetooth アダプタの初期化と Motor クラスの初期化を行っています。

  def initialize
    @bus = DBus::system_bus
    @bluez = @bus.service(SERVICE_NAME)

    @adapter = @bluez.object("#{SERVICE_PATH}/#{ADAPTER}")
    @adapter.introspect

    @motor = Motor.new(PIN_VREF, PIN_IN_1, PIN_IN_2)
    @motor.forward

    @led = PiPiper::Pin.new(pin: PIN_LED, direction: :out)
  end

 execute メソッドの中でBLEデバイスのスキャンを行っていますが、検索対象を絞るために SetDiscoveryFilter で BLE デバイスに限定しています。

    @adapter.SetDiscoveryFilter({'Transport' => 'le'})
    @adapter.StartDiscovery

 RSSI が変わると PropertiesChanged シグナルが送られてくるので、シグナルを受け取った場合にモーターの速度を変更するようにしています。 PWM ピンに設定できる値は 0 〜 1.0 なので、その範囲を出ないようにしています。今回は RSSI を厳密に取り扱うことが目的ではないので、上限と下限を超える場合は超える分をカットしている形です。

    target_device.default_iface = DBUS_PROPERTIES_IF
    target_device.on_signal(PROPERTIES_CHANGED_SIGNAL) do |_, v|
      rssi = v['RSSI']
      vref_value = [[(100 + rssi).to_f / 100, 1.0].min, 0].max
      puts "RSSI: #{rssi} VREF: #{vref_value}"
      @motor.forward
      @motor.adjust(vref_value)
    end

 このコードを動かすと、 SensorTag を Raspberry Pi に近づけるとモーターの回転速度が上がり、遠ざけると回転速度が下がります。SensorTag を動かしてからシグナルが送られるまでにタイムラグがあるのですが、とりあえずイメージした感じにはなっているので、今後調べられそうであればシグナル発行のタイミングも調べてみたいと思います。

まとめ

 今回初めて外部電源(電池ボックス)を使ってモーターを動かすことに挑戦してみました。実は途中で一度、横に置いておいた電池ボックスのリード線がいつの間にかショートして、電池ボックスを一つ焦がしてダメにしてしまい、やっぱり電気は怖いなぁと改めて思ったのですが、モーターなどを動かすことができると色々とやれる幅も広がって楽しいですね。モータードライバなどは物によって使い方が違うので、データシートなどから読み解くのは少し大変ですが、色々触って試してみたいと思います。

BLE Nano を光センサー CdS と組み合わせる

 前回までは BLE Nano をそのまま USB のライター DAPLink にさして使っていましたが、このままだと他のセンサー類と組み合わせることができないので、 BLE Nano をブレッドボードにさして使ってみます。まずは BLE Nano をブレッドボードにさして動作させられることを確認し、その後実際のセンサーと組み合わせて値を取得してみます。

ブレッドボードで配線

 写真だとちょっと分かりづらいですが、今回は下記のように配線しました。基本的には BLE Nano をブレッドボードにさして、DAPLink の対応するピンとオス-オスのジャンパーワイヤーで接続しています。

f:id:akanuma-hiroaki:20170918144703j:plain:w450 f:id:akanuma-hiroaki:20170918144714j:plain:w450

 今回は下記のピンを接続しました。

  • SWCLK
  • SWDIO
  • VIN
  • GND
  • TXD
  • RXD

 シリアル接続でのデバッグ等を行わなければ TXD, RXD は不要かもしれません。

 また、今回は LED と抵抗を接続して、前回のプログラムで BLE Nano のオンボードLEDを点滅させていたところを、ブレッドボード上の LED を点滅させてみます。 BLE Nano の P0_4 ピンから 1kΩ の抵抗を経由して LED のアノード側に接続し、カソード側と BLE Nano の GND ピンに接続しています。

ソースコードを変更してコンパイル&実行

 前回実装したコードの LED のピンの指定を下記のように変更します。

DigitalOut led(LED1, 0);
 ↓
DigitalOut led(P0_4, 0);

 そして mbed compile して hex ファイルを BLE Nano に転送すると、ブレッドボード上の LED が点滅を開始し、 LightBlue などでは前回同様にデバイスが検知され、 Characteristic の値が確認できます。

ボタン電池で動かす

 開発・デバッグ時は DAPLink に接続しているので電源もそこから取っていますが、実際に何か BLE デバイスを作成した時には単体で動くようにする必要があるので、ボタン電池で動かしてみます。配線は下記の写真のようにしました。

f:id:akanuma-hiroaki:20170923015011j:plain:w450

 DAPLink の VIN と GND にさしていたピンをそれぞれボタン電池ホルダーの + 側と - 側に繋いで、それ以外のコードを取り外しています。今回はスイッチなどはつけてないので、ボタン電池をつけるとすぐにプログラムが動き始めます。 LightBlue などで接続してみると今まで同様に動作していることが確認できます。

実際のセンサーと組み合わせる

 先ほどまではダミーの値をセンサー値として使用していましたが、今度は実際のセンサーと組み合わせてみます。今回は下記の光センサー CdSセルを使用してみました。

www.aitendo.com

 これを 4.7kΩの抵抗と組み合わせて下記の写真のように配線しました。

f:id:akanuma-hiroaki:20170923015435j:plain:w450 f:id:akanuma-hiroaki:20170923015458j:plain:w450

 コードも実際のセンサー値を使用するように変更します。 main.cpp は下記のようにしました。

#include "mbed.h"
#include "ble/BLE.h"
#include "LuxService.h"

Serial pc(USBTX, USBRX);
DigitalOut led(P0_4, 0);
AnalogIn cds(P0_5);

const static char DEVICE_NAME[] = "LUXSample";
static const uint16_t uuid16_list[] = {LuxService::LUX_SERVICE_UUID};

static volatile bool triggerSensorPolling = false;

unsigned short luxValue;
const int threshold = 500;

LuxService *luxServicePtr;

Ticker ticker;

BLE ble;

void disconnectionCallback(const Gap::DisconnectionCallbackParams_t *params)
{
  BLE::Instance().gap().startAdvertising();
}

void periodicCallback(void)
{
  luxValue = cds.read_u16();
  if (luxValue >= threshold) {
    led = 0;
  } else {
    led = 1;
  }

  triggerSensorPolling = true;
}

void onBleInitError(BLE &ble, ble_error_t error)
{
}

void bleInitComplete(BLE::InitializationCompleteCallbackContext *params)
{
  BLE& ble = params->ble;
  ble_error_t error = params->error;

  if (error != BLE_ERROR_NONE) {
    onBleInitError(ble, error);
    return;
  }

  if (ble.getInstanceID() != BLE::DEFAULT_INSTANCE) {
    return;
  }

  ble.gap().setDeviceName((const uint8_t *) DEVICE_NAME);
  ble.gap().onDisconnection(disconnectionCallback);

  uint16_t initialValueForLUXCharacteristic = 100;
  luxServicePtr = new LuxService(ble, initialValueForLUXCharacteristic);

  ble.gap().accumulateAdvertisingPayload(GapAdvertisingData::BREDR_NOT_SUPPORTED | GapAdvertisingData::LE_GENERAL_DISCOVERABLE);
  ble.gap().accumulateAdvertisingPayload(GapAdvertisingData::COMPLETE_LIST_16BIT_SERVICE_IDS, (uint8_t *)uuid16_list, sizeof(uuid16_list));
  ble.gap().accumulateAdvertisingPayload(GapAdvertisingData::COMPLETE_LOCAL_NAME, (uint8_t *)DEVICE_NAME, sizeof(DEVICE_NAME));
  ble.gap().setAdvertisingType(GapAdvertisingParams::ADV_CONNECTABLE_UNDIRECTED);
  ble.gap().setAdvertisingInterval(1000);
  ble.gap().startAdvertising();
}

int main(void)
{
  pc.baud(115200);
  pc.printf("Starting BLE_LUX Sample...\r\n");

  ticker.attach(periodicCallback, 1);

  pc.printf("Initializing BLE Controller...\r\n");
  ble.init(bleInitComplete);
  pc.printf("Initialized.\r\n");

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

  pc.printf("Starting loop...\r\n");
  while (true) {
    if (triggerSensorPolling && ble.getGapState().connected) {
      triggerSensorPolling = false;
      pc.printf("%u\r\n", luxValue);
      luxServicePtr->updateLuxValue(luxValue);
    } else {
      ble.waitForEvent();
    }
  }
}

 今までダミーの値を生成していた部分を削除し、実際のセンサーデータを読み出す処理を追加しています。 CdSセルからのデータはアナログ入力として受け取ります。また、今までは一秒間隔で点滅させていたLEDを、センサーデータが閾値より小さかったら(暗かったら)点灯し、大きかったら(明るかったら)消すという処理にしています。

void periodicCallback(void)
{
  luxValue = (cds.read_u16() & 0xffff);
  if (luxValue >= threshold) {
    led = 0;
  } else {
    led = 1;
  }

  triggerSensorPolling = true;
}

 LuxService.h の方も全体を掲載しておきますが、前回との変更は受け取るセンサー値の型を unsigned short に変更しただけです。

#ifndef __BLE_LUX_SERVICE_H__
#define __BLE_LUX_SERVICE_H__

class LuxService {
public:
  const static uint16_t LUX_SERVICE_UUID = 0xA000;
  const static uint16_t LUX_VALUE_CHARACTERISTIC_UUID = 0xA001;

  LuxService(BLE &_ble, uint16_t initialValueForLUXCharacteristic) :
    ble(_ble), luxValue(LUX_VALUE_CHARACTERISTIC_UUID, &initialValueForLUXCharacteristic, GattCharacteristic::BLE_GATT_CHAR_PROPERTIES_NOTIFY)
  {
    GattCharacteristic *charTable[] = {&luxValue};
    GattService luxService(LUX_SERVICE_UUID, charTable, sizeof(charTable) / sizeof(GattCharacteristic *));
    ble.gattServer().addService(luxService);
  }

  void updateLuxValue(unsigned short newValue) {
    ble.gattServer().write(luxValue.getValueHandle(), (uint8_t *)&newValue, sizeof(bool));
  }

private:
  BLE &ble;
  ReadOnlyGattCharacteristic<uint16_t> luxValue;
};

#endif

 これで実際のセンサーの計測値をBLEで受け取ることができるようになります。 LightBlue で見てみると下記のように値が取得できます。

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

まとめ

 BLE Nano とボタン電池を使うことで簡単にワイヤレスで動作するデバイスが試作できました。どんなセンサーと組み合わせて何をするかはアイディア次第だと思いますので、日常的に役に立ちそうなものを考えてプロトタイプを作ってみたいと思います。

 ちなみに BLE Nano は日本でも V2 が発売されましたね。スペックも上がってるようですし、 DAPLink のバージョンも 1.0 から 1.5 に上がっているようです。DAPLink 1.0 でも BLE Nano V2 が使えるのかなどは気になるところです。

mag.switch-science.com

BLE Nano で Advertisement & カスタムサービス実装

 前回はとりあえずサンプルコードで BLE Nano を iBeacon として動かしましたが、今回は Peripheral として Advertisement を送信する例と、カスタムサービスを定義して Central との通信を行う例を実装してみたいと思います。

Advertisement 送信

 まずは Advertisement に独自の情報を載せて送信してみます。下記サンプルコードをベースにしています。

BLE_GAP_Example - a mercurial repository | Mbed

 ただ上記サンプルを mbed import でインポートしてビルドしたところエラーになってしまったので、新規にプロジェクトを作って実装してみることにしました。

 まずは mbed new でプロジェクトを作成します。デフォルトだと mbed os 5 向けとして作成されますが、 BLE Nano では mbed os 5 に対応していないようなので、 mbed os 2 向けとして作成するために、 --mbedlib オプションをつけて作成します。

[vagrant@localhost vagrant]$ mbed new BLE_GAP_EXAMPLE --mbedlib                                                       
[mbed] Creating new program "BLE_GAP_EXAMPLE" (git)                                                                   
[mbed] Adding library "mbed" from "https://mbed.org/users/mbed_official/code/mbed/builds" at latest revision in the current branch                                                                                                          
[mbed] Updating reference "mbed" -> "https://mbed.org/users/mbed_official/code/mbed/builds/tip"                       
[mbed] Couldn't find build tools in your program. Downloading the mbed 2.0 SDK tools...                               
[vagrant@localhost vagrant]$ 
[vagrant@localhost vagrant]$ cd BLE_GAP_EXAMPLE/
[vagrant@localhost BLE_GAP_EXAMPLE]$            
[vagrant@localhost BLE_GAP_EXAMPLE]$ ls         
mbed  mbed.bld  mbed_settings.py                

 とりあえず toolchain と target を指定しておきます。

[vagrant@localhost BLE_GAP_EXAMPLE]$ 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_GAP_EXAMPLE]$                                                 
[vagrant@localhost BLE_GAP_EXAMPLE]$ mbed toolchain GCC_ARM                          
[mbed] GCC_ARM now set as default toolchain in program "BLE_GAP_EXAMPLE"             
[vagrant@localhost BLE_GAP_EXAMPLE]$ mbed target RBLAB_BLENANO                       
[mbed] RBLAB_BLENANO now set as default target in program "BLE_GAP_EXAMPLE"          

 ライブラリを更新します。

[vagrant@localhost BLE_GAP_EXAMPLE]$ mbed deploy
[mbed] Updating library "mbed" to branch tip
[mbed] Downloading library build "675da3299148" (might take a minute)
[mbed] Unpacking library build "675da3299148" in "/vagrant/BLE_GAP_EXAMPLE/mbed"
[mbed] Updating the mbed 2.0 SDK tools...

 BLE の Firmware 開発をするには mbed の BLE API が必要なので、 mbed add でライブラリを追加します。

[vagrant@localhost BLE_GAP_EXAMPLE]$ mbed add http://mbed.org/teams/Bluetooth-Low-Energy/code/BLE_API/
[mbed] Adding library "BLE_API" from "https://mbed.org/teams/Bluetooth-Low-Energy/code/BLE_API" at latest revision in the current branch
[mbed] Updating reference "BLE_API" -> "https://mbed.org/teams/Bluetooth-Low-Energy/code/BLE_API/#65474dc93927"

 nRF51822 のライブラリも必要なので mbed add しておきます。

[vagrant@localhost BLE_GAP_EXAMPLE]$ mbed add https://mbed.org/teams/Nordic-Semiconductor/code/nRF51822/
[mbed] Adding library "nRF51822" from "https://mbed.org/teams/Nordic-Semiconductor/code/nRF51822" at latest revision in the current branch
[mbed] Updating reference "nRF51822" -> "https://mbed.org/teams/Nordic-Semiconductor/code/nRF51822/#c90ae1400bf2"

 そして main.cpp はサンプルコードを参考に下記のように実装します。

#include "mbed.h"
#include "ble/BLE.h"

BLE ble;

const static char DEVICE_NAME[] = "SampleDevice";
const static uint8_t AdvData[] = {"SampleAdvertisement"};

void disconnectionCallback(const Gap::DisconnectionCallbackParams_t *params)
{
  BLE::Instance().gap().startAdvertising();
}

void onBleInitError(BLE &ble, ble_error_t error)
{
  (void) ble;
  (void) error;
}

void bleInitComplete(BLE::InitializationCompleteCallbackContext *params)
{
  BLE &ble          = params->ble;
  ble_error_t error = params->error;

  if (error != BLE_ERROR_NONE) {
    onBleInitError(ble, error);
    return;
  }

  if (ble.getInstanceID() != BLE::DEFAULT_INSTANCE) {
    return;
  }

  ble.gap().setDeviceName((const uint8_t *) DEVICE_NAME);
  ble.gap().onDisconnection(disconnectionCallback);

  ble.gap().accumulateAdvertisingPayload(GapAdvertisingData::BREDR_NOT_SUPPORTED | GapAdvertisingData::LE_GENERAL_DISCOVERABLE);
  ble.gap().setAdvertisingType(GapAdvertisingParams::ADV_CONNECTABLE_UNDIRECTED);

  ble.gap().accumulateAdvertisingPayload(GapAdvertisingData::MANUFACTURER_SPECIFIC_DATA, AdvData, sizeof(AdvData));
  ble.gap().accumulateAdvertisingPayload(GapAdvertisingData::COMPLETE_LOCAL_NAME, (uint8_t *) DEVICE_NAME, sizeof(DEVICE_NAME));

  ble.gap().setAdvertisingInterval(100);
  ble.gap().startAdvertising();
}

int main(void)
{
  ble.init(bleInitComplete);

  while (true) {
    ble.waitForEvent();
  }
}

 今回独自のデータとして使用するのは下記のデバイス名とアドバタイズデータです。

const static char DEVICE_NAME[] = "SampleDevice";
const static uint8_t AdvData[] = {"SampleAdvertisement"};

 bleInitComplete() の中で上記データを Advertisement に設定しています。

  ble.gap().accumulateAdvertisingPayload(GapAdvertisingData::MANUFACTURER_SPECIFIC_DATA, AdvData, sizeof(AdvData));
  ble.gap().accumulateAdvertisingPayload(GapAdvertisingData::COMPLETE_LOCAL_NAME, (uint8_t *) DEVICE_NAME, sizeof(DEVICE_NAME));

 これをコンパイルして BLE Nano にコピーします。

[vagrant@localhost BLE_GAP_EXAMPLE]$ mbed compile
Building project BLE_GAP_EXAMPLE (RBLAB_BLENANO, GCC_ARM)
Scan: .
Scan: env
Scan: mbed
Compile [100.0%]: main.cpp
Link: BLE_GAP_EXAMPLE
Elf2Bin: BLE_GAP_EXAMPLE
+-----------+-------+-------+------+
| Module    | .text | .data | .bss |
+-----------+-------+-------+------+
| Fill      |   112 |     3 |   31 |
| Misc      | 33115 |   141 | 1189 |
| Subtotals | 33227 |   144 | 1220 |
+-----------+-------+-------+------+
Allocated Heap: 2728 bytes
Allocated Stack: 2048 bytes
Total Static RAM memory (data + bss): 1364 bytes
Total RAM memory (data + bss + heap + stack): 6140 bytes
Total Flash memory (text + data + misc): 33371 bytes

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

 すると Advertisement の送信が始まりますので、 iOS アプリの LightBlue などで確認すると、下記のように表示されます。

f:id:akanuma-hiroaki:20170917120410p:plain:w300 f:id:akanuma-hiroaki:20170917120439p:plain:w300

 Manufacturer Data には “SampleAdvertisement” という文字列が16進数変換されたものが表示されています。

独自サービス実装

 mbed の BLE API では下記ページに記載されている BLE Service はサポートされていますので、すぐに利用することができます。

BLE services supported on mbed - | Mbed

 例えば Heart Rate Service を使うサンプルに下記のようなものがあります。

BLE_HeartRate - a mercurial repository | Mbed

 また、カスタムのサービスを実装している例として下記のようなサンプルがあります。

BLE_Button - a mercurial repository | Mbed

 これらを参考にしてカスタムサービスを使う例を実装してみました。照度センサーからの値を扱うケースを想定して、 LuxService を実装して利用する例です。まず LuxService.h を下記のような内容で実装します。

#ifndef __BLE_LUX_SERVICE_H__
#define __BLE_LUX_SERVICE_H__

class LuxService {
public:
  const static uint16_t LUX_SERVICE_UUID = 0xA000;
  const static uint16_t LUX_VALUE_CHARACTERISTIC_UUID = 0xA001;

  LuxService(BLE &_ble, uint16_t initialValueForLUXCharacteristic) :
    ble(_ble), luxValue(LUX_VALUE_CHARACTERISTIC_UUID, &initialValueForLUXCharacteristic, GattCharacteristic::BLE_GATT_CHAR_PROPERTIES_NOTIFY)
  {
    GattCharacteristic *charTable[] = {&luxValue};
    GattService luxService(LUX_SERVICE_UUID, charTable, sizeof(charTable) / sizeof(GattCharacteristic *));
    ble.gattServer().addService(luxService);
  }

  void updateLuxValue(uint16_t newValue) {
    ble.gattServer().write(luxValue.getValueHandle(), (uint8_t *)&newValue, sizeof(bool));
  }

private:
  BLE &ble;
  ReadOnlyGattCharacteristic<uint16_t> luxValue;
};

#endif

 照度の値を扱うため、 private の変数として読み取り専用の Characteristic を luxValue として定義します。

private:
  BLE &ble;
  ReadOnlyGattCharacteristic<uint16_t> luxValue;

 コンストラクタでその Characteristic を初期化します。 Characteristic の値が更新された時に Notification を送れるように、初期化時に GattCharacteristic::BLE_GATT_CHAR_PROPERTIES_NOTIFY を渡しています。そして Service 初期化時に Characteristic を登録します。 最後に Service を GATT Server に登録します。

  LuxService(BLE &_ble, uint16_t initialValueForLUXCharacteristic) :
    ble(_ble), luxValue(LUX_VALUE_CHARACTERISTIC_UUID, &initialValueForLUXCharacteristic, GattCharacteristic::BLE_GATT_CHAR_PROPERTIES_NOTIFY)
  {
    GattCharacteristic *charTable[] = {&luxValue};
    GattService luxService(LUX_SERVICE_UUID, charTable, sizeof(charTable) / sizeof(GattCharacteristic *));
    ble.gattServer().addService(luxService);
  }

 さらに照度の値の更新用のメソッドとして void updateLuxValue(uint16_t newValue) を実装しておきます。

  void updateLuxValue(uint16_t newValue) {
    ble.gattServer().write(luxValue.getValueHandle(), (uint8_t *)&newValue, sizeof(bool));
  }

 では LuxService を利用する main.cpp の実装です。コード全体は下記のようになります。

#include "mbed.h"
#include "ble/BLE.h"
#include "LuxService.h"

DigitalOut led(LED1, 0);

const static char DEVICE_NAME[] = "LUXSample";
static const uint16_t uuid16_list[] = {LuxService::LUX_SERVICE_UUID};

static volatile bool triggerSensorPolling = false;

uint16_t luxValue = 100;
static volatile bool riseValue = true;

LuxService *luxServicePtr;

Ticker ticker;

BLE ble;

void disconnectionCallback(const Gap::DisconnectionCallbackParams_t *params)
{
  BLE::Instance().gap().startAdvertising();
}

void periodicCallback(void)
{
  led = !led;
  triggerSensorPolling = true;
}

void onBleInitError(BLE &ble, ble_error_t error)
{
}

void bleInitComplete(BLE::InitializationCompleteCallbackContext *params)
{
  BLE& ble = params->ble;
  ble_error_t error = params->error;

  if (error != BLE_ERROR_NONE) {
    onBleInitError(ble, error);
    return;
  }

  if (ble.getInstanceID() != BLE::DEFAULT_INSTANCE) {
    return;
  }

  ble.gap().setDeviceName((const uint8_t *) DEVICE_NAME);
  ble.gap().onDisconnection(disconnectionCallback);

  uint16_t initialValueForLUXCharacteristic = 100;
  luxServicePtr = new LuxService(ble, initialValueForLUXCharacteristic);

  ble.gap().accumulateAdvertisingPayload(GapAdvertisingData::BREDR_NOT_SUPPORTED | GapAdvertisingData::LE_GENERAL_DISCOVERABLE);
  ble.gap().accumulateAdvertisingPayload(GapAdvertisingData::COMPLETE_LIST_16BIT_SERVICE_IDS, (uint8_t *)uuid16_list, sizeof(uuid16_list));
  ble.gap().accumulateAdvertisingPayload(GapAdvertisingData::COMPLETE_LOCAL_NAME, (uint8_t *)DEVICE_NAME, sizeof(DEVICE_NAME));
  ble.gap().setAdvertisingType(GapAdvertisingParams::ADV_CONNECTABLE_UNDIRECTED);
  ble.gap().setAdvertisingInterval(1000);
  ble.gap().startAdvertising();
}

int main(void)
{
  ticker.attach(periodicCallback, 1);

  ble.init(bleInitComplete);

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

  while (true) {
    if (triggerSensorPolling && ble.getGapState().connected) {
      triggerSensorPolling = false;

      if (riseValue) {
        luxValue++;
      } else {
        luxValue--;
      }

      if (luxValue == 200) {
        riseValue = false;
      } else if (luxValue == 100) {
        riseValue = true;
      }

      luxServicePtr->updateLuxValue(luxValue);
    } else {
      ble.waitForEvent();
    }
  }
}

 カスタムサービスに関連するところをピックアップして簡単に説明します。まずは先ほど実装した LuxService を include します。

#include "LuxService.h"

 UUID のリストは LuxService で定義されている UUID を使用して作成します。

static const uint16_t uuid16_list[] = {LuxService::LUX_SERVICE_UUID};

 bleInitComplete の中で LuxService を初期化します。

  uint16_t initialValueForLUXCharacteristic = 100;
  luxServicePtr = new LuxService(ble, initialValueForLUXCharacteristic);

 あとは main() の中で、センサーから取得した値(今回は擬似的に変化させている値)で LuxService の値を更新します。

      luxServicePtr->updateLuxValue(luxValue);

 そしてコンパイルして BLE Nano に hex ファイルをコピーし、動作を確認してみます。 LightBlue で確認すると下記のようにデバイスがリストに表示されます。

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

 デバイスを選択して接続すると、プロパティに Read と Notify が表示されています。

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

 Characteristic を選択して Notification の Listen を開始すると、毎秒照度の値が更新される度に値が表示されていきます。

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

まとめ

 ひとまずサンプルコードを頼りに見よう見まねで Advertisement の送信と、カスタムサービスを利用する例を実装してみました。細かいところはまだ理解できていないですが、とりあえず動かせるということはわかったので、詳細は順次確認しつつ、実際にセンサーと組み合わせて処理ができるようにしてみたいと思います。

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 に切り替えて使うのが良さそうです。

BLE Nano と mbed CLI で Lチカしてみる

※USB接続の書き込み用ボードとして MK20 がセットになったものと DAPLink がセットになったものがあります。当初 MK20 と書いていましたが、私が購入したものは DAPLink でしたので、修正しました。(2017/09/18)

 BLE デバイスの Firmware 開発に興味があって入門用のものを探したところ、 BLE Nano というのがみつかったので購入しました。

BLE Nano — RedBear

f:id:akanuma-hiroaki:20170910001301j:plain:w450

 BLE Nano 単体のものと、USB接続の書き込み用ボード MK20 もしくは DAPLink がセットになったキットがありますが、初めて買う場合は書き込み用ボードも必要なのでキットを買うことになります。

www.switch-science.com

 スイッチサイエンスでは在庫切れだったのですが、運よくツクモのロボット王国で購入できました。

 業界最小と謳っているだけのことはあり、かなり小さいです。実際にデバイスに搭載されるモジュール部分だけであれば100円硬貨と同じぐらいです。

f:id:akanuma-hiroaki:20170910001752j:plain:w450

 今回はチュートリアルに沿ってオンラインの開発環境で試した後、 mbed CLI でターミナル上から開発できるようにしてみたいと思います。

設定&チュートリアル

 BLE Nano のチュートリアルは下記ページに公開されています。

Getting Started - nRF51822 — RedBear

 ハードウェアセットアップの説明では MK20 と BLE Nano にピンヘッダをはんだ付けすることになっていますが、私が買ったキットは DAPLink とのセットで、すでにはんだ付けされていました。また、MK20 側のホールが余るような説明になっていますが、DAPLink 側のホールは余っていませんでした。

f:id:akanuma-hiroaki:20170910003341j:plain:w450

f:id:akanuma-hiroaki:20170910003411j:plain:w450

 BLE Nano の開発環境としては、 GCC、 mbed、 Arduino IDE に対応しています。手軽に使えそうなのは mbed なので、今回は mbed で試してみたいと思います。チュートリアルページで紹介されている mbed のリンク先ではアカウント登録はできないようで、下記ページから登録しました。

developer.mbed.org

 アカウント登録が終わったら、BLE Nano + DAPLink をUSBポートに挿すと、USBドライブとして認識されます。私の環境では /Volumes/DAPLINK として認識されていました。ドライブの中に含まれている MBED.HTM をブラウザで開きます。

f:id:akanuma-hiroaki:20170910011134p:plain

 RedBearLab BLE Nano の画面が開きますので、画面右上の Compiler メニューをクリックするとオンラインの開発環境にアクセスでき、下記のような画面になります。

f:id:akanuma-hiroaki:20170910004607p:plain

 画面右上の RedBearLab BLE Nano と表示されているところをクリックすると対象のプラットフォームを選択できますが、すでに BLE Nano が選択されているのでそのままで問題ないと思います。

 また、画面左上の インポート メニューから、公開されているリポジトリのプログラムをインポートすることができます。

f:id:akanuma-hiroaki:20170910005602p:plain

 チュートリアルでは mbed_blinky を検索して選択していますが、検索ではチュートリアルと同じものがみつからなかったので、直接下記URLを指定する形でインポートしました。

https://developer.mbed.org/teams/mbed/code/mbed_blinky/

 無事にインポートされると下記のようにファイルリストが表示されます。

f:id:akanuma-hiroaki:20170910005938p:plain

 main.cpp をダブルクリックして内容を表示し、チュートリアルにあるように、 myled の値を P0_19 に変更します。

f:id:akanuma-hiroaki:20170910010036p:plain

 そして画面上部の コンパイル メニューをクリックするとコンパイルが実行され、 mbed_blinky_RBLAB_BLENANO.hex というファイルがビルドされますので、 /Volumes/DAPLINK にコピーすると BLE Nano にプログラムが書き込まれ、実行されます。

f:id:akanuma-hiroaki:20170910011952j:plain:w450

mbed CLI

 チュートリアルで紹介されているのはここまでですが、オフライン環境でも開発できて CLI から使える mbed CLI というのがあったので、こちらで同様のことを行ってみたいと思います。私としては vim から編集できて、コマンドラインからコンパイル等も行う方が好きなので、良さそうであればこちらのやり方でやっていきたいと思います。

 開発環境の設定については下記ページで紹介されていますので、紹介されている通りに手順を実行します。 Python, Git, Mercurial, GCC をインストールした後で mbed CLI をインストールする形になります。私の場合は Mac 環境なので、後者のページの手順を実行しました。

mbed オフラインの開発環境 | Mbed

mbed CLI (コマンドライン・インタフェース)を Mac OS X で使ってみる | Mbed

 必要なもののインストールが終わったら作業ディレクトリに移動して、オンラインの開発環境でやったのと同じように、公開されているプログラムをインポートします。

mbed_cli  $ mbed import https://developer.mbed.org/teams/mbed/code/mbed_blinky/
[mbed] Importing program "mbed_blinky" from "https://developer.mbed.org/teams/mbed/code/mbed_blinky" at latest revision in the current branch
[mbed] Adding library "mbed" from "https://mbed.org/users/mbed_official/code/mbed/builds" at rev #a97add6d7e64
[mbed] Downloading library build "a97add6d7e64" (might take a minute)
[mbed] Unpacking library build "a97add6d7e64" in "/Users/akanuma/workspace/mbed_cli/mbed_blinky/mbed"
[mbed] Couldn't find build tools in your program. Downloading the mbed 2.0 SDK tools...
[mbed] Auto-installing missing Python modules...

 上記の例だと mbed_blinky というディレクトリが作成されているのでその中に入ります。

mbed_cli  $ cd mbed_blinky/
mbed_blinky  $ 
mbed_blinky  $ ls -ltr
total 24
-rw-r--r--  1 akanuma  staff    66 Sep  9 23:14 mbed.bld
-rw-r--r--  1 akanuma  staff   168 Sep  9 23:14 main.cpp
drwxr-xr-x  4 akanuma  staff   136 Sep  9 23:17 mbed
-rw-r--r--  1 akanuma  staff  1329 Sep  9 23:18 mbed_settings.py

 mbed detect コマンドを実行すると、接続されているデバイスを検出してくれるので実行してみます。

mbed_blinky  $ mbed detect
Traceback (most recent call last):
  File "/Users/akanuma/workspace/mbed_cli/mbed_blinky/.temp/tools/detect_targets.py", line 25, in <module>
    from tools.options import get_default_options_parser
  File "/Users/akanuma/workspace/mbed_cli/mbed_blinky/.temp/tools/options.py", line 21, in <module>
    from tools.toolchains import TOOLCHAINS
  File "/Users/akanuma/workspace/mbed_cli/mbed_blinky/.temp/tools/toolchains/__init__.py", line 29, in <module>
    from tools.config import Config
  File "/Users/akanuma/workspace/mbed_cli/mbed_blinky/.temp/tools/config/__init__.py", line 29, in <module>
    from tools.arm_pack_manager import Cache
  File "/Users/akanuma/workspace/mbed_cli/mbed_blinky/.temp/tools/arm_pack_manager/__init__.py", line 2, in <module>
    from bs4 import BeautifulSoup
ImportError: No module named bs4
[mbed] ERROR: "/usr/local/opt/python/bin/python2.7" returned error code 1.
[mbed] ERROR: Command "/usr/local/opt/python/bin/python2.7 -u /Users/akanuma/workspace/mbed_cli/mbed_blinky/.temp/tools/detect_targets.py" in "/Users/akanuma/workspace/mbed_cli/mbed_blinky"
---

 bs4 というモジュールがないということなので、 pip でインストールします。

mbed_blinky  $ pip install beautifulsoup4
Collecting beautifulsoup4
  Downloading beautifulsoup4-4.6.0-py2-none-any.whl (86kB)
    100% |████████████████████████████████| 92kB 2.5MB/s 
Installing collected packages: beautifulsoup4
Successfully installed beautifulsoup4-4.6.0
mbed_blinky  $ 
mbed_blinky  $ mbed detect                                                                                                                                                                                                                    
Traceback (most recent call last):
  File "/Users/akanuma/workspace/mbed_cli/mbed_blinky/.temp/tools/detect_targets.py", line 25, in <module>
    from tools.options import get_default_options_parser
  File "/Users/akanuma/workspace/mbed_cli/mbed_blinky/.temp/tools/options.py", line 21, in <module>
    from tools.toolchains import TOOLCHAINS
  File "/Users/akanuma/workspace/mbed_cli/mbed_blinky/.temp/tools/toolchains/__init__.py", line 29, in <module>
    from tools.config import Config
  File "/Users/akanuma/workspace/mbed_cli/mbed_blinky/.temp/tools/config/__init__.py", line 29, in <module>
    from tools.arm_pack_manager import Cache
  File "/Users/akanuma/workspace/mbed_cli/mbed_blinky/.temp/tools/arm_pack_manager/__init__.py", line 20, in <module>
    from fuzzywuzzy import process
ImportError: No module named fuzzywuzzy
[mbed] ERROR: "/usr/local/opt/python/bin/python2.7" returned error code 1.
[mbed] ERROR: Command "/usr/local/opt/python/bin/python2.7 -u /Users/akanuma/workspace/mbed_cli/mbed_blinky/.temp/tools/detect_targets.py" in "/Users/akanuma/workspace/mbed_cli/mbed_blinky"
---

 今度は fuzzywuzzy がないということなのでこちらもインストールします。

mbed_blinky  $ pip install fuzzywuzzy
Collecting fuzzywuzzy
  Downloading fuzzywuzzy-0.15.1-py2.py3-none-any.whl
Installing collected packages: fuzzywuzzy
Successfully installed fuzzywuzzy-0.15.1
mbed_blinky  $ 
mbed_blinky  $ mbed detect                                                                                                                                                                                                                    

[mbed] Detected RBLAB_BLENANO, port /dev/tty.usbmodem1412, mounted /Volumes/DAPLINK
[mbed] Supported toolchains for RBLAB_BLENANO
+--------+-----------+-----------+-----+---------+-----+
| Target | mbed OS 2 | mbed OS 5 | ARM | GCC_ARM | IAR |
+--------+-----------+-----------+-----+---------+-----+
+--------+-----------+-----------+-----+---------+-----+
Supported targets: 0

mbed_blinky  $ 

 Detected RBLAB_BLENANO ということで、 BLE Nano が検出されました。表の中と、 Supported targets に何も表示されていないのが気になるところですがひとまず進みます。続いてツールチェーン(コンパイラ)とターゲットを指定します。

mbed_blinky  $ mbed toolchain GCC_ARM
[mbed] GCC_ARM now set as default toolchain in program "mbed_blinky"
mbed_blinky  $ 
mbed_blinky  $ mbed target RBLAB_BLENANO
[mbed] RBLAB_BLENANO now set as default target in program "mbed_blinky"

 それではコンパイルしてみます。 mbed compile を実行するだけです。

mbed_blinky  $ mbed compile
Building project mbed_blinky (RBLAB_BLENANO, GCC_ARM)
Scan: .
Scan: env
Scan: mbed
Compile [100.0%]: main.cpp
Link: mbed_blinky
Elf2Bin: mbed_blinky
+-----------+-------+-------+------+
| Module    | .text | .data | .bss |
+-----------+-------+-------+------+
| Fill      |    20 |     0 |   15 |
| Misc      |  8389 |   124 |  301 |
| Subtotals |  8409 |   124 |  316 |
+-----------+-------+-------+------+
Allocated Heap: 3648 bytes
Allocated Stack: 2048 bytes
Total Static RAM memory (data + bss): 440 bytes
Total RAM memory (data + bss + heap + stack): 6136 bytes
Total Flash memory (text + data + misc): 8533 bytes

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

 コンパイルが実行され、 ./BUILD/RBLAB_BLENANO/GCC_ARM/mbed_blinky.hex というファイルが作成されました。これを BLE Nano にコピーします。

mbed_blinky  $ cp ./BUILD/RBLAB_BLENANO/GCC_ARM/mbed_blinky.hex /Volumes/DAPLINK/

 コピーが完了すると BLE Nano でプログラムが実行されます。

 最初は Vagrant で Linux の VM 上に mbed CLI の環境を作ってみたのですが、インポートしようとすると [mbed] Downloading library build "a97add6d7e64" (might take a minute) で止まってしまって進まなかったので、Macに直接環境を作りました。

デバッグ環境の設定

 前述の mbed CLI の参考サイトにデバッグ方法も紹介されていたので環境設定をしてみました。紹介されている方法の中でMacで使えるのは Eclipse と pyOCD でのデバッグなので、その方法を試してみました。

 まずは Eclipse IDE for C/C++ Developers をダウンロードして解凍し、 Eclipse.app を Applications ディレクトリに移動して起動します。

 次は GNU ARM Eclipse plugin をインストールということなんですが、前述のページで紹介されているURLは Deprecated ということで、色々調べたところ、 GNU ARM plugin は GNU MCU plugin に変わっているようで、下記のサイトでインストール方法が紹介されていました。

gnu-mcu-eclipse.github.io

 Marketplace で検索してインストールするやり方ではエラーになってしまったので Install New Software から下記のURLを指定してインストールしました。

http://gnu-mcu-eclipse.netlify.com/v4-neon-updates/

 PyOCD はインストール済みでした。

mbed_cli  $ pip install pyocd
Requirement already satisfied: pyocd in /usr/local/lib/python2.7/site-packages
Requirement already satisfied: websocket-client in /usr/local/lib/python2.7/site-packages (from pyocd)
Requirement already satisfied: enum34 in /usr/local/lib/python2.7/site-packages (from pyocd)
Requirement already satisfied: intelhex in /usr/local/lib/python2.7/site-packages (from pyocd)
Requirement already satisfied: six in /usr/local/lib/python2.7/site-packages (from pyocd)
Requirement already satisfied: future in /usr/local/lib/python2.7/site-packages (from pyocd)
Requirement already satisfied: hidapi in /usr/local/lib/python2.7/site-packages (from pyocd)
Requirement already satisfied: setuptools>=19.0 in /usr/local/lib/python2.7/site-packages (from hidapi->pyocd)

 GDB がインストールされていなかったので brew でインストールします。

mbed_blinky  $ brew install gdb

 環境の準備ができたら --profile debug オプションをつけてプログラムをコンパイルします。

mbed_blinky  $ mbed compile --profile debug
Building project mbed_blinky (RBLAB_BLENANO, GCC_ARM)
Scan: .
Scan: env
Scan: mbed
Compile [100.0%]: main.cpp
Link: mbed_blinky
Elf2Bin: mbed_blinky
+-----------+-------+-------+------+
| Module    | .text | .data | .bss |
+-----------+-------+-------+------+
| Fill      |    21 |     0 |   15 |
| Misc      |  8604 |   124 |  301 |
| Subtotals |  8625 |   124 |  316 |
+-----------+-------+-------+------+
Allocated Heap: 3648 bytes
Allocated Stack: 2048 bytes
Total Static RAM memory (data + bss): 440 bytes
Total RAM memory (data + bss + heap + stack): 6136 bytes
Total Flash memory (text + data + misc): 8749 bytes

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

 そして Eclipse からデバッグ設定をします。Eclipse での設定も前述の mbed オフラインの開発環境 | Mbed のページに書かれています。

 「Run」メニューの 「Debug Configurations…」からデバッグ設定をします。GDB PyOCD Debugging の下に新しい設定を作成し、 C/C++ Application に先ほどコンパイルして作成されたファイルを指定します。

f:id:akanuma-hiroaki:20170910145301p:plain

 「Debugger」タブで pyOCD の Executable に pyocd-gdbserver のパスを設定し、GDB の Executable に gdb のパスを設定します。

f:id:akanuma-hiroaki:20170910145520p:plain

 ここまで設定したら設定を保存して Debug ボタンでデバッグを開始します。すると Eclipse の Debug Perspective が表示され、実行状況が見られるようになります。

f:id:akanuma-hiroaki:20170910145637p:plain

 試した限りではまだ実行中のコードを見ることができていないので、今後その辺りは調べてみようと思います。

まとめ

 もともと Web 系のエンジニアだった自分としては BLE の Firmware 開発は敷居が高かったのですが、 BLE Nano と mbed で、ひとまず動かしてみるぐらいまでは手軽にできました。また、 mbed CLI でオフラインでターミナルから開発することができるので、 vim でコードを書いたり、Git でバージョン管理もやりやすそうなのですが、私の環境では mbed CLI だとプログラムのインポートや新規作成に10分以上かかってしまっていたので、初期に色々試してみる場合はオンラインの開発環境の方がやりやすいかもしれません。まだ単体で動かしているだけですが、今後色々なセンサーと組み合わせて動かせるようにしていきたいと思います。

OpenBlocks IoT BX1 から Ruby で BLE デバイスにアクセスする

 以前の記事で OpenBlocks IoT BX1 の WebUI から設定を行うことで、 SensorTag のデータ取得を行いました。

blog.akanumahiroaki.com

 SensorTag は BX1 のサポート対象になっているため、 WebUI からの設定のみでセンサーデータを読み取ることができていましたが、サポート対象外の BLE デバイスの場合にはデータを取得するための処理を自前で実装する必要があります。そこで今回は Ruby のスクリプトから SensorTag のデータを読み取ってみたいと思います。

ssh設定

 まずは作業を行いやすいように、 BX1 に ssh でログインできるようにします。工場出荷状態から作業をしたとすると、上記の記事の「初期設定」の項目の内容を実施し、 Wi-Fi 経由でアクセスできるようにします。

 初期設定が終わり再起動が完了したら再び WebUI にアクセスし、「システム」メニューの「フィルター」タブで「SSH」のラジオボタンを「有効」にします。また、再起動後も設定が保持されるように、「再起動後もフィルタ解放設定を有効にする」にチェックを入れて保存します。

f:id:akanuma-hiroaki:20170903224117p:plain

 「SSH関連」タブで ssh の詳細設定を行うことができますが、今回はとりあえずのお試しということで、特に設定は変更せず、 root ログインも許可します。実際の本番運用時にはセキュリティ的に問題ないように設定を変更する必要があるかと思います。

f:id:akanuma-hiroaki:20170903224319p:plain

 root ユーザのパスワードは「パスワード」タブで設定できます。

f:id:akanuma-hiroaki:20170903224517p:plain

 これで Wi-Fi 経由で ssh ログインができるようになります。

$ ssh root@192.168.10.100
root@192.168.10.100's password: 
Linux obsiot.example.org 3.10.17-poky-edison #1 SMP PREEMPT Thu Jun 1 16:35:38 JST 2017 i686

The programs included with the Debian GNU/Linux system are free software;
the exact distribution terms for each program are described in the
individual files in /usr/share/doc/*/copyright.

Debian GNU/Linux comes with ABSOLUTELY NO WARRANTY, to the extent
permitted by applicable law.
Last login: Sun Sep  3 22:47:04 2017 from 192.168.10.8

デフォルトの BlueZ バージョンでのアクセス確認

 BX1 にはデフォルトで BlueZ がインストールされていますが、バージョンは少し古く、 5.23 です。

root@obsiot:~# apt-cache show bluez
Package: bluez
Source: bluez (5.23-2)
Version: 5.23-2+b1
Installed-Size: 3043
Maintainer: Debian Bluetooth Maintainers <pkg-bluetooth-maintainers@lists.alioth.debian.org>
Architecture: i386
Replaces: bluez-audio (<= 3.36-3), bluez-input, bluez-network, bluez-serial, bluez-utils (<= 3.36-3), udev (<< 170-1)
Depends: libc6 (>= 2.15), libdbus-1-3 (>= 1.1.1), libglib2.0-0 (>= 2.28.0), libreadline6 (>= 6.0), libudev1 (>= 196), init-system-helpers (>= 1.18~), kmod, udev (>= 170-1), lsb-base, dbus
Conflicts: bluez-audio (<= 3.36-3), bluez-utils (<= 3.36-3)
Breaks: udev (<< 170-1)
Description-en: Bluetooth tools and daemons
 This package contains tools and system daemons for using Bluetooth devices.
 .
 BlueZ is the official Linux Bluetooth protocol stack. It is an Open Source
 project distributed under GNU General Public License (GPL).
Description-md5: ef25d6a9f4a57e78f32faa7b58ef4e59
Multi-Arch: foreign
Homepage: http://www.bluez.org
Tag: admin::hardware, admin::kernel, hardware::TODO, implemented-in::c,
 protocol::TODO, role::program, use::driver, use::synchronizing
Section: admin
Priority: optional
Filename: pool/main/b/bluez/bluez_5.23-2+b1_i386.deb
Size: 750516
MD5sum: 6f731b661885f89ce75d9204a0ca7a8a
SHA1: 502e7f2d1d99b615313e846fd07177d1cc763e2f
SHA256: 3ab85fc151b51dfe91830dd546554fe739dbd7ac069c091eccb39cce88d9a79f

 apt-get でインストールできるのはこれが最新らしいのですが、執筆時の BlueZ の最新バージョンは 5.46 ですので、少し開きがあります。

 また、 bluetoothd は –experimental オプションなしで起動しています。

root@obsiot:~# ps aux | grep bluetoothd | grep -v grep
root      1498  0.0  0.1   5236  1804 ?        S    23:03   0:00 /usr/sbin/bluetoothd

 –experimental オプション付きで起動するには、 /etc/init.d/bluetooth を編集して、 SSD_OPTIONS の行の内容を下記のように変更します。

SSD_OPTIONS="--oknodo --quiet --exec $DAEMON -- $NOPLUGIN_OPTION"
 ↓
SSD_OPTIONS="--oknodo --quiet --exec $DAEMON -- $NOPLUGIN_OPTION --experimental"

 変更したら bluetoothd を再起動すると、 –experimental オプション付きで起動します。

root@obsiot:~# /etc/init.d/bluetooth restart
Stopping bluetooth: /usr/sbin/bluetoothd.
Starting bluetooth: bluetoothd.
root@obsiot:~# 
root@obsiot:~# ps aux | grep bluetoothd
root     18692  0.3  0.1   5236  1812 ?        S    23:51   0:00 /usr/sbin/bluetoothd --experimental
root     18710  0.0  0.0   5748   832 pts/1    S+   23:51   0:00 grep bluetoothd

 そしてデフォルトでは Bluetooth コントローラは有効になっていません。

root@obsiot:~# rfkill list
0: phy0: Wireless LAN
        Soft blocked: no
        Hard blocked: no
1: brcmfmac-wifi: Wireless LAN
        Soft blocked: no
        Hard blocked: no
2: bcm43xx Bluetooth: Bluetooth
        Soft blocked: yes
        Hard blocked: no

 この状態で bluetoothctl を使用しても下記のようにエラーになります。

root@obsiot:~# bluetoothctl
[bluetooth]# power on
No default controller available

 有効にするためには下記コマンドを実行します。

root@obsiot:~# bluetooth_rfkill_event &
root@obsiot:~# rfkill unblock bluetooth

 実行時の出力は下記のようになります。

root@obsiot:~# bluetooth_rfkill_event &
[1] 3752
root@obsiot:~# 1504361980.098955: idx 2 type 2 op 0 soft 1 hard 0

root@obsiot:~# rfkill unblock bluetooth
1504362012.636847: idx 2 type 2 op 2 soft 0 hard 0
root@obsiot:~# execute brcm_patchram_plus --use_baudrate_for_download --no2bytes --enable_fork --enable_lpm --enable_hci --baudrate 3000000 --patchram /etc/firmware/bcm43341.hcd --bd_addr 98:4F:EE:04:B3:24 --scopcm 1,0,0,0,0,0,0,0,0,0 /dev/ttyMFD0
Done setting line discipline
1504362013.132510: idx 3 type 2 op 0 soft 0 hard 0

 rfkill については下記サイトで説明されています。無線デバイスのON/OFFを行うためのコマンドになります。

3.12. RFKill

 実行後は下記のように hci0 のブロックが解除されて、使用できるようになっています。

root@obsiot:~# rfkill list
0: phy0: Wireless LAN
        Soft blocked: no
        Hard blocked: no
1: brcmfmac-wifi: Wireless LAN
        Soft blocked: no
        Hard blocked: no
2: bcm43xx Bluetooth: Bluetooth
        Soft blocked: no
        Hard blocked: no
3: hci0: Bluetooth
        Soft blocked: no
        Hard blocked: no

 これで bluetoothctl 等で BLE デバイスにアクセスできるようになりますので、 bluetoothctl から SensorTag に接続してみます。

root@obsiot:~# bluetoothctl
[NEW] Controller 98:4F:EE:04:B3:24 BlueZ 5.23 [default]
[NEW] Device CC:78:AB:7F:65:87 CC2650 SensorTag
[bluetooth]# scan on
Discovery started
[CHG] Controller 98:4F:EE:04:B3:24 Discovering: yes
[NEW] Device 62:BA:E7:35:DB:E8 62-BA-E7-35-DB-E8
[NEW] Device 34:36:3B:C7:FB:E9 34-36-3B-C7-FB-E9
[CHG] Device 62:BA:E7:35:DB:E8 RSSI: -41
[CHG] Device CC:78:AB:7F:65:87 RSSI: -46
[bluetooth]# devices
Device CC:78:AB:7F:65:87 CC2650 SensorTag
Device 62:BA:E7:35:DB:E8 62-BA-E7-35-DB-E8
Device 34:36:3B:C7:FB:E9 34-36-3B-C7-FB-E9
[bluetooth]# scan off
[bluetooth]# connect CC:78:AB:7F:65:87
Attempting to connect to CC:78:AB:7F:65:87
[CHG] Device CC:78:AB:7F:65:87 Connected: yes
Connection successful
[CHG] Device CC:78:AB:7F:65:87 UUIDs:
        00001800-0000-1000-8000-00805f9b34fb
        00001801-0000-1000-8000-00805f9b34fb
        0000180a-0000-1000-8000-00805f9b34fb
        0000180f-0000-1000-8000-00805f9b34fb
        0000ffe0-0000-1000-8000-00805f9b34fb
        f000aa00-0451-4000-b000-000000000000
        f000aa20-0451-4000-b000-000000000000
        f000aa40-0451-4000-b000-000000000000
        f000aa64-0451-4000-b000-000000000000
        f000aa70-0451-4000-b000-000000000000
        f000aa80-0451-4000-b000-000000000000
        f000ac00-0451-4000-b000-000000000000
        f000ccc0-0451-4000-b000-000000000000
        f000ffc0-0451-4000-b000-000000000000
[bluetooth]# info CC:78:AB:7F:65:87
Device CC:78:AB:7F:65:87
        Name: CC2650 SensorTag
        Alias: CC2650 SensorTag
        Paired: no
        Trusted: no
        Blocked: no
        Connected: yes
        LegacyPairing: no
        UUID: Generic Access Profile    (00001800-0000-1000-8000-00805f9b34fb)
        UUID: Generic Attribute Profile (00001801-0000-1000-8000-00805f9b34fb)
        UUID: Device Information        (0000180a-0000-1000-8000-00805f9b34fb)
        UUID: Battery Service           (0000180f-0000-1000-8000-00805f9b34fb)
        UUID: Unknown                   (0000ffe0-0000-1000-8000-00805f9b34fb)
        UUID: Vendor specific           (f000aa00-0451-4000-b000-000000000000)
        UUID: Vendor specific           (f000aa20-0451-4000-b000-000000000000)
        UUID: Vendor specific           (f000aa40-0451-4000-b000-000000000000)
        UUID: Vendor specific           (f000aa64-0451-4000-b000-000000000000)
        UUID: Vendor specific           (f000aa70-0451-4000-b000-000000000000)
        UUID: Vendor specific           (f000aa80-0451-4000-b000-000000000000)
        UUID: Vendor specific           (f000ac00-0451-4000-b000-000000000000)
        UUID: Vendor specific           (f000ccc0-0451-4000-b000-000000000000)
        UUID: Vendor specific           (f000ffc0-0451-4000-b000-000000000000)
        Modalias: bluetooth:v000Dp0000d0110

 また、 Ruby の環境を構築し、 irb で dbus からアクセスしてみます。Ruby 環境の構築や dbus でのアクセス方法等はこちらをご参照ください。

Raspberry Pi + RubyでLチカ - Tech Blog by Akanuma Hiroaki

Raspberry Pi 3でD-BusからBLEデバイスにアクセスする - Tech Blog by Akanuma Hiroaki

irb(main):038:0* device.GetAll('org.bluez.Device1')
/root/sensortag_sample/vendor/bundle/ruby/2.4.0/gems/ruby-dbus-0.13.0/lib/dbus/message.rb:129: warning: constant ::Fixnum is deprecated
=> [{"Address"=>"CC:78:AB:7F:65:87", "Name"=>"CC2650 SensorTag", "Alias"=>"CC2650 SensorTag", "Paired"=>false, "Trusted"=>false, "Blocked"=>false, "LegacyPairing"=>false, "RSSI"=>-45, "Connected"=>true, "UUIDs"=>["00001800-0000-1000-8000-00805f9b34fb", "00001801-0000-1000-8000-00805f9b34fb", "0000180a-0000-1000-8000-00805f9b34fb", "0000180f-0000-1000-8000-00805f9b34fb", "0000ffe0-0000-1000-8000-00805f9b34fb", "f000aa00-0451-4000-b000-000000000000", "f000aa20-0451-4000-b000-000000000000", "f000aa40-0451-4000-b000-000000000000", "f000aa64-0451-4000-b000-000000000000", "f000aa70-0451-4000-b000-000000000000", "f000aa80-0451-4000-b000-000000000000", "f000ac00-0451-4000-b000-000000000000", "f000ccc0-0451-4000-b000-000000000000", "f000ffc0-0451-4000-b000-000000000000"], "Modalias"=>"bluetooth:v000Dp0000d0110", "Adapter"=>"/org/bluez/hci0"}]
irb(main):039:0> 
irb(main):040:0* device.subnodes
=> []

 BlueZ のバージョンが古いせいか、もしくは –enable-experimental オプション付きでビルドされていないせいか、 SensorTag 等の BLE デバイスにアクセスしても、接続はできるものの、ServicesResolved プロパティも存在せず、 GATT サービスにアクセスすることができませんでした。そこで、パッケージインストールされているデフォルトの bluez をアンインストールし、ソースからビルドしてインストールすることにします。

BlueZ バージョンアップ

 まずはインストール済みの BlueZ をアンインストールします。下記を実行すると openblocks-iot-webui パッケージもアンインストールされ、 WebUI が使えなくなりますのでご注意ください。

root@obsiot:~# sudo apt-get --purge remove bluez
Reading package lists... Done
Building dependency tree       
Reading state information... Done
The following packages were automatically installed and are no longer required:
  arping bind9-host bluez-hcidump cu curl dns-root-data dnsmasq dnsmasq-base dnsutils expect git git-man hostapd iotop isc-dhcp-server libapparmor1 libb-hooks-endofscope-perl libbind9-90 libbluetooth3 libc-ares2 libclass-load-perl
  libclass-singleton-perl libcurl3 libcurl3-gnutls libdatetime-locale-perl libdatetime-perl libdatetime-timezone-perl libdns100 libdrm-intel1 libdrm-nouveau2 libdrm-radeon1 libdrm2 libelf1 libelfg0 liberror-perl libfontenc1 libgc1c2
  libgl1-mesa-dri libgl1-mesa-glx libglapi-mesa libglib2.0-bin libglib2.0-dev libice6 libisc95 libisccc90 libisccfg90 libjansson4 libjson-c-dev libjson0 libjson0-dev liblist-allutils-perl liblist-moreutils-perl libllvm3.5
  liblockfile-bin liblockfile1 liblua5.1-0 liblua5.1-0-dev liblwres90 libmodule-implementation-perl libmodule-runtime-perl libmysqlclient18 libnamespace-clean-perl libnet1 libnetfilter-conntrack3 libnl-route-3-200 libonig2
  libpackage-stash-perl libpackage-stash-xs-perl libparams-classify-perl libparams-validate-perl libpciaccess0 libpcre3-dev libpcrecpp0 libperl4-corelibs-perl libpq5 libpython-stdlib libpython2.7-minimal libpython2.7-stdlib libqdbm14
  librtmp1 libsm6 libsqlite3-0 libssh2-1 libsub-exporter-progressive-perl libsub-identify-perl libsub-name-perl libtcl8.6 libtk8.6 libtool-bin libtry-tiny-perl libtxc-dxtn-s2tc0 libutempter0 libv8-3.14.5 libvariable-magic-perl
  libx11-xcb1 libxaw7 libxcb-dri2-0 libxcb-dri3-0 libxcb-glx0 libxcb-present0 libxcb-shape0 libxcb-sync1 libxcomposite1 libxdamage1 libxext6 libxfixes3 libxft2 libxi6 libxinerama1 libxmu6 libxmuu1 libxrandr2 libxrender1 libxshmfence1
  libxss1 libxt6 libxtst6 libxv1 libxxf86dga1 libxxf86vm1 lockfile-progs lrzsz lsof lua-json lua-lpeg lua5.1 minicom mysql-common nginx nkf nodejs nodejs-legacy ntpdate openblocks-iot-wstunnel pd-adder pd-emitter-lite pd-handler-ble
  pd-handler-conf pd-handler-plc pd-handler-uart pd-subscriber ph-nodered-packs ph-supervisor-nodered php-auth-sasl php-gettext php-http-request php-mail php-mail-mime php-net-dime php-net-smtp php-net-socket php-net-url php-pear
  php-screw php-soap php5-cgi php5-cli php5-common php5-fpm php5-json php5-readline pkg-config pppconfig python python-meld3 python-messaging python-minimal python-nose python-pkg-resources python-serial python-six python2.7
  python2.7-minimal rsync shellinabox spp-assist sqlite3 sshpass sudo supervisor tcl-expect tcl8.6 tk8.6 x11-common x11-utils xbitmaps xterm
Use 'apt-get autoremove' to remove them.
The following packages will be REMOVED:
  bluez* openblocks-iot-webui*
0 upgraded, 0 newly installed, 2 to remove and 90 not upgraded.
After this operation, 3982 kB disk space will be freed.
Do you want to continue? [Y/n] Y
(Reading database ... 45677 files and directories currently installed.)
Removing openblocks-iot-webui (2.1.1-10) ...
Purging configuration files for openblocks-iot-webui (2.1.1-10) ...
dpkg: warning: while removing openblocks-iot-webui, directory '/var/webui/docroot' not empty so not removed
dpkg: warning: while removing openblocks-iot-webui, directory '/var/webui/config' not empty so not removed
dpkg: warning: while removing openblocks-iot-webui, directory '/var/webui/sound' not empty so not removed
Removing bluez (5.23-2+b1) ...
Stopping bluetooth: /usr/sbin/bluetoothd.
Purging configuration files for bluez (5.23-2+b1) ...
Processing triggers for dbus (1.8.20-0+deb8u1) ...
Processing triggers for man-db (2.7.0.2-5) ...

 下記コマンドで BlueZ のビルドに必要なライブラリをインストールします。

root@obsiot:~# apt-get install libdbus-1-dev libdbus-glib-1-dev libglib2.0-dev libical-dev libreadline-dev libudev-dev libusb-dev

 BlueZ のソースを取得して展開します。

root@obsiot:~# wget http://www.kernel.org/pub/linux/bluetooth/bluez-5.46.tar.xz
root@obsiot:~# xz -dv bluez-5.46.tar.xz 
root@obsiot:~# tar -xf bluez-5.46.tar 
root@obsiot:~# cd bluez-5.46

 そして下記コマンドでビルドしてインストールします。

root@obsiot:~/bluez-5.46# ./configure --enable-experimental --disable-systemd
root@obsiot:~/bluez-5.46# make
root@obsiot:~/bluez-5.46# make install

 インストールが終了したら一度 BX1 を再起動し、とりあえず手動で bluetoothd を起動してみます。

root@obsiot:~# /usr/local/libexec/bluetooth/bluetoothd --experimental &
[1] 1657

 そして bluetoothctl で接続してみます。

root@obsiot:~# bluetoothctl
[NEW] Controller 98:4F:EE:04:B3:24 BlueZ 5.46 [default]
[NEW] Device CC:78:AB:7F:65:87 CC2650 SensorTag
Agent registered
[bluetooth]# connect CC:78:AB:7F:65:87
Attempting to connect to CC:78:AB:7F:65:87
[CHG] Device CC:78:AB:7F:65:87 Connected: yes
Connection successful
[NEW] Primary Service
        /org/bluez/hci0/dev_CC_78_AB_7F_65_87/service0008
        00001801-0000-1000-8000-00805f9b34fb
        Generic Attribute Profile
[NEW] Primary Service
        /org/bluez/hci0/dev_CC_78_AB_7F_65_87/service0009
        0000180a-0000-1000-8000-00805f9b34fb
        Device Information
[NEW] Characteristic
        /org/bluez/hci0/dev_CC_78_AB_7F_65_87/service0009/char000a
        00002a23-0000-1000-8000-00805f9b34fb
        System ID
〜〜〜中略〜〜〜
[NEW] Characteristic
        /org/bluez/hci0/dev_CC_78_AB_7F_65_87/service0063/char006f
        f000ffc4-0451-4000-b000-000000000000
        Vendor specific
[NEW] Descriptor
        /org/bluez/hci0/dev_CC_78_AB_7F_65_87/service0063/char006f/desc0071
        00002902-0000-1000-8000-00805f9b34fb
        Client Characteristic Configuration
[NEW] Descriptor
        /org/bluez/hci0/dev_CC_78_AB_7F_65_87/service0063/char006f/desc0072
        00002901-0000-1000-8000-00805f9b34fb
        Characteristic User Description
[CHG] Device CC:78:AB:7F:65:87 ServicesResolved: yes
[CHG] Device CC:78:AB:7F:65:87 Name: SensorTag 2.0
[CHG] Device CC:78:AB:7F:65:87 Alias: SensorTag 2.0
[CC2650 SensorTag]# 

 バージョンアップ前とは違って、サービスの内容まで取得でき、 ServicesResolved プロパティも表示され、 yes となっています。

 irb からアクセスしてみても下記のようにサービスが取得できるようになっています。

irb(main):024:0* device.GetAll('org.bluez.Device1')
/root/sensortag_sample/vendor/bundle/ruby/2.4.0/gems/ruby-dbus-0.13.0/lib/dbus/message.rb:129: warning: constant ::Fixnum is deprecated
=> [{"Address"=>"CC:78:AB:7F:65:87", "Name"=>"SensorTag 2.0", "Alias"=>"SensorTag 2.0", "Paired"=>false, "Trusted"=>false, "Blocked"=>false, "LegacyPairing"=>false, "Connected"=>true, "UUIDs"=>["00001800-0000-1000-8000-00805f9b34fb", "00001801-0000-1000-8000-00805f9b34fb", "0000180a-0000-1000-8000-00805f9b34fb", "0000180f-0000-1000-8000-00805f9b34fb", "0000ffe0-0000-1000-8000-00805f9b34fb", "f000aa00-0451-4000-b000-000000000000", "f000aa20-0451-4000-b000-000000000000", "f000aa40-0451-4000-b000-000000000000", "f000aa64-0451-4000-b000-000000000000", "f000aa70-0451-4000-b000-000000000000", "f000aa80-0451-4000-b000-000000000000", "f000ac00-0451-4000-b000-000000000000", "f000ccc0-0451-4000-b000-000000000000", "f000ffc0-0451-4000-b000-000000000000"], "Modalias"=>"bluetooth:v000Dp0000d0110", "Adapter"=>"/org/bluez/hci0", "ServicesResolved"=>true}]
irb(main):025:0> 
irb(main):026:0* device.subnodes
=> ["service0008", "service0009", "service001c", "service0022", "service002a", "service0032", "service003a", "service0042", "service004a", "service004f", "service0054", "service005b", "service0063"]
irb(main):027:0> 

Ruby スクリプトからのアクセス

 以前 Raspberry Pi からの SensorTag のデータ取得について書いた時に作成した Ruby のスクリプトを使ってデータを取得してみたいと思います。

blog.akanumahiroaki.com

 コードはこちらにも公開しています。

github.com

 sensortag.rb を実行すると、各センサーのデータをそれぞれログファイルに出力します。

root@obsiot:~/sensortag_sample# bundle exec ruby sensortag.rb 

 下記は温度と湿度のセンサーデータの例です。

root@obsiot:~/sensortag_sample# tail -f logs/humidity.log 
I, [2017-09-04T02:11:52.410172 #1787]  INFO -- : temp: 32.1875 hum: 57.23876953125
I, [2017-09-04T02:11:53.422242 #1787]  INFO -- : temp: 32.1875 hum: 57.23876953125
I, [2017-09-04T02:11:54.435289 #1787]  INFO -- : temp: 32.19757080078125 hum: 57.16552734375
I, [2017-09-04T02:11:55.447590 #1787]  INFO -- : temp: 32.19757080078125 hum: 57.16552734375
I, [2017-09-04T02:11:56.460004 #1787]  INFO -- : temp: 32.2076416015625 hum: 57.16552734375
I, [2017-09-04T02:11:57.472740 #1787]  INFO -- : temp: 32.2076416015625 hum: 57.16552734375
I, [2017-09-04T02:11:58.417763 #1787]  INFO -- : temp: 32.2076416015625 hum: 57.16552734375
I, [2017-09-04T02:11:59.430025 #1787]  INFO -- : temp: 32.2076416015625 hum: 57.16552734375
I, [2017-09-04T02:12:00.442741 #1787]  INFO -- : temp: 32.227783203125 hum: 57.16552734375
I, [2017-09-04T02:12:01.455942 #1787]  INFO -- : temp: 32.227783203125 hum: 57.16552734375
I, [2017-09-04T02:12:02.467395 #1787]  INFO -- : temp: 32.227783203125 hum: 57.16552734375

 これで Ruby のスクリプトからでも BLE デバイスのセンサーデータが取得できるようになりました。

ファクトリーリセット

 色々と試行錯誤する中で、何度か工場出荷状態に戻したいということがあり、はじめは結構手こずったので、参考までにファクトリーリセットの手順を紹介しておきます。ベースは下記製品サイトに掲載されている手順になります。

openblocks.plathome.co.jp

 この手順のままやると、ストレージ併用モードを解除して再起動した段階でネットワーク接続等の情報も消えてしまいますので、無線LAN環境で使っている場合にはその設定を行う手順が必要になってきます。

 まずは手順通りにストレージ併用モードを解除して再起動します。

root@obsiot:~# e2label /dev/mmcblk0p10 ""
root@obsiot:~# reboot

 私の環境はバージョン8 (jessie / FW 2.x系)モデルだったので、下記コマンドでファイルシステムを構築します。

root@obsiot:~# yes | mkfs -t ext4 -L DEBIAN /dev/mmcblk0p10

 構築したファイルシステムをマウントします。

root@obsiot:~# mount /dev/mmcblk0p10 /mnt

 そしてここで工場出荷用データを取得できるように、Wi-Fiの設定を行います。

root@obsiot:~# wpa_passphrase MY_AP_SSID MY_AP_PASSWORD >> /etc/wpa_supplicant/wpa_supplicant.conf
root@obsiot:~# chmod 600 /etc/wpa_supplicant/wpa_supplicant.conf

 /etc/network/interface のWi-Fiインタフェース設定を下記のように設定します。 BX1 からインターネットにアクセスできれば良いので、 DHCP でIPアドレスを取得し、 Wi-Fi アクセス設定は先ほどの設定ファイル wpa_supplicant.conf から読み込む形です。

auto wlan0
iface wlan0 inet dhcp
wpa-conf /etc/wpa_supplicant/wpa_supplicant.conf

#auto wlan0
#iface wlan0 inet static
#     address 192.168.1.17
#     network 192.168.1.0
#     netmask 255.255.255.0
#     broadcast 192.168.1.255
#     gateway 192.168.1.1

 設定を有効にするため一旦 Wi-Fi のネットワークインタフェースを停止し、再度起動します。

root@obsiot:~# ifdown wlan0
root@obsiot:~# ifup wlan0

 設定が正しいのに IP アドレスが正しく取得できていない場合は、再度停止・起動を行ってみると正しく取得できることがあります。

 ネットワーク接続が有効になったら下記コマンドで工場出荷用データを取得します。執筆時の最新バージョンは Kernel 3.10.17-101対応 Ver.2.1.1-10 でした。

root@obsiot:~# wget http://ftp.plathome.co.jp/pub/BX1/jessie/3.10.17-101/obsiot_userland_2.1.1-10_20170602.tgz

 そしてデータを展開して再起動します。

root@obsiot:~# tar xzf /root/obsiot_userland_2.1.1-10_20170602.tgz -C /mnt 2> /dev/null
root@obsiot:~# umount /mnt
root@obsiot:~# reboot

 すると工場出荷時の状態で起動し、Wi-Fiインタフェースもアクセスポイントモードで起動してきますので、 iotfamily_シリアル番号 という SSID でアクセスできるようになります。

 下記サイトも参考にさせていただきました。

dev.classmethod.jp

まとめ

 IoT ゲートウェイはそれぞれの製品によって特徴があり、採用されているOSによっても違いが出て来ます。また、Raspberry Pi などとは違った癖があるように思いますし、情報もあまり多くはないと思いますので、細かいことは色々と調べる必要があります。 BX1 のように WebUI から設定できるようになっている場合でも、サポートされていないデバイスを使おうと思うと Linux 上で動く BLE アプリを自前開発する必要がありますので、結構ハードルは高いように感じています。そして今回はとりあえず BLE デバイスのデータを取得するところまでということで、 WebUI も使えないままにしてしまっていますし、さらに最終的にはデータをどこかに送信しないと意味がないので、 その辺りも今後やってみたいと思います。

 今回はこちらも参考にさせていただきました。

openblocks.plathome.co.jp