ESP32でLINE Simple Beaconを2000円弱で作ってみた

<img width="800px" alt="安く作れるLINE Beacon図" src="https://qiita-image-store.s3.amazonaws.com/0/164153/810fd2cc-45df-615d-ee3f-1eed189a51b1.jpeg">

簡単にセットアップできて使いやすいLINE Beacon。LINEアプリ(アカウント)と連携させることができ非常に協力なのですが、[shopで買うと](https://beacon.theshop.jp/items/6617930)は1個5400円とちょっと高いんですよね。

幸い[LINE Simple Beacon](https://engineering.linecorp.com/ja/blog/detail/117)という仕様が公開されているので、それを利用して安く、2000円弱でLINE Beaconを作ってみます。ハードウェアはあまり詳しくないのでもし間違い等ありましたらコメント等で指摘していただけると嬉しいです。

[追記]
[雪山BOT with LINE Beacon](https://hacklog.jp/works/50269)でおなじみの久田さんがライブラリを作って[micro:bitをLINE Simple Beacon 化するライブラリ](https://github.com/Pizayanz/arduino-LINESimpleBeacon)を作って下さいました！手順も丁寧に記載されていますので、お持ちの方は是非！

# 必要なもの

全部秋月で手にはいります。

<img width="800px" alt="安く作れるLINE Beacon図" src="https://qiita-image-store.s3.amazonaws.com/0/164153/08e67639-6ba5-7994-8b89-6f6039e5ef72.jpeg">

* ESP32-DevKitC http://akizukidenshi.com/catalog/g/gM-11819/
* ブレッドボード6穴版 EIC-3901 http://akizukidenshi.com/catalog/g/gP-12366/
* USBケーブル Aオス-マイクロBオス http://akizukidenshi.com/catalog/g/gC-09314/
* 電池ボックス 単3×2本 リード線・間仕切 http://akizukidenshi.com/catalog/g/gP-10196/

しめて1930円。USBケーブルはもう持ってる方も多いでしょうしブレッドボードも最悪無くてもいいので最低限だと1600円位ですかね。

# ESP32の開発環境

では最初にMacからESP32に書き込めるよう準備をしましょう。

## Arduino IDE

https://www.arduino.cc/en/Main/Software#

からArduino IDEをダウンロード、インストールします。

```bash
mkdir -p ~/Documents/Arduino/hardware/espressif && \
cd ~/Documents/Arduino/hardware/espressif && \
git clone https://github.com/espressif/arduino-esp32.git esp32 && \
cd esp32/tools/ && \
python get.py
```

Pythonのバージョンによってはエラーになりますので、2.7で実行する等して対応して下さい。

```bash
python2.7 get.py
```

## BLEライブラリのアップデート
これで動くはずだったのですが、私の環境だとArduino/hardware/espressif/esp32/libraries/BLEが無効なライブラリだという警告が出るので、最新版にアップデートします。

[こちら](https://qiita.com/tomorrow56/items/afa06e206eec9fafcc7a)に書いてある通りで大丈夫でした。

## ドライバ
[CP210x USB - UART ブリッジ VCP ドライバ](https://jp.silabs.com/products/development-tools/software/usb-to-uart-bridge-vcp-drivers#mac)

からMac用のドライバをインストールします。

# ビーコンIDの払い出し

[LINE@ Managerのビーコン登録画面](https://admin-official.line.me/beacon/register/)へアクセスし、「ハードウェアIDの払い出し」をクリックします。ビーコンを紐付けたいアカウントを選択して発行します。IDは後で必要なので控えておいて下さい。

[LINE Developers](https://developers.line.me/ja/)にてLINE Bot用の設定を済ませ、友だちになっておいて下さい。とりあえずWebhookにはwebhookを出力するようなコードだけ。

```PHP
<?php

require_once __DIR__ . '/vendor/autoload.php';

$httpClient = new \LINE\LINEBot\HTTPClient\CurlHTTPClient(getenv('CHANNEL_ACCESS_TOKEN'));
$bot = new \LINE\LINEBot($httpClient, ['channelSecret' => getenv('CHANNEL_SECRET')]);
$signature = $_SERVER["HTTP_" . \LINE\LINEBot\Constant\HTTPHeader::LINE_SIGNATURE];

$inputString = file_get_contents("php://input");
error_log($inputString);

$events = $bot->parseEventRequest(file_get_contents('php://input'), $signature);
foreach ($events as $event) {
  if ($event instanceof \LINE\LINEBot\Event\BeaconDetectionEvent) {
    if ($event instanceof \LINE\LINEBot\Event\MessageEvent\TextMessage) {
      $bot->replyMessage($event->getReplyToken(),
        (new \LINE\LINEBot\MessageBuilder\MultiMessageBuilder())
          ->add(new \LINE\LINEBot\MessageBuilder\StickerMessageBuilder(1, 17))
          ->add(new \LINE\LINEBot\MessageBuilder\TextMessageBuilder('You are near beacon'))
      );
    }
    continue;
  }
}

?>

```

# 開発

MacとESP32をケーブルで接続、Arduino IDEを起動し、ツール > ボードから「ESP32 Dev Module」を、シリアルポートから「/dev/cu.SLAB_USBtoUART」を選択します。

Arduino IDEに以下のようなコードをペーストし、マイコンボードに書き込みます。

```C
void setup() {
  // put your setup code here, to run once:
  Serial.begin(115200);
  while (!Serial);

  Serial.printf("Revision: %d\r\n" , ESP.getChipRevision());
}

void loop() {
  // put your main code here, to run repeatedly:
  Serial.write("loop\n");
  delay(1000);
}
```

ターミナルで以下のコードを実行します。

```bash
screen /dev/tty.SLAB_USBtoUART 115200
```

以下のように、「loop」という文字列が1秒ごとに出力されます。

```bash
loop
loop
loop
```

書き込めることが確認できました。終了はctrl+a押してからk、yです。

では、実際にLINE Simple Beaconとして動くコードを書き込んでみます。といっても、[公式](https://engineering.linecorp.com/ja/blog/detail/149)から必要なコードだけを抜き出しただけです。

```C
#include "bt.h"
#include "esp_gap_ble_api.h"

static const uint8_t HWID[5] = {0x12, 0x34, 0x56, 0x78, 0x90};// LINEから払い出されたhw idを2桁区切りにして0x__の部分に入れる
static const uint16_t UUID_FOR_LINECORP = 0xFE6F;
static const uint8_t  MAX_SIMPLEBEACON_DEVICEMESSAGE_SIZE = 13;
static uint8_t deviceMessageSize = 1;
static uint8_t deviceMessage[MAX_SIMPLEBEACON_DEVICEMESSAGE_SIZE];
static const uint8_t  MAX_BLE_ADVERTISING_DATA_SIZE = 31;
static const uint16_t HCI_LE_Set_Advertising_Data   = (0x08 << 10) | 0x0008;
static const uint16_t HCI_LE_Set_Advertising_Enable = (0x08 << 10) | 0x000A;

static void _dump(const char * title, uint8_t *data, size_t dataSize) {
  Serial.printf("%s [%d]:", title, dataSize);
  for (size_t i = 0; i < dataSize; i++) {
    Serial.printf(" %02x", data[i]);
  }
  Serial.println();
}

static void putUint8(uint8_t** bufferPtr, uint8_t data) {
  *(*bufferPtr)++ = data;
}

static void putUint16LE(uint8_t** bufferPtr, uint16_t data) {
  *(*bufferPtr)++ = lowByte(data);
  *(*bufferPtr)++ = highByte(data);
}

static void putArray(uint8_t** bufferPtr, const void* data, size_t dataSize) {
  memcpy(*bufferPtr, data, dataSize);
  (*bufferPtr) += dataSize;
}

static void notifyHostSendAvailableHandler() {
  //  _dump("notify_host_send_available", NULL, 0);
}
static int notifyHostRecvHandler(uint8_t *data, uint16_t len) {
  //  _dump("notify_host_recv", data, len);
  return 0;
}

static esp_vhci_host_callback_t vhciHostCallback = {
  notifyHostSendAvailableHandler,
  notifyHostRecvHandler
};

void setup() {
  // put your setup code here, to run once:

  Serial.begin(115200, SERIAL_8N1);

  // bluetoothを初期化
  btStart();
  esp_vhci_host_register_callback(&vhciHostCallback);

  updateSimpleBeaconDeviceMessage();
  updateAdvertisingData();
  
  uint8_t enable = 1;
  executeBluetoothHCICommand(HCI_LE_Set_Advertising_Enable, &enable, 1);
}

static void updateSimpleBeaconDeviceMessage() {
  memset(deviceMessage, 0x00, MAX_SIMPLEBEACON_DEVICEMESSAGE_SIZE);
  uint8_t* deviceMessagePtr = deviceMessage;

  const uint8_t messageStringSize = 8;
  char messageString[messageStringSize];
  snprintf(messageString, messageStringSize, "beac");
  putArray(&deviceMessagePtr, messageString, messageStringSize);

  deviceMessageSize = deviceMessagePtr - deviceMessage;
}

static void executeBluetoothHCICommand(uint16_t opCode, const uint8_t * hciData, uint8_t hciDataSize) {
  uint8_t buf[4 + MAX_BLE_ADVERTISING_DATA_SIZE];
  uint8_t* bufPtr = buf;

  putUint8(&bufPtr, 1);// 1 = H4_TYPE_COMMAND
  putUint16LE(&bufPtr, opCode);
  putUint8(&bufPtr, hciDataSize);
  putArray(&bufPtr, hciData, hciDataSize);

  uint8_t bufSize = bufPtr - buf;

  while (!esp_vhci_host_check_send_available());
  esp_vhci_host_send_packet(buf, bufSize);
}

static void updateAdvertisingData() {
  uint8_t data[MAX_BLE_ADVERTISING_DATA_SIZE];
  uint8_t* dataPtr = data;

  {
    // flag
    putUint8(&dataPtr, 1 + 1);
    putUint8(&dataPtr, ESP_BLE_AD_TYPE_FLAG);
    putUint8(&dataPtr, ESP_BLE_ADV_FLAG_GEN_DISC | ESP_BLE_ADV_FLAG_BREDR_NOT_SPT);
  }
  {
    // complete list of service uuid
    putUint8(&dataPtr, 1 + 2);
    putUint8(&dataPtr, ESP_BLE_AD_TYPE_16SRV_CMPL);
    putUint16LE(&dataPtr, UUID_FOR_LINECORP);
  }
  {
    // simple beacon
    putUint8(&dataPtr, 1 + 9 + deviceMessageSize);
    putUint8(&dataPtr, ESP_BLE_AD_TYPE_SERVICE_DATA);
    putUint16LE(&dataPtr, UUID_FOR_LINECORP);
    putUint8(&dataPtr, 0x02);    // frame type
    putArray(&dataPtr, HWID, 5);
    putUint8(&dataPtr, 0x7F);    // measured txpower
    putArray(&dataPtr, deviceMessage, deviceMessageSize);
  }
  uint8_t dataSize = dataPtr - data;
  _dump("simple beacon", data, dataSize);

  uint8_t hciDataSize = 1 + MAX_BLE_ADVERTISING_DATA_SIZE;
  uint8_t hciData[hciDataSize];

  hciData[0] = dataSize;
  memcpy(hciData + 1, data, dataSize);

  executeBluetoothHCICommand(HCI_LE_Set_Advertising_Data, hciData, hciDataSize);
}

void loop() {

}
```

これで書き込むとビーコンがBLEのadvertiseを飛ばし続けるようになり、LINEがインストールされたスマホが新たに接近した(enter)と判断されたタイミングで指定したエンドポイントにイベントが送信されるようになります。

```javascript
{  
   "events":[  
      {  
         "type":"beacon",
         "replyToken":"XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX",
         "source":{  
            "userId":"XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX",
            "type":"user"
         },
         "timestamp":1513930225758,
         "beacon":{  
            "hwid":"xxxxxxxxxx",
            "dm":"62656163000000d0",
            "type":"enter"
         }
      }
   ]
}
```

# 電池駆動

電池ボックスに電池を入れ、赤を3V3、黒をGNDにつなげばESP32の電源が入ります。抵抗とかも無くていいっぽい？起動時には書き込んだスケッチが読み込まれ、サービスが起動します。確認してみましたが正常に動作しているようです。

<img width="800px" alt="安く作れるLINE Beacon図" src="https://qiita-image-store.s3.amazonaws.com/0/164153/810fd2cc-45df-615d-ee3f-1eed189a51b1.jpeg">

# まとめ

便利なのは知っていても高くて手が出ないという声をよく聞くLINE Beacon。この通りにやれば少しは安く作れます。キリンやユニクロ等大手からはもう出ていますが、小規模サービスでもビーコンとLINEを連携させた革新的なLINE Botの登場が待ち遠しいですね！

もうすぐ年末休みですので、是非LINE Botを作って見て下さい。Bot関連のノウハウ集は[Qiitaで人気のLINE関連投稿紹介（保存版）](https://engineering.linecorp.com/ja/blog/detail/234)によくまとまっています。

# 参考
https://engineering.linecorp.com/ja/blog/detail/149
https://qiita.com/zakkied/items/9a602878b9d178fa27b1
https://qiita.com/suigin/items/dcf704c05e98a00acae8
https://qiita.com/tomorrow56/items/afa06e206eec9fafcc7a

感謝です！