73
73

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

回路設計なしで省電力なBluetooth LE Beaconを開発する

Last updated at Posted at 2019-10-16

Beaconとは

 Wikipediaによると、Beaconとは

ビーコンは、地上にある無線局などから発射される電波(あるいはIR(赤外線)のような高周波の電磁波)を航空機・船舶・自動車などの移動体に搭載された機器で受信することにより、位置をはじめとした各種情報を取得するための設備である。

とあります。

 BluetoothのBeacon機器の場合、温度や気圧などのセンサを搭載したモジュールを任意の場所に設置して、その場所の環境を観測するために使います。
また、Bluetoothの電波は数十メートルほどしか届かないため、単純にその電波を受信したらBluetooth機器のIDから自分の位置を把握したり、電波強度から位置を調べることもできます。
BluetoothのBeacon機能は非常に省電力で、ボダン電池でも1年以上動作するものも存在します。

概要

 今回はLeafonyというArduino互換の小型な開発基板を使って、定期的に温度を測定する非常に省電力なBluetooth LE Beacon (以下 BLE Beacon)を開発してみました。ボタン電池駆動で半年程度持つことを目標とします。

Leafonyにはセンサ基板やBluetooth基板などを接続するだけで簡単にBluetoothセンサ機器が実現できるため、ハードウェアの設計の設計をせず非常に簡単にオリジナルのBeacon機器を作ることができます。

こんな感じで家の中に置いて部屋の温度を測定するイメージです。
Leafonyであれば、縦横高さ2cmなので非常に小さく、どこに設置しても余り邪魔にはならなさそうです。

ファイル名

開発環境

ハードウェアの準備

Bluetooth機器として使用するハードウェアの開発はLeafonyで行います。

使用する基板はBasic Kitに含まれる下記の5枚です。

  • 4-Sensorsリーフ (温度・湿度・照度・加速度)
  • AVR MCUリーフ (ATmega328P)
  • USBリーフ
  • BLE Sugarリーフ
  • CR2032コイン電池リーフ
ファイル名

Beaconの実装

データ送信の仕組み

Beaconでのデータのやり取りはAdvertising dataというパケットを使って行われます。
Advertising dataはブロードキャストされるので、周辺のBluetooth機器がすべてこのデータをスキャンすることが可能です。
受信する側は常にスキャンを行っていればBeacon機器側は好きなタイミングでデータを送信して、送信していないときはスリープを行うことができるため、電力を抑えられます。
このAdvertising dataの中にデバイス名や送信時の電力、温度センサのデータなどを入れて送ることでBeaconとして動作させることができます。

Bluetooth 4.0 の仕様書を読むと、Advertising dataのパケットについて下図のように書かれています。

image.png
(引用: BLUETOOTH SPECIFICATION Version 4.0 [Vol. 3] page 375 Part C Generic Access Profile 11 Advertising and Scan Response Data Format)

Advertising dataは、いくつかのAD Structureというデータに分けられます。
このAD StructureにはLengthAD TypeAD Dataというデータで構成されています。

AD TypeによってこのAD Structureは何のデータを送るためのものかが決められていて、
例えば
AD Type = 0x09なら、AD Dataはデバイス名 (Complete Local Name) であるとか、
AD Type = 0x0Aなら、AD Dataは送信電力 (TX Power Level) のように、
様々なデータをのせることができます。
どのようなデータがのせられるのかは、下記のリンクが参考になります。

今回は温度センサとコイン電池の電圧をManufacturer Specific Data (AD Type = 0xFF) に入れてデータを送信します。

ソースコード

実際にLeafony側に書き込むプログラムを見ていきましょう。
adv_data[]が送信するAdvertising dataです。

ここではAD Structure 1FlagAD Structure 2Complete Local NameAD Structure 3Manufacturer Specific Dataで、合計25 octetsのデータを作っています。

AD Structure 1Flag = 0x06なのは下記のリンクが参考になります。

Complete Local NameManufacturer Specific Dataには、
ASCIIコードの文字を入れていきます。

最後に、ble112.ble_cmd_le_gap_set_adv_dataでAdvertising dataをBLEモジュールに書き込んで、
ble112.ble_cmd_le_gap_start_advertisingでブロードキャストを開始しています。

