SORACOM M5Stack 用 3G 拡張ボードで kintone に環境計測情報を保管

## 概要
SORACOM から発売された M5Stack 用 3G 拡張ボードと、温湿度気圧センサを使って kintone で環境計測データを管理できるか試してみました。
![IMG_3721.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/75892/e5c0697a-f517-321d-5ecd-7aef07fdb1da.jpeg)

## 利用機器
今回は SORACOM Discovery 2019 当日に発表され、会場で即注文した「M5Stack Basic 3G 拡張ボード セット」と、スイッチサイエンスで追加購入した「環境センサユニット」と、「電池モジュール」を追加した構成で試しています。このレポート作成中は環境センサ、電池モジュールとも在庫切れのようで、M5Stack 関連は人気があるようですね！

M5Stack Basic 3G 拡張ボード セット
https://soracom.jp/products/kit/3g_module_m5stack_set/

M5Stack用環境センサユニット
https://www.switch-science.com/catalog/5690/

M5Stack用電池モジュール
https://www.switch-science.com/catalog/3653/

## 設定
以下の手順で設定を行います。
__1.SORACOM SIM の登録__
__2.M5Srick と 3G 拡張ボード、開発環境の設定__
__3.kintone のアプリ追加__
__4.SORACOM Beam の設定__

### 1.SORACOM SIM の登録
SORACOM SIM の登録方法については、以下を参照ください。
https://dev.soracom.io/jp/start/console/#registsim

### 2.M5Srick と 3G 拡張ボード、開発環境の設定
M5Stack と 3G 拡張ボードのセットアップと、開発環境の設定は、以下を参照ください。
https://dev.soracom.io/jp/start/m5stack/
SORACOMさんはいつも丁寧なドキュメントを用意してくれるので助かります！

### 3.kintone のアプリ追加
以下のようなkintoneアプリを準備します。
![M5-3 .png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/75892/b00c25ff-fce7-31a8-8978-c60a3722879b.png)
「日時」は日時の自動セットで設定すると良いでしょう。（計測時間の数秒程度の誤差は許容範囲）
「場所」「MACアドレス」「CO2」などの項目はなくて良いです。
「温度」「湿度」「気圧」は数値で設定します。
__APIトークンを「レコード追加」のアクセス権限で設定します。__

### 4.SORACOM Beam の設定
環境センサの計測値はkintoneのAPI経由で登録しますが、kintoneのAPIはHTTPSしかサポートしていません。

M5StackのWifi経由の通信ではライブラリィの HTTPClient で簡単に通信できますが、 今回利用する3G 拡張ボードのライブラリィ TinyGsmClient では HTTPS の通信手順を手作業で進める必要があります。HTTPSの実装は面倒なので、SORACOM Beam を利用して、HTTP で実装した通信を SORACOM Beam で HTTPS に変換するように設定します。

さらに SORACOM Beam で kintone API の認証トークンのHTTPヘッダーを SORACOM Beam 側に設定することで、エッジ側の M5Stack に認証データを持たなくて良いように設定します。

SORACOM Beam（ https://dev.soracom.io/jp/start/beam/#http ）を参照しながら、以下のように設定します。
![M5-1 .png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/75892/5770fff2-fe25-7361-f048-3e3a8f1b7c65.png)
![M5-2 .png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/75892/038060d7-d6c3-bf65-7c86-424f228ce5f5.png)

## 開発
M5Stack Docs に Unit ENV のサンプルコードがありますので、先に紹介した「M5Stack と 3G 拡張ボードのセットアップと、開発環境の設定」の「ステップ 2: M5Stack で開発してみる」のスケッチに、以下のセンサの計測部分を参考に処理を追記する方法で開発しました。

Unit ENV の説明（サンプルコード含む）
https://docs.m5stack.com/#/en/unit/env

Unit ENV の説明にある Github のリンクより
https://github.com/m5stack/M5Stack/tree/master/examples/Unit/ENV
Adafruit_Sensor.h
DHT12.cpp
DHT12.h
を入手します。
※ https://github.com/m5stack/M5Stack の最新マスターをダウンロードすると含まれています。

ですが、上記だけではライブラリィが不足していますので、以下より
https://www.arduinolibraries.info/libraries/adafruit-bmp280-library
Adafruit_BMP280.cpp
Adafruit_BMP280.h
を入手します。

先にも初回した、以下の「ステップ 2: M5Stack で開発してみる」で作成、保管したスケッチのフォルダに、上記のファイルを全て追加します。
https://dev.soracom.io/jp/start/m5stack/

以下のコードで、kintone に環境計測のデータが追加されるようになりました。
※実際のファイルの拡張子は ino ですが、コードを見やすくするため c で表記しています。

