M5StickCでLINE Thingsの自動通信機能を試してみた

2019年4月にLINE Thingsの自動通信機能がリリースされました。
https://engineering.linecorp.com/ja/blog/line-things-automatic-communication/

これを使えば、Wi-Fiが届きにくい環境でもIoTデバイスを利用できるという利点があります。

![https://engineering.linecorp.com/wp-content/uploads/2019/05/about-auto-communication-01.jpg](https://engineering.linecorp.com/wp-content/uploads/2019/05/about-auto-communication-01.jpg)
※LINE Engineerブログより

# M5StickCについて
2019年5月4日〜5日に開催されたMaker Faire Kyotoにて先行発売されていたM5StickCを運良くゲットできました。
これまでは技適が通っていなかったため日本での発売ができなかったのですが、この度認証されて日本で販売されました。

<blockquote class="twitter-tweet"><p lang="ja" dir="ltr">M5Stickゲットだぜ！<a href="https://twitter.com/hashtag/MFKyoto2019?src=hash&amp;ref_src=twsrc%5Etfw">#MFKyoto2019</a> <a href="https://t.co/a7waw4jqds">pic.twitter.com/a7waw4jqds</a></p>&mdash; がおまる@スマートスピーカーアプリ開発入門発売中！ (@gaomar) <a href="https://twitter.com/gaomar/status/1124570670971514882?ref_src=twsrc%5Etfw">May 4, 2019</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

M5StickCはこちらを確認してください
https://docs.m5stack.com/#/ja/core/m5stickc

![m5stickc_01.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154355/8fee0430-a748-b81d-87c7-b58b41fe6f9d.png)

これだけ充実した機能が揃って会場価格で<font color='red'>**¥1,500**</font>でした。とってもお安いですね！

# サービス例
ボタンが押されたらAmazon Connectから電話がかかってきたりと、色々な展開をすることができますよ。

<blockquote class="twitter-tweet"><p lang="ja" dir="ltr">LINE Thingsの自動通信機能とAmazon Connect連携してみた。ボタンを押すと携帯に電話がかかってきます。<a href="https://twitter.com/hashtag/LINEThings?src=hash&amp;ref_src=twsrc%5Etfw">#LINEThings</a> <a href="https://twitter.com/hashtag/M5StickC?src=hash&amp;ref_src=twsrc%5Etfw">#M5StickC</a> <a href="https://twitter.com/hashtag/AmazonConnect?src=hash&amp;ref_src=twsrc%5Etfw">#AmazonConnect</a> <a href="https://t.co/8hW5f16EEr">pic.twitter.com/8hW5f16EEr</a></p>&mdash; がおまる@スマートスピーカーアプリ開発入門発売中！ (@gaomar) <a href="https://twitter.com/gaomar/status/1126506760884314112?ref_src=twsrc%5Etfw">May 9, 2019</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

# 今回やるデモ
M5StickCでBボタンが押されたら数値をカウントアップをして、Aボタンが押されたらLINE Thingsの自動通信機能を使ってWebhook URLへデータを送信するところを行います。
Webhook先URLはお好きなものを指定すれば良いですが、今回は`API Gateway` と `AWS Lambda`で実装しました。

# 1. LIFF設定
LINE DeveloperページでLIFFの設定を行います。
BLE futureは`ON`にしておきます。エンドポイントURLは適当で構いません。

<img width="815" alt="s100.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154355/60940d3d-4033-571b-8591-e112b503d9f7.png">

発行されるLIFFのID（XXXXXXXXXX-XXXXXXXX）をメモしておきます。
<img width="493" alt="s101.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154355/f492e751-ef82-7be7-2ed9-fed06db2b96d.png">

# 2. LINE Thingsの設定を行う

## 2-1. トライアルプロダクト情報を作成する
ターミナルを開いて、下記コマンドを実行します。先程発行された`LIFFのID`を指定します。
LINE Botの`アクセストークン`をコピペします。

<img width="1172" alt="s102.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154355/8a5bec95-b4bb-22ad-3826-a4163cad97fe.png">

詳細はこちら
https://developers.line.biz/ja/reference/line-things/#post-trial-products

```shell:ターミナルで実行する
curl -X POST https://api.line.me/things/v1/trial/products \
-H 'Authorization: Bearer {LINE Botのアクセストークン}' \
-H 'Content-Type:application/json' \
-d '{
  "name": "{ お好きな名前 }",
  "liffId": "{ LIFFのID }"
}'
```

コマンドを実行するとJSONでレスポンスが返ってきます。
`プロダクトID`と`サービスUUID`は後ほど使用するのでメモしておきます。

```shell:レスポンス
{
    "id": { プロダクトID },
    "name": "{ お好きな名前 }",
    "type": "BLE",
    "channelId": xxxxxxxxxx,
    "actionUri": "line://app/xxxxxxxxxx-xxxxxxxx",
    "serviceUuid": "{ サービスUUID }",
    "psdiServiceUuid": "e625601e-9e55-4597-a598-76018a0d293d",
    "psdiCharacteristicUuid": "26e2b12b-85f0-4f3f-9fdd-91d114270e6e"
}
```

## 2-2. シナリオセットの登録
シナリオと呼ばれる自動通信機能を登録します。

詳細はこちら
https://developers.line.biz/ja/reference/line-things/#create-scenario-set

下記コマンドを実行します。
`プロダクトID`、`LINE Botのアクセストークン`、`サービスUUID`は2-1で取得した値を設定してください。

`characteristicUuid`はM5StickCからデータを送受信する際に必要なUUIDです。
トライアル版なので、この値はこのまま変更せずに設定します。

```shell:ターミナル
curl -v -X PUT https://api.line.me/things/v1/products/{ プロダクトID }/scenario-set \
-H "Authorization: Bearer { LINE Botのアクセストークン }" \
-H 'Content-Type:application/json' \
-d '
{
  "autoClose": false,
  "suppressionInterval": 0,
  "scenarios": [
    {
      "trigger": {
        "type": "BLE_NOTIFICATION",
        "serviceUuid": "{ サービスUUID }",
        "characteristicUuid": "62FBD229-6EDD-4D1A-B554-5C4E1BB29169"
      },
      "actions": [
      ]
    }
  ]
}'
```

# 3. Lambdaプログラム
サーバー側のプログラムも記述しておきます。今回は簡単な例で、コンソールにログが表示されるだけのシンプルな例です。
Lambdaにプログラムを書き込みます。

LINE Thingsから送られてくる値(`bleNotificationPayload`)はBase64のバイナリデータが飛んできますので、
読み取れるようにデコードしています。

```javascript:index.js
exports.handler = async (event) => {
    const body = JSON.parse(event.body);
    const thingsData = body.events[0].things.result;

    const response = {
        statusCode: 200,
        body: JSON.stringify('Hello from M5StickC!'),
    };

    // bleNotificationPayloadにデータが来る
    if (thingsData.bleNotificationPayload) {
        const blePayload = thingsData.bleNotificationPayload;
        var buffer1 = new Buffer(blePayload, 'base64');
        var stickData = buffer1.toString('ascii');  //Base64をデコード
        console.log("M5StickC-Payload=" + stickData);
    }
    return response;
};
```

# 4. M5StickCにプログラムを書き込む
では、M5StickCにプログラムを書き込んでいきます。Arduinoi IDEを開いて下記プログラムを書き込んでみます。
11行目の`サービスUUID`は2-1で取得した`serviceUuid`の値を指定します。

```c:ArduinoIDE
#include <BLEDevice.h>
#include <BLEServer.h>
#include <BLEUtils.h>
#include <BLE2902.h>
#include <M5StickC.h>

// Device Name: Maximum 30 bytes
#define DEVICE_NAME "LINE Things Trial M5Stick"

// あなたのサービスUUIDを貼り付けてください
#define USER_SERVICE_UUID "{ あなたのサービスUUID }"
// Notify UUID: トライアル版は値が固定される
#define NOTIFY_CHARACTERISTIC_UUID "62FBD229-6EDD-4D1A-B554-5C4E1BB29169"
// PSDI Service UUID: トライアル版は値が固定される
#define PSDI_SERVICE_UUID "E625601E-9E55-4597-A598-76018A0D293D"
// PSDI CHARACTERISTIC UUID: トライアル版は値が固定される
#define PSDI_CHARACTERISTIC_UUID "26E2B12B-85F0-4F3F-9FDD-91D114270E6E"

BLEServer* thingsServer;
BLESecurity* thingsSecurity;
BLEService* userService;
BLEService* psdiService;
BLECharacteristic* psdiCharacteristic;
BLECharacteristic* notifyCharacteristic;

bool deviceConnected = false;
bool oldDeviceConnected = false;

class serverCallbacks: public BLEServerCallbacks {
  void onConnect(BLEServer* pServer) {
   deviceConnected = true;
  };

  void onDisconnect(BLEServer* pServer) {
    deviceConnected = false;
  }
};

void setup() {
  Serial.begin(115200);

  BLEDevice::init("");
  BLEDevice::setEncryptionLevel(ESP_BLE_SEC_ENCRYPT_NO_MITM);

  // Security Settings
  BLESecurity *thingsSecurity = new BLESecurity();
  thingsSecurity->setAuthenticationMode(ESP_LE_AUTH_BOND);
  thingsSecurity->setCapability(ESP_IO_CAP_NONE);
  thingsSecurity->setInitEncryptionKey(ESP_BLE_ENC_KEY_MASK | ESP_BLE_ID_KEY_MASK);

  setupServices();
  startAdvertising();

  // M5Stack LCD Setup
  M5.begin(true, false, false);
  M5.Lcd.setRotation(3);

  // ボタン初期化
  pinMode(M5_BUTTON_HOME, INPUT);  
  pinMode(M5_BUTTON_RST, INPUT);

  M5.Lcd.fillScreen(TFT_BLACK);
  M5.Lcd.setTextColor(YELLOW);
  M5.Lcd.setCursor(0, 0);   
  M5.Lcd.print("Ready to Connect");
  Serial.println("Ready to Connect");
}

int btnValue = 0;

void loop() {

  if(digitalRead(M5_BUTTON_RST) == LOW) {
    // カウントアップ
    btnValue++;
    M5.Lcd.fillScreen(TFT_BLACK);
    M5.Lcd.setTextSize(2);
    M5.Lcd.setCursor(0, 0);
    M5.Lcd.print("Count");
    M5.Lcd.setTextColor(GREEN);
    M5.Lcd.setCursor(30, 40);
    M5.Lcd.printf("%d", btnValue);

    // ボタンが離されるまでループ
    while(digitalRead(M5_BUTTON_RST) == LOW);
  }

  if(digitalRead(M5_BUTTON_HOME) == LOW) {
    // LINE Botに紐づくWebhookにデータ送信
    const char *newValue=((String)btnValue).c_str();
    notifyCharacteristic->setValue(newValue);
    notifyCharacteristic->notify();

    M5.Lcd.fillScreen(TFT_BLACK);
    M5.Lcd.setTextSize(2);
    M5.Lcd.setCursor(0, 0);
    M5.Lcd.print("Send!!");

    // ボタンが離されるまでループ
    while(digitalRead(M5_BUTTON_HOME) == LOW);

    // カウント初期化
    btnValue = 0;
    delay(3000);
    M5.Lcd.fillScreen(TFT_BLACK);
    M5.Lcd.setCursor(0, 0);
    M5.Lcd.print("Count");
    M5.Lcd.setTextColor(GREEN);
    M5.Lcd.setCursor(30, 40);
    M5.Lcd.printf("%d", btnValue);    
  }

  // Disconnection
  if (!deviceConnected && oldDeviceConnected) {
    delay(500); // Wait for BLE Stack to be ready
    thingsServer->startAdvertising(); // Restart advertising
    oldDeviceConnected = deviceConnected;
    M5.Lcd.fillScreen(TFT_BLACK);
    M5.Lcd.setTextColor(YELLOW);
    M5.Lcd.setCursor(0, 0);     
    M5.Lcd.print("Ready to Connect");
  }
  // Connection
  if (deviceConnected && !oldDeviceConnected) {
    oldDeviceConnected = deviceConnected;
    M5.Lcd.fillScreen(TFT_BLACK);
    M5.Lcd.setTextColor(GREEN);
    M5.Lcd.setCursor(0, 0);       
    M5.Lcd.print("Connected");
  }
}

// サービス初期化
void setupServices(void) {
  // Create BLE Server
  thingsServer = BLEDevice::createServer();
  thingsServer->setCallbacks(new serverCallbacks());

  // Setup User Service
  userService = thingsServer->createService(USER_SERVICE_UUID);

  // Notifyセットアップ
  notifyCharacteristic = userService->createCharacteristic(NOTIFY_CHARACTERISTIC_UUID, BLECharacteristic::PROPERTY_NOTIFY);
  notifyCharacteristic->setAccessPermissions(ESP_GATT_PERM_READ_ENCRYPTED | ESP_GATT_PERM_WRITE_ENCRYPTED);
  BLE2902* ble9202 = new BLE2902();
  ble9202->setNotifications(true);
  ble9202->setAccessPermissions(ESP_GATT_PERM_READ_ENCRYPTED | ESP_GATT_PERM_WRITE_ENCRYPTED);
  notifyCharacteristic->addDescriptor(ble9202);

  // Setup PSDI Service
  psdiService = thingsServer->createService(PSDI_SERVICE_UUID);
  psdiCharacteristic = psdiService->createCharacteristic(PSDI_CHARACTERISTIC_UUID, BLECharacteristic::PROPERTY_READ);
  psdiCharacteristic->setAccessPermissions(ESP_GATT_PERM_READ_ENCRYPTED | ESP_GATT_PERM_WRITE_ENCRYPTED);

  // Set PSDI (Product Specific Device ID) value
  uint64_t macAddress = ESP.getEfuseMac();
  psdiCharacteristic->setValue((uint8_t*) &macAddress, sizeof(macAddress));

  // Start BLE Services
  userService->start();
  psdiService->start();
}

void startAdvertising(void) {
  // Start Advertising
  BLEAdvertisementData scanResponseData = BLEAdvertisementData();
  scanResponseData.setFlags(0x06); // GENERAL_DISC_MODE 0x02 | BR_EDR_NOT_SUPPORTED 0x04
  scanResponseData.setName(DEVICE_NAME);

  thingsServer->getAdvertising()->addServiceUUID(userService->getUUID());
  thingsServer->getAdvertising()->setScanResponseData(scanResponseData);
  thingsServer->getAdvertising()->start();
}
```

# 5. 動作確認
ボードに書き込んだらLINEアプリを開きます。LINE Thingsに`連携可能なデバイス`が表示されます。
タッチして、マイデバイスに設定しておきます。

![s103.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154355/6329f4be-98c0-cf7d-fa93-a1ac38727b2b.png)

Android端末だとうまく接続されると、`自動通信を実行中です。`と表示されます。

![s104.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154355/e62bc17e-8b26-468a-4d79-d7cacdedefce.png)

Bボタンを押して数字をカウントアップします。

![s105.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154355/ef7dc6cd-4bb3-6e48-4c0d-f5f8eec064f0.jpeg)

Aボタンを押すと数字がAWS Lambdaに送信されます。
CloudWatchでログを確認すると、ちゃんとデータが送られてきているのがわかりますね。

<img width="916" alt="s106.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154355/8a8659cf-fd1f-b1bc-981e-7d7553bd0c93.png">

# まとめ
簡単なデモを試してみましたが、もっと幅広い展開をすることだってできます。
Wi-Fiが届きにくい環境でもBluetoothの電波が届く範囲ならばIoT機器を動作させることができます。

センサーの異常値の時にLINE Botへ通知を送ったり、Amazon Connectと連携して電話をかけてもらったり色々なことができます。

LINE Thingsを使って良きIoTライフを！

システム化のご検討やご相談は弊社までお問い合わせください。
https://i-enter.co.jp/contact/