leafony_beacon.ino
void StartAdvData()
{
  (中略)

  /* [set Advertising Data]  25byte MAX*/
  uint8 adv_data[] = {
      (2),                                  //0:  field length
      BGLIB_GAP_AD_TYPE_FLAGS,              //1:  field type (0x01)
      (6),                                  //2:  data
      (1),                                  //3:  field length (1は仮の初期値) BGLIB_GAP_AD_TYPE_LOCALNAME_COMPLETE(LOCAL DEVICE NAMEのデータ長
      BGLIB_GAP_AD_TYPE_LOCALNAME_COMPLETE, //4:  field type (0x09)
      (0),                                  //5:  L  1
      (0),                                  //6:  e  2
      (0),                                  //7:  a  3
      (0),                                  //8:  f  4
      (0),                                  //9:  _  5
      (0),                                  //10: A  6
      (0),                                  //11: field length
      (0xff),                               //12: field type (0xff)
      (0),                                  //13: 温度 1 T 1
      (0),                                  //14: 温度 2 X 2
      (0),                                  //15: 温度 3 X 3
      (0),                                  //16: 温度 4 . 4
      (0),                                  //17: 温度 5 X 5
      (0),                                  //18: 温度 6 X 6
      (0),                                  //19: 電圧 S V 7
      (0),                                  //20: 電圧 1 X 8
      (0),                                  //21: 電圧 2 . 9
      (0),                                  //22: 電圧 3 X 10
      (0),                                  //23: 電圧 4 X 11
      (0),                                  //24:
  };

  (中略)

  //アドバタイズデータを登録
  stLen = (5 + lenStr2 + 13);
  ble112.ble_cmd_le_gap_set_adv_data(SCAN_RSP_ADVERTISING_PACKETS, stLen, adv_data); //SCAN_RSP_ADVERTISING_PACKETS
  while (ble112.checkActivity(1000)); /* 受信チェック */

  /* start */
  // index = 0  LE_GAP_SCANNABLE_NON_CONNECTABLE / LE_GAP_UNDIRECTED_CONNECTABLE
  ble112.ble_cmd_le_gap_start_advertising(0, LE_GAP_USER_DATA, LE_GAP_SCANNABLE_NON_CONNECTABLE);
  while (ble112.checkActivity(1000)); /* 受信チェック */
}

省電力化のための実装

Beaconのいいところは、Beacon機器側が好きなタイミングでデータをブロードキャストして、あとはスリープできるところです。
下図のようにAdvertising中はBLEモジュールやマイコン、センサ等が起動しているので電力は大きいですが、
一定期間送信したらスリープモードに突入し、マイコンとBLEをスリープモードに入れたり、
センサの電源端子に接続されたスイッチをマイコンで切ってあげることで、電力を大幅に削減することが可能です。

image.png

データの受信

Beacon信号の受信はPCで行います。
今回はNode.jsのnobleというBluetoothライブラリを使ってBeaconスキャナを実装しました。
Windows 10へのnobleのインストールには苦戦したので、インストール方法に関して別に記事を書きましたのでそちらを参考にしてください。
(このあたりはRaspberry Piでやったほうが圧倒的に楽でした。nobleのインストールも簡単ですし、Bluetoothのモジュールも入っているので、何も難しいことはなくすんなりテストできます。)

スキャナのソースコードは下記の

scan.js
'use strict';

const noble = require('noble');

const textEncoding = require('text-encoding');
const TextDecoder = textEncoding.TextDecoder;

require('date-utils');

//discovered BLE device
const discovered = (peripheral) => {
    const device = {
        name: peripheral.advertisement.localName,
        uuid: peripheral.uuid,
        rssi: peripheral.rssi,
        data: peripheral.advertisement.manufacturerData
    };

    // Leaf_A~Leaf_Fという名前のデバイスだけを表示
    if (String(device.name).match(/^Leaf_[A-F]$/) != null){
        let dt = new Date();
        let dt_s = dt.toFormat('YYYY/MM/DD,HH24:MI:SS');
        let data_s = (new TextDecoder('utf-8')).decode(device.data);
        console.log(`${device.name},${dt_s},${data_s},${device.rssi}`);
    }
}

//BLE scan start
const scanStart = () => {
    noble.startScanning([], true);
    noble.on('discover', discovered);
}

if(noble.state === 'poweredOn'){
    scanStart();
}else{
    noble.on('stateChange', scanStart);
}

Leafonyの発するAdvertisementパケットを特定する方法として今回はデバイス名を使用しました。他にももっといい方法はあるのかもしれませんが。。。

5台のLeafonyを15分おきにデータ送信をするように設定し、
実行するとBeaconの温度、バッテリー残量、電波強度を見ることができました。

> node .\scan.js
Leaf_C,2019/10/28,17:28:20,T23.32V3.02,-64
Leaf_E,2019/10/28,17:42:27,T22.58V2.96,-70
Leaf_A,2019/10/28,17:43:08,T21.40V2.81,-65
Leaf_C,2019/10/28,17:44:32,T23.30V3.02,-65
Leaf_B,2019/10/28,17:49:01,T23.06V2.89,-74
Leaf_D,2019/10/28,17:49:25,T22.92V2.84,-64

TXX.XXが温度、VX.XXがバッテリー残量です。(USBから給電している場合は0.00Vになるようになっています。)

一番最後の数字はRSSIです。RSSIは受信時の電波強度を表しています。
Advertising dataにTxPowerを入れて送信時の電力を知ることができれば、RSSIと比較してBeaconがどのくらいの距離離れた場所にあるかなどもわかります。
詳しくは下記の記事が参考になりました。

電流測定 (追記 2019/12/07)

ボタン電池の電圧3.0Vでの電流値を測定しました。下の図をご覧ください。
横軸が電源を投入してからの時間(単位:秒)、縦軸が電池から流れる電流(単位:アンペア、対数目盛)です。
グラフを見やすくするために、今回は送信時間3秒に対してスリープ時間を8秒と、送信頻度を高く設定しました。