``` envKintone.c
#include <M5Stack.h>

// ENV Sensor I2C
#include <Wire.h>
#include "DHT12.h"
#include "Adafruit_Sensor.h"
#include "Adafruit_BMP280.h"

DHT12 dht12;
Adafruit_BMP280 bme;

// UART2 3G Modem
#define TINY_GSM_MODEM_UBLOX
#include <TinyGsmClient.h>

TinyGsm modem(Serial2);
TinyGsmClient ctx(modem);

int count = 0;

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

  M5.Lcd.clear(BLACK);
  M5.Lcd.setTextColor(WHITE);
  M5.Lcd.setTextSize(2);

  // 3G Module
  M5.Lcd.println(F("M5Stack + 3G Module"));
  M5.Lcd.print(F("modem.restart()"));
  Serial2.begin(115200, SERIAL_8N1, 16, 17);
  modem.restart();
  M5.Lcd.println(F(" Ok"));

  Serial.println();
  Serial.print("getModemInfo:");
  String modemInfo = modem.getModemInfo();
  Serial.println(modemInfo);
  
  M5.Lcd.print(F("waitForNetwork()"));
  while (!modem.waitForNetwork()) M5.Lcd.print(".");
  M5.Lcd.println(F(" Ok"));

  M5.Lcd.print(F("gprsConnect(soracom.io)"));
  modem.gprsConnect("soracom.io", "sora", "sora");
  M5.Lcd.println(F("OK"));

  M5.Lcd.print(F("isNetworkConnected()"));
  while (!modem.isNetworkConnected()) M5.Lcd.print(".");
  M5.Lcd.println(F("Ok"));

  M5.Lcd.print(F("IP addr: "));
  IPAddress ipaddr = modem.localIP();
  M5.Lcd.print(ipaddr);
  delay(1000);

  // ENV Sensor I2C
  Wire.begin();
  while (!bme.begin(0x76)){  
    M5.Lcd.println("Could not find a valid BMP280 sensor, check wiring!");
  }
  delay(1000);

  if (!ctx.connect("worldtimeapi.org", 80)) {
    Serial.println(F("Connect failed."));
    return;
  }
  Serial.println(F("connected."));
  
  delay(2000);
  M5.Lcd.clear(BLACK);
}

void loop() {

  // Power Off
  if (M5.BtnB.wasPressed()) {
    M5.Lcd.setTextSize(2);
    M5.Lcd.fillScreen(TFT_BLACK);
    M5.Lcd.setTextColor(YELLOW, BLACK);
    M5.Lcd.drawCentreString("Power Off...", 160, 80, 4);
    delay(2000);
    M5.powerOFF();
  }
  M5.update();

  // LCD Setting
  M5.Lcd.setCursor(0,0);
  M5.Lcd.setTextColor(WHITE,BLACK);
  M5.Lcd.setTextSize(3);
  
  // ENV Sensor I2C
  float tmp = dht12.readTemperature();
  float hum = dht12.readHumidity();
  float pressure = bme.readPressure();
  M5.Lcd.printf("Temp: %2.1f  \r\nHumi: %2.0f%%  \r\nPressure:%2.0fhPa\r\n", tmp, hum, (pressure / 100));
  M5.Lcd.println();
  M5.Lcd.printf("Measure count: %d\r\n", count);

  // Data Post For kintone
  if((count % 60) == 0){
    
    char payload[256];
    sprintf(payload, "{\"app\":\"154\", \"record\":{\"place\":{\"value\":\"Kataoka Test\"},\"temp\":{\"value\":\"%2.1f\"},\"humi\":{\"value\":\"%2.0f\"},\"pressure\":{\"value\":\"%2.0f\"}}}", tmp, hum, (pressure / 100));
    Serial.println(payload);
    
    ctx.connect("beam.soracom.io", 8888);
    ctx.println("POST /k/v1/record.json HTTP/1.1");
    ctx.println("Host: beam.soracom.io");
    char content_length_hdr[32];
    sprintf(content_length_hdr, "Content-Length: %lu", strlen(payload));
    ctx.println(content_length_hdr);
    ctx.println();
    ctx.println(payload);
    Serial.println("sent.");
    
    while (ctx.connected()) {
      String line = ctx.readStringUntil('\n');
      Serial.println(line);
      if (line == "\r") {
        Serial.println("headers received.");
        break;
      }
    }
    char buf[1 * 1024] = {0};
    ctx.readBytes(buf, sizeof(buf)); /* body */
    ctx.stop();
    Serial.println(buf);
  }
  count++;
  delay(1000);
}
``` 

## 結果と解説

###　結果
setup() 中の表示。
![IMG_3713.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/75892/5c761873-b431-71e3-119b-fa64a554ddee.jpeg)

計測開始。
![IMG_3715.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/75892/87943950-2324-029e-a1e4-7a102149ce5c.jpeg)

シリアルモニタでモデムの情報や、kintone に送信する json データ、HTTPS のレスポンスなどが確認できました。

