爆速開発IoT遠隔センサーロガー: obnizからGoogle Spreadsheetにセンサー値を定期的に保存（環境構築不要！汎用性高）

温度や明るさ，ドアが開いた回数，人が通った回数などのデータをセンサーで取得し，モノの状態をリアルタイムにインターネットを通じてどこからでも知ることができるようにすることは，IoTの代表的な例だといえます．
これをobnizを使えば，環境構築不要，ハードウェアの配線も一瞬，ソフトウェアもほぼコピペで，簡単に実現できるので，その方法をまとめました．

# この開発手法のメリット
後に説明するように，今回はobnizとGoogle Apps Script, Googleスプレッドシートを使って開発しました．これにより，

- **環境構築不要**…デバイスもサーバー側のプログラムも，全てブラウザ上で開発可能
- **ハードウェアの配線が一瞬**…obnizはセンサーやモーターなど様々なペリフェラルを直接接続できる
- **デバイスのプログラムがシンプル & ほぼコピペ**…obniz公式に多様なパーツのライブラリが用意されている
- **デバイスへのプログラム書き込み不要，無線で即動作開始**…obnizはクラウドを介して操作できる
- **サーバー側のプログラムもほぼコピペ**…GoogleスプレッドシートをIoT向けロガーとして使えるよう汎用なGASプログラムを掲載
- **リアルタイムにデータ閲覧可能**…Googleスプレッドシートにアクセスすればどこからでも最新のデータやグラフが見られる
- **更にPythonでのデータ解析もシームレスに実行可能**…Google Colabでスプレッドシートの最新データをインポートできるのでいちいちCSVにしてダウンロードする必要もない

といったメリットがあります！！

# obnizとは
[obniz（オブナイズ）](https://obniz.io/ja/)は日本のCambrianRobotics社が開発したマイコンボードで，Wi-Fiに接続してインターネット経由で操作します．JavaScriptやPython, ブロックプログラミングで記述することができます．

スマートフォンやPCのブラウザ上でJavaScriptから操作できるので，スマートフォンの加速度センサやカメラと組み合せることもできますし，TensorFlow.jsを使った機械学習とも相性が良いです．

さらに「最大1Aまで利用でき，5v/3v切り替えやオープンドレインも利用できる12のIO」が搭載されているため，モータードライバ不要で，ハードウェアは非常にシンプルに，例えば多機能なラジコンを作ることもできます．

# Google Apps Scriptとは
[Google Apps Script](https://developers.google.com/apps-script/)とは，クラウドで動き，Gmail, カレンダー，ドライブなどGoogleの様々なサービスと連携できる，JavaScriptで開発可能な，Googleの迅速なアプリケーション開発プラットフォームです．インストール不要ですぐに使い始めることができます．略してGAS（ガス）と呼ばれます．

# 構成
obniz側とGAS側の2つのプログラムを書きます．
obnizでセンサー値を取得すると，GASで作成したウェブアプリケーションにその値がPOSTされ，Googleスプレッドシートの最下行に追記されるようにします．

実際には，スマートフォンやPCなどからobniz Cloudに接続し，クラウド上のAPIを操作することで，obnizを操作できます．またはobniz Cloudのサーバーレスイベント機能を利用することで，Webhookをきっかけとしたり，定期的に実行することもできます．

obniz側のプログラムでは，

- obnizでセンサー値を取得
- センサー値をGAS側ウェブアプリケーションにPOST

します．

GAS側のプログラムでは，

- POSTされたデータをスプレッドシートに追記
- GETされたら溜まったデータを返す

ようにします．

これにより，obnizで取得したセンサー値をスプレッドシートに表として保存することができ，リアルタイムに最新のデータを閲覧したり，グラフ化したり，CSVでダウンロードしたりと汎用できます．

# サーバー側の準備
Googleドライブを開き，センサーデータを保存したい場所に新しくGoogleスプレッドシートを作ります．

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/401669/83ab68ec-a821-96aa-3052-e0eab8d6dfc8.png" width="50%">

セルA1に`uuid`, B1に`date`と入力し，その右には送信予定の各データのラベルを付けてください．このラベルは，obniz側のプログラムで送信するオブジェクトのキーと合わせておく必要があります．また，シート名は`log`としました．
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/401669/3563983c-c24e-b090-22d4-a95837122130.png)

次に，GASのスクリプトを作成します．「ツール」→「スクリプトエディタ」を開きます．
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/401669/8a18e9ed-1b2f-d64f-2a98-333260265403.png)

