toioをobniz経由で操作・衝突検知しkintoneやLINE Notify、LINE WORK APIと連携させ、MESH LEDを光らせる

この記事は以下のハッカソンに参加した際に作ったプロダクトの一部の作り方を示したものです。

https://mashupawards.connpass.com/event/291510/

# 作るもの
- toioをkintoneのレコード状況に応じてobniz経由で動かします
- toioの衝突を検知してkintoneにレコード登録し、LINE Notifyで通知し、LINE WORKS APIを使ってカレンダーに予定登録します

# kintoneアプリ作成
まずはレコードを登録するkintoneアプリを作成します。
ライセンスの取得や作り方などは各所のドキュメントにまとめられてます。
過去に記事を書いたのでこちらを参考にしていただいても問題ないかと思います。

https://qiita.com/itortie/items/1f095af42b51f95df6fc

![スクリーンショット 2023-10-30 10.21.55.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2628523/22132fc7-febf-b287-5a33-0a6c9c023006.png)

APIトークンも生成しておきましょう。
![スクリーンショット 2023-10-30 10.24.02.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2628523/32f19324-e50f-03cf-ceca-8816f6f9075c.png)

# LINE Notify作成
以下から簡単に作成できます。

https://notify-bot.line.me/my/

![スクリーンショット 2023-10-30 10.26.05.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2628523/c4ca6152-d069-3a36-8ca5-cd6d3698bdb0.png)

Fromはネタです。実際に鬼電がかかってくることはありません。念の為。
生成したトークンをメモしておきましょう。

# LINE WORKSテナント作成
LINE WORKSの公式サイトから無料テナントを作成します。

https://line.worksmobile.com/jp/

https://qiita.com/mmclsntr/items/3da41a9d03d6dba16290

APIを使うためにはアクセストークンが必要になります。
アクセストークンの取得は少し手順が必要なので以下を参考にしてください。
（自分も、ハッカソン中のテクニカルサポートをたくさん受けました）

https://qiita.com/mmclsntr/items/eee8d8f3546410fe6652

アクセストークンの有効期限は1日なので、永続して使うためには工夫が必要ですが、今回はハッカソンのデモ用に当日Postmanで取得したトークンを埋め込む形で利用します。

# obnizのスクリプト
obnizをnode.jsで動かしtoioと連携する手順やサンプルソースがまとまってます。以下を参考に環境を構築し、スクリプトを記載します。

https://docs.obniz.com/ja/guides/nodejs/how-to-run

https://docs.obniz.com/ja/sdk/parts/toio_corecube/README.md

ハッカソン用なので色々と雑ですが、以下のように実装しました。

```javascript:app.js
var Obniz = require("obniz");
var obniz = new Obniz("your obniz id");

obniz.onconnect = async () => {
  log("obniz connected");
  await obniz.ble.initWait();
  const Toio_CoreCube = Obniz.getPartsClass("toio_CoreCube");
  obniz.ble.scan.onfind = async (peripheral) => {
    log("name:", peripheral.localName);
    if (!Toio_CoreCube.isDevice(peripheral)) {
      return;
    }
    log("found");

    const toio = new Toio_CoreCube(peripheral);
    await toio.connectWait();

    const data = await toio.getMotionWait();
    log(data);

    // 衝突検知
    const service = peripheral.getService("10B20100-5B3B-4571-9508-CF3EFCD7BBAE");
    const char = service.getCharacteristic("10B20106-5B3B-4571-9508-CF3EFCD7BBAE");
    await char.registerNotifyWait((data) => {
      log(data);
      if ( data[2] ) {
        sendToKintone();
        sendToLINE();
      }
      toio.moveAroundWait(0, 0);
    });

    log("connected");

    // 一定間隔でkintoneのレコード状態を確認
    setInterval(async function(){
        client.record.getRecords({
            app: Config.kintone.appId,
            query: "order by $id desc limit 1"
          }).then((rsp) => {
            log(rsp.records[0].status.value);
            if ( rsp.records[0].status.value == "故障" ) {
              toio.moveAroundWait(0, 0);
            } else {
              toio.moveAroundWait(40, 60);
            }
          }).catch((err) => {
            log(err);
        });
        log("loop");
      }, 10000);
  };
  await obniz.ble.scan.startWait(
    { localNamePrefix: "toio" },
    { duration: null }
  );
}

const log = (...args) => {
  console.log(new Date(), ...args);
};
const wait = (ms) => {
  return new Promise((resolve) => {
    setTimeout(resolve, ms);
  });
};

log("program start");


const { KintoneRestAPIClient } = require("@kintone/rest-api-client");
const Config = {
  kintone: {
    apiToken: "your kintone app api token",
    baseUrl: "https://your domain.cybozu.com",
    appId: 1,
  }
};
const client = new KintoneRestAPIClient({
  baseUrl: Config.kintone.baseUrl,
  auth: {
    apiToken: Config.kintone.apiToken,
  },
});
const sendToKintone = async () => {
  await client.record.addRecord({
    app: Config.kintone.appId,
    record: {
      status: { value: "故障" }
    },
  });
};

const BASE_URL = 'https://notify-api.line.me';
const PATH =  '/api/notify';
const LINE_TOKEN = `your line notify token`;

const params = new URLSearchParams({
    message: 'こんばんわ。サーバーが死にました。復旧よろしくお願いします。',
});

const config = {
    method: 'POST',
    headers: {
        'Content-Type': 'application/x-www-form-urlencoded',
        'Authorization': `Bearer ${LINE_TOKEN}`
    },
    body: params.toString()
};


const sendToLINE = async () => {
    const res = await fetch(BASE_URL + PATH, config);
    console.log(res.status);
};
```

