会議室をIoTした

[Opt Technologies Advent Calendar 2017](https://qiita.com/advent-calendar/2017/opt-technologies)の12日目です。
# はじめに
オプトテクノロジーズの伊藤です。
QiitaはもっぱらROM専でしたが、Advent Calendarで投稿の機会をいただき、自社の会議室の最適利用の為にIoTした事例をご紹介します。

# 課題ときっかけ
「とにかく会議室の空きがない」「ミーティングしたいのに場所がない」なんて声を社内のあちらこちらで聞くようになりました。
この課題に向き合うと決めて、オフィスサービスEXPOなどの展示会を見に行ったり、IoTをリードする企業様と会って話を聞きましたが、何れもイニシャル/ランニングコストが高額だったり、大規模な工事や機器の設置が必要なソリューションばかりで、とても気軽に導入できるものではありませんでした。
一方で、個人的な趣味で人感（赤外線）センサーを使った行動モニタリングをやっていたこともあり、自作のデバイスを活用したソリューション開発を推進することになりました。

# 課題の考察
全社的に会議自体の頻度が多い＆時間が長いのもありますが、実は会議室をおさえたものの開催されなかったケースが一定数あるような気がしました。
総務とも共同し、一週間程度の会議室の利用状況を計測したところ、71%程度の稼働率。つまり、約3割は使われていないことが判明しました。

# ソリューション要件
* 会議室の利用状況をリアルタイムで可視化したい
* 会議室が未利用の場合はスケジュールを自動削除したい
* 会議室が未利用の場合はログを残したい（空予約者のあぶり出し）
* 会議終了の5分前にアラームを鳴らしたい

# 実現方式
## どうすれば会議室の利用状況が把握できるか？
### 【試行１】 人感（赤外線）センサーによる計測数値をモニタリング
<img width="284" alt="スクリーンショット 2017-12-12 14.28.28.png" src="https://qiita-image-store.s3.amazonaws.com/0/114936/6fe029b3-e0ed-9743-3619-3147ec161a24.png">
箱の右側にある突起物が人感（赤外線）センサーで、その計測数値をWi-Fiモジュールを使ってBaaSに投げつけるもの。
ちなみにBaaSはIoTのデータ可視化でお馴染みの[Ambient](https://ambidata.io/)です。

この方式で人が居る/居ないが判定できればよかったのですが <b>深夜や早朝に謎の計測があがる</b> おーこわ…
<img width="284" alt="スクリーンショット 2017-12-12 14.36.19.png" src="https://qiita-image-store.s3.amazonaws.com/0/114936/0b3d1511-fc79-0479-97b2-998cd4cf0173.png">

そもそも、この人感センサーは、焦電型赤外線センサーと言うやつでして、デバイスメーカーさんの説明によると
> センサー自身からLEDなどの光を発光するのではなく、周囲と温度差のある人（物）が動く際におこる赤外線の変化量を検出するセンサーです。 温度差を検出するため、体温を持つ人体の検出に適します。

とのことなので、つまり <b>温度差に弱い</b> のです。
太陽光などの外的要因で温度差が生じ、誤検知も十分にありえます。
これはいけません。そこで次に試したのは。

### 【試行２】 音量センサーによる計測数値をモニタリング
<img width="284" alt="スクリーンショット 2017-12-12 14.39.12.png" src="https://qiita-image-store.s3.amazonaws.com/0/114936/71c8d0b0-d27d-74f2-7376-2b7eee8e3691.png">

箱の角に穴が空いてますが、その中に音量センサーを追加し、上記と同様に計測数値をBaaSに投げつけます。

結果。よくわからない。

生活音や屋外の音も拾ってしまい、居る/居ないの正確な判断ができませんでした。
（そもそも会議中に誰も喋らない時間があったらマズいのでは？とかも）
<img width="284" alt="スクリーンショット 2017-12-12 14.40.51.png" src="https://qiita-image-store.s3.amazonaws.com/0/114936/5e0df6ae-01f4-c24c-6b00-b1859587fd35.png">

その後も、計測数値を1分間サマった平均値をとって比較したり、サーモグラフィーでも買ってみようかとか迷走しましたが、結局は↓に落ち着きました。

### 【試行３】 ボタン押下による計測数値をモニタリング
<img width="284" alt="スクリーンショット 2017-12-12 16.27.49.png" src="https://qiita-image-store.s3.amazonaws.com/0/114936/89211d66-2785-9705-22c9-8ac688a4254c.png">
これまでの試行錯誤と比べると極めて原始的ですが、人間がボタンを押すので誤検知はありませんね（押し忘れなければね）。
測り方は至ってシンプル。
入室時に赤ボタンを押して「利用中」の信号を飛ばし、退出時に青ボタンを押して「未利用」の信号を飛ばします。
ついでにステータスを同色のLEDで光らします。

余談ですが、先述の展示会で拝見したソリューションのほとんどはこの方式です。
もちろん、計測デバイスはこんなお粗末なものではなく、タブレットを採用していてリッチで現代的ですが、計測する為の原理原則は同じです。
とにもかくにも、これで計測はクリアです。

## 会議室の利用状況をリアルタイムで可視化する
方法は色々とあるのですが、バックエンドでかつリアルタイム通知ができるサービスでパッと思いついたのが <b>Firebase</b> でした（使ったことがなかったので学習も含め）。
特にアプリ開発のシーンでは度々利用されていますね。リアルタイムデータベースとかpush通知とか。他にも機能はたくさんあるはず。
そして、先の章では触れませんでしたが、計測結果をWi-FiでBaaSに飛ばしているモジュールは[ESP-WROOM-02](https://www.switch-science.com/catalog/2346/)というもので、このモジュールとArduinoを組み合わせ、Arduinoスケッチで書いたC++をフラッシュメモリに書き込んで動かしています。
つまり、ArduinoでFirebaseのAPIを実行する必要があります。

これを一から書くのは骨が折れるなぁ…と思っていたら、ありましたよライブラリが。
[Firebase-Arduino](https://github.com/firebase/firebase-arduino)
OSS最高ですね。Contributorの方々にはホント頭が下がります。

使い方は、masterブランチをダウンロードし、Aruduino IDEでライブラリとして登録。
サンプルのスケッチも参考にして欲しいのですが、以下のようなコードでいけるはずです。

```firebase-arduino.ino
#include <ESP8266WiFi.h>
#include <FirebaseArduino.h>

#define FIREBASE_HOST "xxx.firebaseio.com"
#define FIREBASE_AUTH "Firebaseのデータベースシークレット"
#define WIFI_SSID "Wi-FiのSSID"
#define WIFI_PASSWORD "Wi-Fiのパスワード"

// ボタンの接続ピン番号
const int buttonBlue = 12;
const int buttonRed = 13;
// LEDの接続ピン番号
const int ledBlue = 14;
const int ledRed = 15;

void setup() {
  // Aruduinoに結線しているIN/OUTのPINを定義
  pinMode(buttonBlue, INPUT);
  pinMode(buttonRed, INPUT);
  pinMode(ledBlue, OUTPUT);
  pinMode(ledRed, OUTPUT);  

  Serial.begin(115200);

  // Wi-Fi接続
  WiFi.begin(WIFI_SSID, WIFI_PASSWORD);
  Serial.print("connecting");
  while (WiFi.status() != WL_CONNECTED) {
    Serial.print(".");
    delay(500);
  }
  Serial.println();
  Serial.print("connected: ");
  Serial.println(WiFi.localIP());
  
  // Firebaseに接続
  Firebase.begin(FIREBASE_HOST, FIREBASE_AUTH);
}

void loop() {
  // スイッチの状態を取得
  int buttonBlueState = digitalRead(buttonBlue);
  int buttonRedState = digitalRead(buttonRed);

  // 青ボタンが押された場合
  if (buttonBlueState) {
    // 青のLEDを点灯し、赤のLEDを消灯
    digitalWrite(ledBlue, HIGH);
    digitalWrite(ledRed, LOW);
    // Firebaseにデータをセット
    Firebase.setString("Firebaseデータキー", "0");
  }
  // 赤ボタンが押された場合
  if (buttonRedState) {
    // 赤のLEDを点灯し、青のLEDを消灯
    digitalWrite(ledBlue, LOW);
    digitalWrite(ledRed, HIGH);
    // Firebaseにデータをセット
    Firebase.setString("Firebaseデータキー", "1");
  }

  // Firebaseからデータを取得
  String strStatus = Firebase.getString("Firebaseデータキー");
  if (strStatus == "0") {
    // 青のLEDを点灯し、赤のLEDを消灯
    digitalWrite(ledBlue, HIGH);
    digitalWrite(ledRed, LOW);
  }
  if (strStatus == "1") {
    // 赤のLEDを点灯し、青のLEDを消灯
    digitalWrite(ledBlue, LOW);
    digitalWrite(ledRed, HIGH);
  }
}
```

上記でバックエンドに投げる側の実装はOKです。
次にユーザーインターフェイス側です。
これも非常に容易でして、Firebaseの管理画面に <b>ウェブアプリに Firebase を追加</b> というリンクがあり、 [Get Started with Firebase for Web Apps](https://firebase.google.com/docs/web/setup?hl=ja)に使い方が載ってますので、そちらを参考にしていただき、キチンとデザインされたUIに導入していただければ良いと思います。
ちなみに、デザインスキルに乏しい私がやるとこんな感じにチープな仕上がりになります。一応レスポンシブ対応なのでスマホブラウザだと縦表示になったりはします。
<img width="500" alt="スクリーンショット 2017-12-13 1.10.41.png" src="https://qiita-image-store.s3.amazonaws.com/0/114936/4a2ffe3d-75bb-963c-47d7-de8eabc236a5.png">

ソースコードを一部抜粋するとこんな感じです（Configや必要なコンポーネントのインクルード部分は省略）。

```ui.html
<script>
  var db = firebase.database();
  var key = db.ref("Firebaseデータキー");
  key.on("value", function(snapshot) {
    document.getElementById("id").innerText = (snapshot.val().status == "1") ? "使用中" : "未使用";
    document.getElementById("id").style.backgroundColor = (snapshot.val().status == "1") ? "Red" : "Blue";
  });
</script>
```

## 会議室が未利用の場合はスケジュールを自動削除する
前章で計測デバイス、及びその計測。そして状態の可視化までできました。
続いてはスケジュールの操作です。
当社では、Googleカレンダー上でスケジュールや会議室の予約を管理しており、必然的に <b>Google Apps Script</b> を採用するのが最良と判断しました。
当然ですが、Google Apps ScriptからFirebase APIも実行可能です。そしてやはりライブラリです。
[こちら](https://sites.google.com/site/scriptsexamples/new-connectors-to-google-services/firebase)を参考に、GASのスクリプトエディタからライブラリを追加してください。
なお、ライブラリのバージョンは適宜ご指定ください。投稿時期の兼ね合いもあるのであえて明示はしません。
Firebaseとのインターフェースのコードを抜粋するとこんな感じでシンプルな実装になっています。

```firebase.gs
var firebaseUrl = "FirebaseのURL";
var secret = "Firebaseのデータベースシークレット";
var base = FirebaseApp.getDatabaseByUrl(firebaseUrl, secret);
// Firebaseからデータを取得する
base.getData("Firebaseデータキー");
// Firebaseにデータをセットする
base.setData("Firebaseデータキー", "セットしたい値");
```

続いて、カレンダーの操作です。

GASからGoogleカレンダーにアクセスする為には <b>CalendarApp</b> というクラスオブジェクトを使います。
[リファレンス](https://developers.google.com/apps-script/reference/calendar/calendar-app)にもありますが `Calendar` という親オブジェクトの下に `CalendarEvent` という子オブジェクトがあり、この子オブジェクトであるカレンダーイベントが、カレンダーに登録している予定そのものです。
こんな感じで、オブジェクトをブレイクダウンし、カレンダーイベントのオブジェクトを操作してください。

```calendar.gs
// 全てのカレンダーオブジェクト取得  
var cal = CalendarApp.getAllCalendars();

// カレンダーオブジェクト
for (var i = 0; i < cal.length; i++) {
  // 日時指定でカレンダーイベントを取得
  var ev = cal[i].getEvents(new Date('yyyy/MM/dd HH:mm:ss'), new Date('yyyy/MM/dd HH:mm:ss'));
  // カレンダーイベントオブジェクト
  for (var j = 0; j < ev.length; j++) {
    ev[j].getTitle(); // カレンダータイトルを取得
    ev[j].getDescription(); // カレンダー詳細を取得
    ev[j].getLocation(); // 場所を取得
    ev[j].getStartTime(); // 開始日時を取得
    ev[j].getEndTime(); // 終了日時を取得
    ev[j].deleteEvent();  // イベントを削除
  }
}
```

Firebaseとのインターフェースとカレンダー操作を組み合わせたものを、数分おきに実行されるようにトリガーを設定してください。
そして、開始日時をN分経過した時点でfirebaseの値を取得し、未使用値ならイベントを削除すれば良いです。

ちなみに、GASのトリガーに登録可能な数は最大で20個なので、以前に投稿した[記事](https://qiita.com/yorishigeito/items/2d74a5e73f6078a665e8)を参考にトリガーが最適化されるような工夫が必要です。

## 会議室が未利用の場合はログを残す（空予約者のあぶり出し）
前章の `deleteEvent` でイベントを削除する直前にスプレッドシートにイベント情報を書き出せば良いです。

```log.gs
var ss = SpreadsheetApp.openById("スプレッドシートID");
var sheet = ss.getSheetByName("シート名");
var newRow = sheet.getLastRow() + 1;
sheet.getRange(newRow, 1).setValue("書き出すイベントの内容");
```

## 会議終了の5分前にアラームを鳴らす
まずは計測デバイスにアラームを鳴らす為のモジュールを追加する必要があります。
秋葉原に圧電ブザーがありました。「ピーピーピー」と連続音を鳴らすものです。
<img width="284" alt="スクリーンショット 2017-12-13 2.58.10.png" src="https://qiita-image-store.s3.amazonaws.com/0/114936/72bd341a-5f58-9213-a207-2ea8033ec50a.png">

これを計測デバイスに組み込んだものがこちら。相変わらずイケてない。
<img width="284" alt="スクリーンショット 2017-12-13 3.06.45.png" src="https://qiita-image-store.s3.amazonaws.com/0/114936/8006cd32-e909-035d-fec3-22a8d5d9eaee.png">

そして、Arduinoから圧電ブザーを鳴らしてみましょう。
Arduinoでは手軽にブザーを鳴らす為のtone関数というものが用意されていますのでそれを実装します。

```tone.ino
// ブザーの接続ピン番号
const int buzzerPin = 16;

void loop() {
  // firebaseからアラートフラグを取得
  String strAlert = Firebase.getString("Firebaseデータキー");
  if (strAlert == "1") {
    // ピン番号, 出力周波数, 長さ（3秒）
    tone(buzzerPin, 1, 3000);
    // firebaseのアラート値を初期化
    Firebase.setString("Firebaseデータキー", "0");
  }
}
``` 

前章のGASによって、イベントの終了時間は取得できているので、会議終了の5分前にfirebaseにアラートフラグを立ててやればデバイス側でそれを検知し、アラームを鳴らしてフラグを初期化します。

# 導入後

2017年12月現在、一部の会議室でパイロット運用し、課題を洗い出しています。
ですので、これが完成形ではありません。デバイスはチープだし、UIも改善の余地があるので、全社展開の際には改良を予定しています。

これまでに判明していることとしては、Google Cloudが不安定なのか `サーバーエラーが発生しました。しばらくしてからもう一度試してください。` 的なエラーが定期的に発生するので、Try Catchによるエラーハンドリングは必須です（バッチですから）。
一方で利用者側からの要望は下記のようなものが挙がっており、随時対応中です。

* 空予約をしてしまうと（ボタンを押し忘れると）カレンダーが消えるのは困る
* 退出時に青ボタンを押し忘れる
* 部屋の予約だけ削除できないの？

# 部品類の情報共有
これまでの章では方式中心の説明でしたが、本デバイスに使用した部品に関する情報を列挙します。

<img width="284" alt="スクリーンショット 2017-12-13 13.49.36.png" src="https://qiita-image-store.s3.amazonaws.com/0/114936/f33c71d3-c519-a7c5-d76b-12755e2c23b5.png">
会議室の利用状況を変更する為のボタンです。正確には `ゲームスイッチ` と呼ばれ、ゲームセンターのアーケードゲーム機に付いているアレですね。単価250円ぐらい。
https://www.sengoku.co.jp/mod/sgk_cart/detail.php?code=EEHD-04AU

<img width="284" alt="スクリーンショット 2017-12-13 13.58.46.png" src="https://qiita-image-store.s3.amazonaws.com/0/114936/e29ad917-60dd-3f8b-a31f-ea4512c887c8.png">
会議室の利用状況を示すLEDです。単価50円ぐらい。
https://www.sengoku.co.jp/mod/sgk_cart/detail.php?code=EEHD-0GD3

<img width="284" alt="スクリーンショット 2017-12-13 16.25.58.png" src="https://qiita-image-store.s3.amazonaws.com/0/114936/7eccae07-20f5-a6df-924f-231627f58afd.png">
人感（焦電型赤外線）センサーです。単価500円。
http://akizukidenshi.com/catalog/g/gM-09002/

<img width="284" alt="スクリーンショット 2017-12-13 16.28.04.png" src="https://qiita-image-store.s3.amazonaws.com/0/114936/87c5886b-c32d-86f3-b8a9-8e27ba2a9c02.png">
音の大きさを電圧値に変換する音量センサーです。単価800円。
https://www.sengoku.co.jp/mod/sgk_cart/detail.php?code=EEHD-4S7J

<img width="284" alt="スクリーンショット 2017-12-13 16.29.49.png" src="https://qiita-image-store.s3.amazonaws.com/0/114936/005ec135-b712-cf24-40a5-a346c6c330da.png">
各種モジュールの信号や外からの信号を受発信するWi-Fiモジュールです。単価2,580円。
このモジュールはESP-WROOM-02単体よりも遥かに扱いやすく、USB-シリアル変換とか電圧変換レギュレーターが回路に組み込まれているので初めての方にはオススメです（単体で購入した方が安上がりですが…）。

<img width="284" alt="スクリーンショット 2017-12-13 2.58.10.png" src="https://qiita-image-store.s3.amazonaws.com/0/114936/72bd341a-5f58-9213-a207-2ea8033ec50a.png">
会議終了5分前アラートを鳴らす圧電ブザーです。300円ぐらい。

<img width="284" alt="スクリーンショット 2018-01-09 18.10.57.png" src="https://qiita-image-store.s3.amazonaws.com/0/114936/321c623a-9a35-d3f4-0562-2a98ffe82f1b.png">
抵抗器です。LED用に330Ωが2つ、ボタン用に10KΩが2つあればOK。単価5円ぐらい。

<img width="284" alt="スクリーンショット 2017-12-13 16.31.13.png" src="https://qiita-image-store.s3.amazonaws.com/0/114936/c9545526-d22d-339a-f42e-280210ee7228.png">
外箱（保存容器）です。3つで100円なので単価33円ぐらい。

<img width="284" alt="スクリーンショット 2017-12-13 16.33.50.png" src="https://qiita-image-store.s3.amazonaws.com/0/114936/4ae3b2d7-771a-e62e-b53b-a043f3cd54eb.png">
電子回路の基盤とも言えるブレッドボードです。単価150円ぐらい。

<img width="284" alt="スクリーンショット 2018-01-06 2.53.39.png" src="https://qiita-image-store.s3.amazonaws.com/0/114936/2d63ec54-300a-8451-c3b3-063a5a3d89e2.png">
<img width="284" alt="スクリーンショット 2018-01-06 2.53.50.png" src="https://qiita-image-store.s3.amazonaws.com/0/114936/8e9ff938-f715-88cc-e6d7-0f9da4beef38.png">
ジャンパーピン・ジャンパーワイヤです。電子回路には必需品。単価数十円から数百円。
http://akizukidenshi.com/catalog/c/cbreadj/

<img width="284" alt="スクリーンショット 2018-01-06 3.03.04.png" src="https://qiita-image-store.s3.amazonaws.com/0/114936/4393d735-aedf-a95e-aafe-4623792112d9.png">
microUSBケーブルです。Wi-FiモジュールとACアダプタを繋ぎます。単価100円ぐらい。
http://www.sengoku.co.jp/mod/sgk_cart/detail.php?code=EEHD-4Y7H#

<img width="284" alt="スクリーンショット 2018-01-06 3.31.25.png" src="https://qiita-image-store.s3.amazonaws.com/0/114936/e50e4042-f2a2-98f0-02f5-c00ad034e2b5.png">
はんだごてとはんだです。これがなくても電子回路は組めますが、部品とワイヤーを強固に接続する為にもあった方がいいです。1000円前後で購入。

# 工作関連の情報共有

<img width="284" alt="スクリーンショット 2018-01-09 17.22.48.png" src="https://qiita-image-store.s3.amazonaws.com/0/114936/d275033e-0f36-c447-a207-fef877916429.png">
ボタン・LEDとジャンパーワイヤをはんだで接続します。
ちなみに、LEDには抵抗が必要なので、写真のように予め抵抗と一緒に接続することをオススメします。

<img width="284" alt="スクリーンショット 2018-01-09 17.32.50.png" src="https://qiita-image-store.s3.amazonaws.com/0/114936/aa2d424c-2f4d-b6e0-6c39-8d65707b622c.png">
保存容器の蓋にボタン・LED・ブザーの穴を開けます。
保存容器の蓋の多くは柔らかい素材でできているのでカッターで簡単に開けられます。

<img width="284" alt="スクリーンショット 2018-01-09 17.58.18.png" src="https://qiita-image-store.s3.amazonaws.com/0/114936/f601f7e8-5eb1-d11a-127a-8488c27b5a2f.png">
ボタン・LED・ブザーと共に電子回路を組みます。
各部品に繋げたジャンパーワイヤをWi-Fiモジュールのピンに差し込む際、ソースコードで定義したピン番号に差し込んでください。

<img width="284" alt="スクリーンショット 2018-01-11 14.15.35.png" src="https://qiita-image-store.s3.amazonaws.com/0/114936/83fc8851-34f9-24b9-a88b-c412af5abc20.png">
保存容器そのものにmicroUSBケーブルのコネクタ用の穴を開けます。
これ、結構硬い素材なのではんだごてで溶かして開けました。バリ取りはカッターで。

<img width="284" alt="スクリーンショット 2018-01-11 14.23.14.png" src="https://qiita-image-store.s3.amazonaws.com/0/114936/61e1e683-b5d7-e3c9-0227-3ae7309e71b7.png">
蓋を閉めて、テプラ貼って、電源に繋いで、はい完成。

# 参考情報
https://www.mgo-tec.com/blog-entry-ss-wroom-howto01.html
https://www3.panasonic.biz/ac/j/control/sensor/human/index.jsp
https://sites.google.com/site/scriptsexamples/new-connectors-to-google-services/firebase
http://jkoba.net/prototyping/arduino/led_switch.html