image.png

それでは、実際にボタン電池CR2032でどれくらいの時間動作するのかを見積もってみましょう。

電波送信中は10mA程度で3秒間、スリープ中は60μA程度で8秒間動作しています。
平均すれば1秒あたり2.77mA流れる計算です。

Panasonic製のCR2032は公称容量が225mAhとなっています。つまり、225mA流し続けて1時間もつ容量です。
(参考: CR2032 : CR系コイン形リチウム電池 - 電子デバイス・産業用機器 - Panasonic )

2.77mA流れる場合は、

\frac{225 \rm mA}{2.77 \rm mA} \times 1  \rm hour \fallingdotseq 81.2 \rm hours \fallingdotseq 3.38 \rm days

3日半程度しか持ちませんでした。

では、送信時間は3秒固定でスリープ時間を15分程度とすれば、平均電流は0.093mAとなるので2418時間≒100日となります。
15分に1回通信を行うようにすれば3ヶ月程度もつ計算になりました。
なかなか半年までの道のりは厳しいです。

Leafonyには容量が620mAhのボタン電池CR2450用のボードもあります。
これは容量が2.75倍ほどあるため、15分澗間隔で8.25ヶ月持つ計算になります。

実際に部屋の温度を測ってみた (追記 2019/10/30)

 研究室の学生居室に、LeafonyのBeaconを5台設置して、各座席の温度と、Beaconの電池残量のログを取ってみました。
受信機はRaspberry Pi 3 B+を使っています。
(ここまでWindowsでの環境構築を説明したのですが、部屋に設置することを考えると、小型で環境構築なラズパイが便利でした。)

部屋の広さはおおよそ6m × 7mでラズパイは部屋の中央付近に設置されています。

ラズパイの環境を構築して、下記のコマンドでログが取れます。

sudo node ./scan.js | tee -a log.txt

定期的にlog.txtをsftpなどで取ってきて、下記のPythonスクリプトでグラフを描画しています。

graph.py
import numpy as np
import csv
import datetime
import matplotlib.pyplot as plt

d = {}
leaves = []
with open('log.txt', 'r') as f:
    reader = csv.reader(f)

    for row in reader:
        devname = row[0]
        dt = datetime.datetime.strptime(row[1] + ' ' + row[2], '%Y/%m/%d %H:%M:%S')
        temp = float(row[3][1:6])
        batt = float(row[3][7:12])
        rssi = float(row[4])

        if (d.get(devname) == None):
            leaves.append(devname)
            d.setdefault(devname, \
                { \
                    'datetime': [dt], \
                    'temp': [temp], \
                    'batt': [batt], \
                    'rssi': [rssi] \
                })
        else:
            d[devname]['datetime'].append(dt)
            d[devname]['temp'].append(temp)
            d[devname]['batt'].append(batt)
            d[devname]['rssi'].append(rssi)

leaves.sort()

plt.figure()

plt.subplot(1,2,1)
plt.title('Temperature')
plt.xlabel('Datetime')
plt.ylabel('Temp [℃]')
for leaf in leaves:
    plt.plot(d[leaf]['datetime'], d[leaf]['temp'], label=leaf)
plt.legend()
plt.grid()

plt.subplot(1,2,2)
plt.title('Battery')
plt.xlabel('Datetime')
plt.ylabel('Voltage [V]')
for leaf in leaves:
    plt.plot(d[leaf]['datetime'], d[leaf]['batt'], label=leaf)
plt.legend()
plt.grid()

plt.show()

とれたグラフがこちら
Figure_1.png

青色が私の座席です。窓際なので他の席よりも常に1℃くらい気温が低いですね。
グラフが急激に下がっている部分は窓を開けた時間に一致しています。生活リズムが狂った学生が早朝4時位に窓を開けているのがわかりますね。
緩やかに温度が上がってしばらく一定になる箇所は、窓を締め切ってエアコンをつけていた時間です。
夜中から早朝にかけては人もいないので、測定器等の冷却をするようのエアコンが稼働して一定温度に保たれています。

右側のグラフは電池電圧で、バッテリーはCR2032 225mAhのものを使っています。
最初しばらく下がって2.85V付近になってからほとんど変化していません。

うまく行けば数ヶ月ほどは持つ計算なのですが、もう数ヶ月ほど様子を見てみましょう。

実際に部屋の温度を測ってみた 2 (追記 2019/12/07)

実験開始から1ヶ月半ほど経って電池電圧のグラフを取ってみました。茶色は1.5V乾電池で動作させているので、1.5V付近にグラフがいます。

緑だけPanasonic製のCR2032を入れて、その他はLeafonyに付属していたお試しの電池を使ったせいか、わずか1週間足らずで動作を停止してるものもあります。
緑も残り3週間程度といったところでしょうか。現実は15分ごとの測定でせいぜい2ヶ月というところみたいです。

一方で、乾電池の方は非常に電圧が安定しています。容量も900mAh程度あるようなのでまだまだ持つと思います。

image.png

73
73
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
73
73

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?