そして既存のコードを，以下のコードで上書きしてください．
このプログラムは，[Google SpreadSheet にデータを追加、更新、削除する Web API をサクッと作る](https://qiita.com/takatama/items/e5cb83012d14c0094a79)を参考にして一部改変しました．

```javascript:code.gs
function createRow(sheet, parameter) {
  var keys = sheet.getDataRange().getValues()[0];
  var row = [];  
  keys.map(function(key) {
    var value = parameter[key];
    if (value) {
      row.push(value);
    }else{
      row.push(null);
    }
  });
  return row;
}

function appendRow(sheet, parameter) {
  parameter['uuid'] = Utilities.getUuid();
  parameter['date'] = new Date();
  var row = createRow(sheet, parameter);
  sheet.appendRow(row);
  return parameter;
}

function updateRow(sheet, parameter) {
  var row = createRow(sheet, parameter);
  var values = sheet.getDataRange().getValues();
  for (var i = 0; i < values.length; i++) {
    if (values[i][0] === row[0]) {
      sheet.getRange(i + 1, 1, 1, values[i].length).setValues([row]);
    }
  }
}

function deleteRow(sheet, parameter) {
  var row = createRow(sheet, parameter);
  var values = sheet.getDataRange().getValues();
  for (var i = 0; i < values.length; i++) {
    if (values[i][0] === row[0]) {
      sheet.deleteRow(i + 1);
    }
  }
}

function findRow(sheet,val,col){
  var dat = sheet.getDataRange().getValues();
  for(var i=1;i<dat.length;i++){
    if(dat[i][col-1] === val){
      return i+1;
    }
  }
  return 0;
}

function obj2txtout(obj){
      var output = ContentService.createTextOutput();
      output.setMimeType(ContentService.MimeType.JSON);
     output.setContent(JSON.stringify(obj));
  return output;
}

function doPost(e) {
  var SHEET_ID = PropertiesService.getScriptProperties().getProperty("SHEET_ID");
  var ss = SpreadsheetApp.openById(SHEET_ID);
   if (e.parameter.sheet) {
     var sheet = ss.getSheetByName(e.parameter.sheet);
   }else{
    var sheet = ss.getSheetByName("シート1");
   }
  if (e.parameter.action) {
    var action = e.parameter.action.toLowerCase();
    if (action === 'update') {
     // updateRow(sheet, e.parameter);
    } else if (action === 'delete') {
     // deleteRow(sheet, e.parameter);
    }
  } else {
    var param=appendRow(sheet, e.parameter);
    return obj2txtout({ message: "success!", });
  }
}

function doGet(e){
  var SHEET_ID = PropertiesService.getScriptProperties().getProperty("SHEET_ID");
  var ss = SpreadsheetApp.openById(SHEET_ID);
   if (e.parameter.sheet) {
     var sheet = ss.getSheetByName(e.parameter.sheet);
   }else{
    var sheet = ss.getSheetByName("シート1");
   }
  var read_enabled_sheet_names=["log","シート1"];
  if(read_enabled_sheet_names.indexOf(sheet.getName()) == -1){
    return obj2txtout({message: "failed. Do not have read permission.",});
  }
  return obj2txtout({message: "success!", data: sheet.getDataRange().getValues(), });
}
```

そして，「ファイル」→「プロジェクトのプロパティ」から，
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/401669/72793c49-e19a-2800-ff16-7978406b950b.png)

「スクリプトのプロパティ」に，プロパティには`SHEET_ID`, 値には，スプレッドシートの編集画面のURL`https://docs.google.com/spreadsheets/d/YOUR-SPREADSHEET-ID/edit#gid=0`の`YOUR-SPREADSHEET-ID`に当たる部分の文字列を指定してください．

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/401669/3c8cd485-8870-b074-3024-6b2749f7aaf5.png" width="70%">