``` 
M5Stack initializing...
getModemInfo:SARA-U201-63B-01
connected.
{"app":"154", "record":{"place":{"value":"Kataoka Test"},"temp":{"value":"29.2"},"humi":{"value":"89"},"pressure":{"value":"996"}}}
sent.
HTTP/1.1 200 OK
Server: nginx
Date: Mon, 12 Aug 2019 07:57:12 GMT
Content-Type: application/json;charset=utf-8
Transfer-Encoding: chunked
Connection: close
Vary: Accept-Encoding
X-Frame-Options: SAMEORIGIN
Cache-Control: no-cache, no-store, must-revalidate
X-ConcurrencyLimit-Limit: 100
X-ConcurrencyLimit-Running: 1
Set-Cookie: JSESSIONID=nqb6jfRIk2GXOjgrff5nnOE6DVXAN1uia1lXBwGkDrtE5hJkCtcS6NbmyoGb3b77;Path=/;Secure;HttpOnly
Set-Cookie: __ctc=Z08OKl1SaVwk4+EHCVFuAg==; expires=Fri, 10-Aug-29 07:40:12 GMT; domain=cybozu.com; path=/
Expires: Thu, 01 Jan 1970 00:00:00 GMT
Strict-Transport-Security: max-age=315360000; includeSubDomains; preload;
X-UA-Compatible: IE=Edge
X-Content-Type-Options: nosniff
X-XSS-Protection: 1; mode=block

headers received.
1b
{"id":"737","revision":"1"}
0
``` 

kintone にデータが登録されています。
![M5-4.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/75892/b6993a33-a94d-3ddc-1a4b-a808d17f80df.png)


### 解説（はまりどころ含む）

以下の modem.waitForNetwork() は電池モジュールで起動した場合に True が返って来ないことが多いです。
このままでは永久ループになる可能性がありますので、本来なら回避する処理を追加すべきです。
※電池モジュールではモデムが稼働するのに十分な電圧で給電できないのが原因かな？

``` envKintone.c
（前略）
  M5.Lcd.print(F("waitForNetwork()"));
  while (!modem.waitForNetwork()) M5.Lcd.print(".");
  M5.Lcd.println(F(" Ok"));
（後略）
``` 

私の自宅は3G電波が弱くてたまに繋がらないことがあります。
こんな場所では loop() 内で接続をチェックして、再接続する処理が必要ですよね。
※LTEの通信に全く問題なくても、3Gの通信が不安定な場所があるので要注意です。

``` envKintone.c
（前略）
  M5.Lcd.print(F("gprsConnect(soracom.io)"));
  modem.gprsConnect("soracom.io", "sora", "sora");
  M5.Lcd.println(F("OK"));

  M5.Lcd.print(F("isNetworkConnected()"));
  while (!modem.isNetworkConnected()) M5.Lcd.print(".");
  M5.Lcd.println(F("Ok"));
（後略）
``` 

本来ならヘッダーやら、HTTPSに変換する処理やらを実装しなければならないのですが、この記述で済むのは本当に楽！
※とはいえ、Wifi利用時のHTTPClientのようなライブラリィもあると便利なんですが。

``` envKintone.c
（前略）
    ctx.connect("beam.soracom.io", 8888);
    ctx.println("POST /k/v1/record.json HTTP/1.1");
    ctx.println("Host: beam.soracom.io");
    char content_length_hdr[32];
    sprintf(content_length_hdr, "Content-Length: %lu", strlen(payload));
    ctx.println(content_length_hdr);
    ctx.println();
    ctx.println(payload);
（後略）
``` 

### まとめ
電池モジュールでの起動に不安があるが、オフィスなどでの環境計測には使えそう。
（今後屋外の高温環境でも試験予定。）
外部APIを使う場合は、SORACOM Beam などとの併用がお勧め。
MH-Z19 CO2センサとの併用は UART の数が不足し実現しなかった。
（別途 M5Stack で使えることは確認済だが、LCDと3GモデムがUARTを利用するため口が不足。）
700 mAh のバッテリーで約3時間4分稼働していました。
（約230mAhの消費電力）

## 参考（関連）情報
M5Stack の情報
https://docs.m5stack.com/#/
https://www.switch-science.com/catalog/3647/
https://goworkship.com/magazine/what_can_m5stack_do/
https://mag.switch-science.com/2018/02/28/getting-started-with-m5stack/
https://github.com/m5stack/M5Stack/blob/master/docs/getting_started_ja.md

M5Stack用 3G 拡張ボードの情報
https://soracom.jp/products/kit/3g_module_m5stack/
https://dev.soracom.io/jp/start/m5stack/

SORACOM Beam の情報
https://soracom.jp/services/beam/
https://dev.soracom.io/jp/start/beam/#http

M5Stack ENV センサユニットの情報
https://docs.m5stack.com/#/en/unit/env
https://github.com/m5stack/M5Stack/tree/master/examples/Unit/ENV