toioの衝突検知について、モーション検知のCharacteristic UUIDをobniz側で監視登録・変更通知の受け取りをするようにしてます。

https://toio.github.io/toio-spec/docs/ble_sensor/

https://docs.obniz.com/ja/reference/common/ble/central-subscribe

kintone状態確認については、webhookなどを活用することで定期的なレコード監視よりも効率的に操作が行えるかもしれません。

# MESHを光らせ、LINE WORKS API経由でカレンダー登録
obnizとMESH LEDを用意し、先ほど同様に環境構築をします。axiosが追加されています。

obniz経由でMESH操作のサンプルコードは以下のドキュメントにまとまっております。

https://docs.obniz.com/ja/sdk/parts/MESH_100LE/README.md

```javascript:app.js
const axios = require("axios");

var Obniz = require("obniz");
var obniz = new Obniz("your obniz id");

obniz.onconnect = async () => {
  log("obniz connected");
  await obniz.ble.initWait();
  const MESH_100LE = Obniz.getPartsClass("MESH_100LE");

  obniz.ble.scan.onfind = async (peripheral) => {
      log("name:", peripheral.localName);
      if (!MESH_100LE.isMESHblock(peripheral)) {
        return;
      }
      log("found");

      // Create an instance
      const ledBlock = new MESH_100LE(peripheral);

      // Connect to the Brightness block
      await ledBlock.connectWait();
      log("connected");

      // kintoneレコードに応じて色を変える
      setInterval(async function(){
        client.record.getRecords({
            app: Config.kintone.appId,
            query: "order by $id desc limit 1"
          }).then((rsp) => {
            log(rsp.records[0].status.value);
              const colors = {
                  red: 127,    // Set LED-Red in the range of 0 to 127.
                  green: 0,  // Set LED-Green in the range of 0 to 127.
                  blue: 0     // Set LED-Blue in the range of 0 to 127.
              };

            if ( rsp.records[0].status.value == "復旧" ) {
              colors.red = 0;
              colors.green = 127;
            }
            // 光らせっぱなしはできないので点灯時間 > 更新間隔にして擬似的に点灯を維持
            const totalTime = 15000;     // Set the total control time in the range of 0 to 65,535[ms].
            const cycleOnTime = 15000;   // Set the light on time in cycle in the range of 0 to 65,535[ms].
            const cycleOffTime = 0;   // Set the light off time in cycle in the range of 0 to 65,535[ms].
            const pattern = MESH_100LE.Pattern.FIREFLY; // Set the blinking pattern to blink or firefly.
            ledBlock.setLed(colors, totalTime, cycleOnTime, cycleOffTime, pattern);

          }).catch((err) => {
            log(err);
        });
        log("loop");
      }, 10000);
  };

  await obniz.ble.scan.startWait(
    { localNamePrefix: "MESH-100" },
    { duration: null }
  );
};

const log = (...args) => {
  console.log(new Date(), ...args);
};
const wait = (ms) => {
  return new Promise((resolve) => {
    setTimeout(resolve, ms);
  });
};

log("program start");

const { KintoneRestAPIClient } = require("@kintone/rest-api-client");
const Config = {
  kintone: {
    apiToken: "your kintone app api token",
    baseUrl: "https://your domain.cybozu.com",
    appId: 1,
  }
};
const client = new KintoneRestAPIClient({
  baseUrl: Config.kintone.baseUrl,
  auth: {
    apiToken: Config.kintone.apiToken,
  },
});


const accessToken = "your line works api access token"

const userId = "your user id"

const resistCalendar = async () => {
    const headers = {
        Authorization: `Bearer ${accessToken}`
    };
    // とりあえず日程決め打ち
    const content = {
      "eventComponents": [{
        "summary": "再発防止策会議",
        "start": {
          "dateTime": "2023-10-02T09:30:00",
          "timeZone": "Asia/Tokyo"
        },
        "end": {
          "dateTime": "2023-10-02T12:00:00",
          "timeZone": "Asia/Tokyo"
        }
      }]
    };
};

log(resistCalendar());
```

# まとめ
色々とサンプルコードをつなぎ合わせた状態ですが、部品部品で誰かの参考になれば幸いです。
- obnizでtoio/MESHを操作
- toioの衝突検知
- kintoneレコード監視
- LINE Notify連携
- LINE WORKS API連携