保存したら，「公開」→「ウェブアプリケーションとして導入…」から，「プロジェクトバージョン」は**New**，「アプリケーションにアクセスできるユーザー」は**全員（匿名ユーザーを含む）**を選択して**更新**してください．
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/401669/7b99cbf8-004e-8f67-7eb2-e32957597fe2.png" width="70%">

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/401669/a9913894-a5e5-f6ac-b63e-91cba7189c61.png" width="70%">
そして表示される「現在のウェブアプリケーションのURL」をコピーしておいてください．


# obniz側のプログラムを作る
[obniz SDKパーツライブラリ](https://obniz.io/ja/sdk/parts)にあるパーツであれば，ほぼコピペで完成します．今回は，LM60という温度センサがあったのでそれを使いました．

[obniz公式サイト](https://obniz.io/ja/)にアクセスし，右上の「開発者コンソール」で，左側のメニューから「リポジトリ」を選択します．右上の「新規作成」を押して適当なファイル名を付けて「作成」すると，エディタ画面に移ります．

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/401669/7f21c82f-c36a-8399-fa69-fa8c65eedc7b.png" width="70%">

`<body>`~`</body>`の中身を，以下のコードに書き換えます．
パーツライブラリのサンプルをコピペしてコードを作成しました．

```html:sensor_logger.html
    <div id="obniz-debug"></div>
    <h1>obniz Sensor Logger</h1>
    <button id="post">送信</button>
    <button id="get">取得</button>

    <script>
      var obniz = new Obniz("XXXX-XXXX");//obnizのIDを指定してください
      const url="";//GASのウェブアプリケーションのURLを指定してください
      var sensor;
      
      obniz.onconnect = async function () {
        sensor = obniz.wired("LM60",   { gnd:0 , output:1, vcc:2});
        var temp = await sensor.getWait();
        console.log(temp);
        var param={sheet: "log", obniz_id: obniz.id, temperature: temp };
        console.log(param);
        $.post(url, param)
          .done(function (data) {console.dir(data);});
      }

      $("#post").click(async function(e) {
        var temp = await sensor.getWait();
        $.post(url, {sheet: "log", obniz_id: obniz.id, temperature: temp })
          .done(function (data) {console.dir(data);});
      });
      
      $("#get").click(function(e) {
        $.get(url,{sheet: "log"},function(data){console.dir(data);});
      });
    </script>
```

この中で書き換える場所は，まず`new Obniz("XXXX-XXXX");`にobnizのIDを指定してください．そして`const url="";`には，GASの「現在のウェブアプリケーションのURL」を貼り付けてください．

また使用するセンサが異なる場合，そのパーツが[obniz SDKパーツライブラリ](https://obniz.io/ja/sdk/parts)にあればそのサンプルを参考に書き換える必要があります．サンプルがない場合でも，AD変換，I2CやSPI，シリアル通信のサンプルはあるので，それを参考にプログラムを書けばセンサを扱うことができます．

また今回は送信するデータのキーを`temperature`としていますが，送信するデータに合わせて，変更，また追加してください．

# ハードウェアの配線
使用するセンサとプログラムに合わせて，配線してください．といっても[LM60 - obnizパーツライブラリ](https://obniz.io/ja/sdk/parts/LM60/README.md)の通りにLM60を表向きにobnizに挿すだけでした．

そしてUSB電源に繋ぐと画面が点くので，表示にしたがってWi-Fiに接続します．既に接続されていれば，プログラム実行の待機中QRコードが表示されます．

![IMG_1841 2.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/401669/0d428be1-7c68-b965-ec44-1b353c9ca86c.jpeg)

写真はプログラム実行中の様子です．

# 実行
obnizのエディタ画面右上の実行ボタンを押すと，以下のように表示されます．

![スクリーンショット 2019-07-22 20.28.55.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/401669/4479ea8d-74b9-6096-4a8b-4fa983272e66.png)

今回載せたobniz側プログラムでは，画面を開いたときに1回センサー値を取得して送信し，また「送信」ボタンを押したときにもセンサー値を取得して送信します．また「取得」ボタンを押すと，スプレッドシートに溜まったデータを全て取得することができます．ブラウザのコンソールに出力しているため，画面に表示はされません．

この画面を開いた時点で，スプレッドシートを見てみると，ちゃんとデータが記録されています！
![スクリーンショット 2019-07-22 20.44.52.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/401669/7a1d2d22-99c2-d905-efea-35fa014bc03b.png)

obniz側の実行画面を再読み込みすることで再実行したり，「送信」ボタンを押すことで，スプレッドシートにさらに追記されていくのを見ることができます．

さらに，`date`の列と`temperature`の列を選択し，「挿入」→「グラフ」から折れ線グラフを挿入するだけで，図のように温度変化を可視化することができました！

![スクリーンショット 2019-07-22 21.06.46.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/401669/dda219c4-4118-a608-5312-6eef3364688b.png)


# 定期的に実行する
obnizの開発コンソール左側のメニューから「イベント」を選び，「新規作成」します．名前を入力し，「実行するWebApp」として先ほど作成したプログラムを選びます．「きっかけ」として「繰り返し」を選び，実行間隔を入力します．
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/401669/9e2d64d7-062e-73c5-9c1e-0749a8629cc2.png)

これでもう，いちいちobnizの実行画面を開いたり，送信ボタンを押す必要さえありません！

# センサーが感知した時刻を記録する
ドアの開閉時刻と回数とか，人の通過人数などを記録したい場合があると思います．
例えばPIRセンサーのHC-SR505では，赤外線で人や動物が近くにいるかを検出できます．
[HC-SR505 - obnizパーツライブラリ](https://obniz.io/ja/sdk/parts/HC-SR505/README.md)を使えば，`onchange`にセンサーデータを`POST`する関数を入れることで，センサーの値が変化したときにデータを送信することができます．今回の例では`$.post()`が，jQueryによる`POST`する関数でした．

# 集まったデータを解析する
[Google Colab](https://colab.research.google.com/)を使えば，環境構築不要で，ブラウザ上でPython, Jupyter Notebookのプログラムを編集・実行することができます．Pythonにはデータ解析やAIのライブラリが豊富であり，Google ColabではGoogleスプレッドシートのデータを読み込むことも容易にできるので，今回溜まったセンサーデータの解析にも非常に適しています．
以下のようなプログラムで，集まったデータをPandas DataFrameの形に収めることができます．

```python:analyze.py
!pip install --upgrade --quiet gspread
from google.colab import auth
auth.authenticate_user()

import gspread
from oauth2client.client import GoogleCredentials
gc = gspread.authorize(GoogleCredentials.get_application_default())

import pandas as pd
workbook = gc.open_by_key('YOUR-SHEET-ID')
worksheet = workbook.worksheet('log')
data = worksheet.get_all_values()
df = pd.DataFrame.from_records(data)
```

# 備考
## キーにハイフンを入れないほうがいい
[jsonのキーにハイフンをいれないほうがいい](https://qiita.com/missyyy/items/db6b73dac2d66c7a3158)の記事に書いてあるとおり，obnizからPOSTするオブジェクトのキーにはハイフンを入れないほうが良さそうでした．
スプレッドシートに溜まったセンサーデータをGETして解析するプログラムを作成する場合，JavaScriptでの取得時に戸惑う可能性があります．

## API呼び出し制限
- [obniz Cloudのサーバーレスイベント機能](https://obniz.io/ja/doc/obniz_cloud/about_event): 120 run/day, 1 runあたり30秒以内
- [GAS](https://developers.google.com/apps-script/guides/services/quotas): Triggers total runtime 90min/day

というAPI呼び出し制限があります．
より高頻度でデータを保存したい場合は，obniz側のプログラムで`setInterval`を用いて書き，node.js等で常に実行しておくという方法があります．しかし，GASの利用制限も超えてしまう場合は，Googleスプレッドシートを使うことはできないので，他のIoT向けサービスを検討する必要があります．IoTに特化したプラットフォームとして，[Google Cloud IoT](https://cloud.google.com/solutions/iot/?hl=ja), [Microsoft Azure IoT](https://azure.microsoft.com/ja-jp/overview/iot/), [AWS IoT](https://aws.amazon.com/jp/iot/)等があり，IoT向けの通信にはMQTTという通信方式が使われます．

# 最後に
obnizとGoogle Apps Script, Googleスプレッドシートを使って，環境構築不要，ほぼコピペでできるIoT遠隔センサーロガーを紹介しました．
集まったデータの解析もGoogle Colabを使えば，すぐにPythonで解析できます．
obnizがあれば，個人でも非常に簡単にいろんなものをIoT化して，さらにはデータ解析など好きなように応用することができそうです！これを応用していろんなデータを取得して解析してみたいと思います．