お問い合わせ
トップ » SORACOM IoT DIY レシピ » M5Stackで作る、設定温度で通知が届くIoT水温計

SORACOM IoT レシピ:M5Stackで作る、設定温度で通知が届くIoT水温計

公開日: 2021年7月
レシピ難易度:★★☆☆☆

料理をするうえで温度管理は要です。その温度管理にもIoTを絡めてみたらもっと便利になるのではないでしょうか。
本レシピでは、M5Stackを使って設定温度になったら通知が届くIoT水温計を作っていきます。

全体構成

システム構成図

使用するSORACOMサービス

本レシピに連動した動画を公開しています

本レシピを行うのに必要な時間、概算費用

本レシピは以下の通りです。

  • 必要な時間: 約60分
  • 概算費用: 約15,000円

※ 概算費用: ハードウェアや SORACOM を始めとした各種サービスの概ねの費用 (送料などの付帯費用や無料枠適用は考慮しないものとしています)

このコンテンツの進め方

上から内容を読み進みながら作業を行なっていきます。また左サイドに追従する目次からページ内の移動が可能です。

本コンテンツは現状のままで提供され、株式会社ソラコムは、誤りがないことの保証を含め、明示であると黙示であるとを問わず、本コンテンツの記載内容につき、いかなる種類の表明も保証も行いません。

掲載情報の閲覧及び利用により、利用者自身、もしくは第三者が被った損害に対して、直接的、間接的を問わず、株式会社ソラコムは責任を負いかねます。

本コンテンツを実践する中で用意された機器、利用されたサービスについてのご質問は、それぞれの機器やサービスの提供元にお問い合わせをお願いします。機器やサービスの仕様は、本コンテンツ作成当時のものです。

株式会社ソラコムが提供する機器・サービスについてのご質問はフォームで受け付けております。機器・サービスご利用前の導入相談は https://soracom.jp/contact/ に、機器・サービスご利用開始後のサポートは、SORACOMユーザーコンソール内のサポートサイトから「リクエストを送信」(要ログイン)にてお問い合わせください。

Copyright (c) 2023 SORACOM, INC.

準備

本レシピを行うためには以下のものをご用意下さい。

ハードウェア

品名数量価格備考
M5Stack Basic 3G 拡張ボード セット112,980円セットの中には以下のものが含まれています。それぞれを個別に準備しても構いません。
・M5Stack Basic x 1台
M5Stack 用 3G 拡張ボード x 1台
※注意: M5Stack 用 3G 拡張ボードが対応している M5Stack は Basic と Gray の2モデルです。M5Stack FIRE は非対応ですのでご注意ください。
SORACOM 特定地域向け IoT SIM (plan-D ナノサイズ)1903.1円M5Stack用3G拡張ボードが対応しているSIMサイズはナノサイズです。金額は1枚あたりで、決済時に商品の合計金額が1円未満の金額については繰り上げとなります。
対辺1.5mm 六角レンチ(ドライバー)1約300円M5Stack 用 3G 拡張ボードへ SIM を挿す際にボードの取り付け・取り外しに使用します。
単線温度センサー1約869円OneWireプロトコルによる防水温度センサーです。Seeed Technology社から販売されているのは加工をすることなく(抵抗の追加作業)することなく使用できます。
開発用パソコン1Arduino IDE と M5Stack 開発環境が整っていること。セットアップ方法は 【SORACOM ハンズオン】M5Stack 開発環境セットアップ (Windows / macOS 共通) (全体で約90分) をご覧ください。
(必要な方のみ)USB 変換アダプタ1パソコンに USB Type-A ポートがない場合に準備してください。1A 以上の電力が供給できるものを利用してください。(USB 3.0以上に対応していれば概ね安心です)
(必要な方のみ)USB Type-C ↔ Type-C ケーブル1パソコンに USB Type-A ポートが用意できず、また、USB 変換アダプタも用意できない場合に準備してください。

※ 金額はレシピ作成時となります。金額は税込み・送料別です。その他は参考価格となります。

ご購入について

ハードウェアは以下よりご購入いただけます。

ご購入先にはSORACOM IoT ストア以外のショッピングサイトも含まれています。ご購入先や在庫、動作保証をするものではありませんのでご留意ください。

その他必要なもの

必要なもの費用作成方法など
SORACOM アカウント無料※SORACOM アカウントの作成 (JP)

※ アカウント作成・維持の費用の料金です。

M5Stack 用 3G 拡張ボードに SIM を取り付ける

M5Stack 用 3G 拡張ボード(以下、3G拡張ボード)には SIM スロットが備わっており、ここに SIM を入れることで 3G 通信が可能となります。 SIM の取り付け・取り外しは 3G拡張ボードをケースから取り外す必要があります。

3G拡張ボードをケースから取り外す

3G拡張ボードの四隅にあるネジを取り外します。ネジは紛失しないようにしてください。

3G拡張ボードをケースから取り外す

SIM を取り付ける(取り外し方法含む)

SIM のサイズは nano です。取り付けはSIMをスロットに挿入したら「カチッ」と音が鳴るまで押し込みます。音が鳴ったら完了です。取り外しはSIMを奥まで押し込み「カチッ」と音が鳴ればSIMが出てきますので取り外しできます。

SIM を取り付ける

3G拡張ボードをケースに取り付ける

再度3G拡張ボードをケースに取り付けます。取り付け向きはピンが外側 (ケースから飛び出るように) します。逆向き (ピンがケースの内側を向いてしまっている) には取り付け内でください。

最後はネジで固定します。
ケースに取り付ける

M5Stack は正方形であるためボードとケースの取り付け方角がわかりづらいのですが、ボード上のピン(M5-BUS と呼ばれる)の辺と、ケース側面の切れ込みの辺を合わせるようです。

重ねる

取り付け終わったら一番下から「BOTTOM」「3G拡張ボード」「Core※」と重ねていきます。

※ Core = M5Stack の LCD(モニター)やボタンがついているモジュール

装着図

以上で3G 拡張ボードへの SIM 取り付け作業は完了です。

SORACOM Air を利用してセルラー通信をしてみる

3G 拡張ボードを使って SORACOM Air によるセルラー通信を行い、3G 拡張ボードの動作確認を行います。

セルラー通信ライブラリのインストール

3G 拡張ボードで利用できる通信ライブラリをインストールします。今回は TinyGSM というオープンソースライブラリを利用して、世界時計を M5Stack で表示してみます。

Arduino IDE を開き、[スケッチ]>[ライブラリをインクルード]>[ライブラリを管理…]をクリックします

※ 画面は macOS ですが、Windows も同様です。

“TinyGSM” をインストールする

ライブラリマネージャの一覧から TinyGSM (by Volodymyr Shymanskyy) を選んで[インストール]をクリックします。

バージョンはインストール時における最新バージョンを選んでください。

インストールが終了したら[閉じる]をクリックします。

“TinyGSM” を簡単に探し出す方法
検索用のテキストボックスへ tinygsm と入力すると素早く見つけることができます。

World Time API を取得して表示するスケッチ

動作テストを兼ねて世界時計を API で提供している World Time API から日時を取得して表示します。

ブラウザで試してみるには?
World Time API を始めとした、いわゆる Web API はブラウザでも実行できることがあります。今回の World Time API であればブラウザで http://worldtimeapi.org/api/timezone/Asia/Tokyo.txt を開いてみると、API で取得できる値をブラウザで表示できます。

Arduino IDE を起動し[ファイル]>[新規ファイル]を開くと void setup() { から始まる「空のスケッチ」が表示されます。

一度スケッチの内容を削除してから、後述のスケッチで置き換えてください。

m5stack_3gextboard_worldclock.ino

/*
 * Copyright (c) 2020 SORACOM, INC.
 * Released under the MIT license
 * https://opensource.org/licenses/mit-license.php
*/
#include <M5Stack.h>

#define TINY_GSM_MODEM_UBLOX
#include <TinyGsmClient.h>

TinyGsm modem(Serial2); /* 3G board modem */
TinyGsmClient ctx(modem);

void setup() {
  M5.begin();
  M5.Lcd.clear(BLACK);
  M5.Lcd.setTextColor(WHITE);
  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("done"));

  M5.Lcd.print(F("getModemInfo:"));
  String modemInfo = modem.getModemInfo();
  M5.Lcd.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("done"));

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

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

void loop() {
  M5.update();

  M5.Lcd.clear(BLACK);
  M5.Lcd.setCursor(0, 0);
  M5.Lcd.println(F("World Clock from worldtimeapi.org"));

  /* HTTP GET example */
  if (!ctx.connect("worldtimeapi.org", 80)) {
    Serial.println(F("Connect failed."));
    return;
  }
  Serial.println(F("connected."));

  /* send request */
  ctx.println("GET /api/timezone/Asia/Tokyo.txt HTTP/1.0");
  ctx.println("Host: worldtimeapi.org");
  ctx.println();
  Serial.println("sent.");

  /* receive response */
  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();
  M5.Lcd.println(buf);

  delay(1000 * 10);
}

マイコンボードに書き込む

M5Stack を PC に取り付けた後、Arduino IDE でボタンをクリックします。ボードへの書き込みが完了しました。と表示されたら正常終了です。

ファイルの保存ダイアログが表示されたら
ファイルを保存してください。ファイル名は任意ですが m5stack_3gextboard_worldclock というファイル名はどうでしょうか。
ファイルを保存しない (キャンセル) してもコンパイルとスケッチの転送はされます。

実行の様子

最初にモデムの型番や IP アドレスを表示した後に World Time API から取得したデータを表示します。

gprsConnect(soracom.io) から実際に IP アドレスが表示される(= 接続される)までに30~45秒程度掛かりますが正常です。

動作確認1

動作確認2

うまく動作しなかった場合

症状考えられる原因対策
getModemInfo() の結果が SARA-U201... と表示されない(空の場合など)3G 拡張ボードで内部エラーが発生している可能性があるカスタマーサポートへご連絡ください。
waitForNetwork() より先に進まない電力が不足しているUSB Type-C ケーブルを別のものに変えてみてください。
SIM が取り付けられていない。(もしくは SORACOM IoT SIM ではない)SORACOM 特定地域向け IoT SIM plan-D を取り付けてください。
電波が圏外もしくは微弱である可能性がある窓際等、通信条件が良い環境でお試しください。
SIM が SORACOM に登録されていない※ SORACOM ユーザーコンソールで確認できます ( “登録されてない” 事が確認できます)発注済みの SIM を登録する もしくは 通販サイトやイベント等で入手した SIM を登録する を行ってください。
SIM の「状態」が “準備完了” となっている( “使用中” でない)※ SORACOM ユーザーコンソールで確認できます当該 SIM のチェックボックスをチェックしてから[操作]>[使用開始]をクリックして “使用中” に変更してください。

以上で M5Stack の開発環境から M5Stack 本体と 3G 拡張ボードの動作確認が完了しました。

温度センサーの取り付け

単線温度センサーはGroveケーブルを使用してM5Stack横のGroveポートに接続します。

OneWireの接続

スケッチの書き込み

温度を測定し、クラウドにデータを送るスケッチをM5Stackへ書き込みます。

温度測定ライブラリのインストール

M5Stackで単線温度センサーを利用するためのライブラリをインストールします。今回はOneWireDallasTemperatureというオープンソースライブラリを利用します。

Arduino IDE を開き、[スケッチ]>[ライブラリをインクルード]>[ライブラリを管理…]をクリックします

ライブラリをインクルード

※ 画面は macOS ですが、Windows も同様です。

“OneWire” をインストールする

ライブラリマネージャの一覧から OneWire (by Paul Stoffregen) を選んで[インストール]をクリックします。

バージョンはインストール時における最新バージョンを選んでください。

OneWire

“OneWire” を簡単に探し出す方法
検索用のテキストボックスへ OneWire と入力すると素早く見つけることができます。

“DallasTemperature” をインストールする

同様に、ライブラリマネージャの一覧からDallasTemperature (by Miles Burton)を選んで[インストール]をクリックします。

バージョンはインストール時における最新バージョンを選んでください。

DallasTemperature

“DallasTemperature” を簡単に探し出す方法
検索用のテキストボックスへ DallasTemperature と入力すると素早く見つけることができます。

HTTP通信ライブラリのインストール

M5Stack で HTTP 通信を行うライブラリをインストールします。今回はArduinoHttpClientというオープンソースライブラリを利用します。

Arduino IDE を開き、[スケッチ]>[ライブラリをインクルード]>[ライブラリを管理…]をクリックします

ArduinoHttpClient

※ 画面は macOS ですが、Windows も同様です。

“ArduinoHttpClient” をインストールする

ライブラリマネージャの一覧から ArduinoHttpClient (by Arduino) を選んで[インストール]をクリックします。

バージョンはインストール時における最新バージョンを選んでください。

“ArduinoHttpClient” を簡単に探し出す方法
検索用のテキストボックスへ arduinoHttp と入力すると素早く見つけることができます。似たようなライブラリが表示されるため by Arduino という表記を頼りに探してください。

M5Stack へ書き込むスケッチを入手する

Arduino IDE を起動し[ファイル]>[新規ファイル]を開くと void setup() { から始まる「空のスケッチ」が表示されます。

一度スケッチの内容を削除してから、以下の内容と置き換えてください。

/*
 * Copyright (c) 2021 SORACOM, INC.
 * Released under the MIT license
 * https://opensource.org/licenses/mit-license.php
*/

#include <M5Stack.h>
#include <HTTPClient.h> // Why? see https://qiita.com/ma2shita/items/97bf1a0c3158b848019a
#define SerialMon Serial
#define TEXT_SIZE 2

#include <OneWire.h>
#include <DallasTemperature.h>


// add this line
#include <Wire.h>


#define ONE_WIRE_BUS 22 

OneWire oneWire(ONE_WIRE_BUS);
DallasTemperature DS18B20(&oneWire);

#include <ArduinoJson.h>
/* Dynamic configuration by SORACOM Air metadata:
 *  example:
{
  "COMMUNICATION_INTERVAL_SEC": 10,
  "LCD_TURN_ON_AT_BOOT": true
}
*/
long COMMUNICATION_INTERVAL_SEC = 10;
bool LCD_TURN_ON_AT_BOOT = true;

#define SerialAT Serial2 // `Serial2` is 3G Extension board for M5Stack Basic/Gray
#define TINY_GSM_MODEM_UBLOX
#include <TinyGsmClient.h>
TinyGsm modem(SerialAT);
TinyGsmClient socket(modem);

template<typename T, typename U> void check_connection_status_also_reconnect(T *modem, U *serialMon) {
  serialMon->println((modem->isGprsConnected()) ? "isGprsConnected(): true" : "isGprsConnected(): false");
  long s = millis();
  if (!modem->isGprsConnected()) {
    serialMon->print(F("modem.restart(): "));
    modem->restart();
    serialMon->println(F("done"));
    serialMon->print(F("getModemInfo(): "));
    serialMon->println(modem->getModemInfo());
    serialMon->print(F("getIMEI(): "));
    serialMon->println(modem->getIMEI());
    serialMon->print(F("waitForNetwork(): "));
    while (!modem->waitForNetwork()) serialMon->print(F("."));
    serialMon->println(F("Ok"));
    serialMon->print(F("gprsConnect(soracom.io): "));
    modem->gprsConnect("soracom.io", "sora", "sora");
    serialMon->println(F("done"));
    serialMon->print(F("isNetworkConnected(): "));
    while (!modem->isNetworkConnected()) serialMon->print(F("."));
    serialMon->println(F("Ok"));
    serialMon->print(F("localIP(): "));
    serialMon->println(modem->localIP());
  }
  long e = millis();
  serialMon->print(F("Modem bootup elapsed(ms): "));
  serialMon->println(e - s);
  serialMon->print(F("getSignalQuality(): "));
  serialMon->println(modem->getSignalQuality());
}

#include <ArduinoHttpClient.h>

template<typename T, typename U> String get_value_of(const char *tag_name, T socket, U *serialMon) {
  char path[255];
  sprintf_P(path, PSTR("/v1/subscriber.tags.%s"), tag_name);
  serialMon->print(F("path="));
  serialMon->println(path);
  HttpClient http = HttpClient(socket, "metadata.soracom.io", 80);
  http.get(path);
  int rc = http.responseStatusCode();
  String rb = http.responseBody();
  http.stop();
  serialMon->print(F("responseStatusCode(): "));
  serialMon->println(rc);
  serialMon->print(F("responseBody(): "));
  serialMon->println(rb);
  if (rc != 200) rb = "";
  return rb;
}

template<typename T, typename U> void send_to_cloud(const char *upstream_payload, T socket, U *serialMon) {
  serialMon->println(upstream_payload);
  HttpClient http = HttpClient(socket, "uni.soracom.io", 80);
  http.post("/", "application/json", upstream_payload);
  serialMon->print(F("responseStatusCode(): "));
  serialMon->println(http.responseStatusCode());
  http.stop();
}


bool lcd_status = LCD_TURN_ON_AT_BOOT;
bool using_cloud = true;

#include <Ticker.h>
Ticker ticker1;

volatile long count = 0;
void countdown_ticker_for_communication() {
  count--;
  if (count < 0) count = 0; // Avoiding minus
}

void reset_count() {
  count = COMMUNICATION_INTERVAL_SEC - 1; // Zero origin
}

void setup() {
  M5.begin();
  SerialAT.begin(115200, SERIAL_8N1, 16, 17);

  
  M5.Lcd.fillScreen(TFT_BLACK);
  M5.Lcd.setTextSize(TEXT_SIZE);
  M5.Lcd.setCursor(0, 0);
  SerialMon.println(F("Boot..."));
  M5.Lcd.println(F("Boot..."));
  
  M5.update();
  if (M5.BtnA.isPressed()) {
    SerialMon.println(F("Skip fetching config from Cloud"));
    M5.Lcd.println(F("Skip fetching config from Cloud"));
    delay(2000);
    using_cloud = false;
  }
  
  const size_t capacity = JSON_OBJECT_SIZE(6) + 140; // Code generate by https://arduinojson.org/v6/assistant/
  DynamicJsonDocument doc(capacity);  
  if (using_cloud) {
    SerialMon.println(F("Trying fetch config from Cloud (Wait about 50 seconds..."));
    M5.Lcd.println(F("Trying fetch config from Cloud (Wait about 50 seconds..."));
    check_connection_status_also_reconnect(&modem, &SerialMon);
    String tag_value = get_value_of("config_json", socket, &SerialMon);
    DeserializationError err = deserializeJson(doc, tag_value);
    if (err) {
      SerialMon.print(F("deserializeJson() failed: "));
      SerialMon.println(err.c_str());
      SerialMon.println(F("(But ignored.)"));
    }
  }
  
  // Load value from JSON or set default
  COMMUNICATION_INTERVAL_SEC = doc["COMMUNICATION_INTERVAL_SEC"] | COMMUNICATION_INTERVAL_SEC;
  LCD_TURN_ON_AT_BOOT = doc["LCD_TURN_ON_AT_BOOT"] | LCD_TURN_ON_AT_BOOT;
  
  if (LCD_TURN_ON_AT_BOOT) {
    M5.Lcd.wakeup();
    M5.Lcd.setBrightness(255);
  } else {
    M5.Lcd.sleep();
    M5.Lcd.setBrightness(0);
  }
  
  M5.Lcd.fillScreen(TFT_BLACK); // for clear
  M5.Lcd.fillRect(0, M5.Lcd.fontHeight() * 0, M5.Lcd.width(), M5.Lcd.fontHeight() * 1, TFT_DARKGREY); // 0means 0row
  M5.Lcd.setTextColor(TFT_WHITE);
  M5.Lcd.setTextDatum(TL_DATUM);
  M5.Lcd.drawString("Config", 0, 0);
  
  M5.Lcd.setCursor(0, M5.Lcd.fontHeight() * 1); // 1means 1row
  M5.Lcd.setTextColor(TFT_WHITE);
  M5.Lcd.printf("Communication Int.(s): %3u\r\n", COMMUNICATION_INTERVAL_SEC);
  M5.Lcd.print("Trun ON LCD at boot: "); M5.Lcd.println((LCD_TURN_ON_AT_BOOT) ? "true" : "false");
  
  M5.Lcd.fillRect(0, M5.Lcd.fontHeight() * 6, M5.Lcd.width(), M5.Lcd.fontHeight() * 1, TFT_DARKGREEN); // 6means 6row
  M5.Lcd.setTextColor(TFT_WHITE);
  M5.Lcd.setTextDatum(TL_DATUM);
  M5.Lcd.drawString("Current Status", 0, M5.Lcd.fontHeight() * 6); // 6means 6row

  reset_count();
  ticker1.attach_ms(1000, countdown_ticker_for_communication);
}

void loop() {
  float celsius;
  M5.update();
  
  if (M5.BtnC.wasReleased()) {
    if (lcd_status) {
      M5.Lcd.sleep();
      M5.Lcd.setBrightness(0);
    } else {
      M5.Lcd.wakeup();
      M5.Lcd.setBrightness(255);
    }
    lcd_status = !lcd_status;
  }

  DS18B20.begin();
  int device_count = DS18B20.getDS18Count();
  DS18B20.requestTemperatures();
  celsius = DS18B20.getTempCByIndex(0);
  
  M5.Lcd.fillRect(0, M5.Lcd.fontHeight() * 7, M5.Lcd.textWidth("Temperature: nn.n C"), M5.Lcd.fontHeight() * 1, TFT_BLACK); // 7means 7row
  M5.Lcd.setTextColor(TFT_WHITE);
  M5.Lcd.setTextDatum(TL_DATUM);
  M5.Lcd.setCursor(0, M5.Lcd.fontHeight() * 7); // 7means 7row
  M5.Lcd.printf("Temperature: %2.1f C\r\n", celsius);

  if(using_cloud) {
    SerialMon.print(F("countdown: ")); SerialMon.println(count);
    M5.Lcd.fillRect(0, M5.Lcd.fontHeight() * 9, M5.Lcd.textWidth("Communicate After: 9999s"), M5.Lcd.fontHeight() * 1, TFT_BLACK); // 9means 9row
    M5.Lcd.setTextColor(TFT_WHITE);
    M5.Lcd.setTextDatum(TL_DATUM);
    M5.Lcd.setCursor(0, M5.Lcd.fontHeight() * 9); // 9means 9row
    M5.Lcd.printf("Communicate After: %4u s\r\n", count);
    
      if (count == 0) {
      M5.Lcd.fillRect(0, M5.Lcd.fontHeight() * 10, M5.Lcd.textWidth("Send to cloud..."), M5.Lcd.fontHeight() * 1, TFT_BLACK); // 10means 10row
      if (device_count == 0) {
        SerialMon.println(F("No device found"));
        M5.Lcd.setTextColor(TFT_MAROON);
        M5.Lcd.setTextDatum(TL_DATUM);
        M5.Lcd.drawString("No device found", 0, M5.Lcd.fontHeight() * 10); // 10means 10row
        delay(3000);
      } else {
        SerialMon.println(F("Send to cloud..."));
        M5.Lcd.setTextColor(TFT_CYAN);
        M5.Lcd.setTextDatum(TL_DATUM);
        M5.Lcd.drawString("Send to cloud...", 0, M5.Lcd.fontHeight() * 10); // 10means 10row
        const size_t capacity = JSON_OBJECT_SIZE(1); // Code generate by https://arduinojson.org/v6/assistant/
        DynamicJsonDocument doc(capacity);
        doc["temp"] = celsius;
        char upstream_payload[512];
        serializeJson(doc, upstream_payload);
        if (using_cloud) {
          check_connection_status_also_reconnect(&modem, &SerialMon);
          send_to_cloud(upstream_payload, socket, &SerialMon);
        }
      }
      M5.Lcd.fillRect(0, M5.Lcd.fontHeight() * 10, M5.Lcd.textWidth("Send to cloud..."), M5.Lcd.fontHeight() * 1, TFT_BLACK); // 10means 10row
      reset_count();
    }
  }
}

マイコンボードに書き込む

M5Stack を PC に取り付けた後、Arduino IDE でボタンをクリックします。ボードへの書き込みが完了しました。と表示されたら正常終了です。

ファイルの保存ダイアログが表示されたら
ファイルを保存してください。ファイル名は任意ですが m5stack_temperature というファイル名はどうでしょうか。
ファイルを保存しない (キャンセル) してもコンパイルとスケッチの転送はされます。

以上で書き込みは完了です。

書き込みが終わったら、M5Stack から USB ケーブルを外し、電源を OFF にしておきましょう。

SORACOM Harvest Data の設定をする

M5Stack に取り付けた SIM に「SORACOM Air メタデータサービス」と「SORACOM Harvest Data」の設定を行います。

SORACOM ユーザーコンソールの[Menu]>[SIM 管理]とクリックして SIM 管理画面を開きます。

User Console

M5Stack に取り付けた SIM) にチェックを付け、[操作]>[所属グループ変更]とクリックします。

所属グループ変更

SORACOM の便利な使い方: 複数の SIM を同時に扱う
チェックをつける対象を複数にすれば、一度の複数の SIM を対象に操作が可能です。

「新しい所属グループ」のプルダウンボックスをクリックした後、[新しいグループを作成…]をクリックします。

新しいグループ

「グループ作成」のグループ名を入力して[グループ作成]をクリックします。

項目備考
グループ名temperature-check自由に入力可能です。日本語も設定可能です。

改めて「新しい所属グループ」に今入力したグループ名が入っていることを確認して[グループ変更]をクリックします。

SIM 管理画面で割り当てたグループ名をクリックします。

グループ名をクリック

[SORACOM Harvest Data 設定]をクリックして設定ができるように開きます。

「SORACOM Harvest Data 設定」で以下のように設定します。

項目設定値備考
(スイッチ)ONスイッチはクリックすることで OFF から ON に切り替えることができます。
Harvest Data On

その後[保存]をクリックしてください。
その後表示される「SORACOM Harvest Data が有効になっています」のダイアログでは[OK]をクリックしてください。

以上で「SORACOM Air メタデータサービス」と「SORACOM Harvest Data」の設定が完了しました。

SORACOM Harvest Data でデータを確認する

デバイスからのデータを SORACOM Harvest Data で確認します。

SORACOM ユーザーコンソールの[Menu]>[SIM 管理]とクリックして SIM 管理画面を開きます。

M5Stack に取り付けた SIM) にチェックを付け、[操作]>[データを確認]とクリックします。

表示された画面で[自動更新]を ON にします。

この表示された画面が SORACOM Harvest Data の画面となります。

自動更新とは?
画面を再読み込みしなくとも新しいデータが自動的に表示されるようにする機能です。5秒毎に新しいデータを表示するようになります。この機能が OFF の場合は、表示する期間を指定して[検索]をクリックすることで期間内のデータを表示できます。

M5Stack に USB ケーブルを接続して電源を ON にします

M5StackにUSB ケーブルを接続して電源をONにします。既に電源がONだった場合、または、USBケーブルを接続しても電源がONにならない場合は、電源ボタンを 1 回押してみてください。リセットがかかり、電源がONになります。

M5Stackに表示される画面

M5Stack の 電源 ON から 50~60秒程度すると、下記のような画面が表示されデータがクラウドに送信され始めます。

画面説明

ケトル内にワイヤーを入れて計測をしてみる

先ほど書き込んだスケッチは10秒ごとに測定温度を℃でデータ送信するようになっています。実際にケトル内にワイヤーを入れてみて、データが送信されるかを確認します。

SORACOM Harvest Data では以下のように確認できます。
湯沸しを開始してから沸騰するまでの温度変化です。

温度推移

seeed社より販売されている単線温度センサーの耐熱温度は -55°C から +125°C です。

デバイスが認識されていない場合は「No device found」というメッセージが表示され、クラウドへのデータ送信は行われません。

以上でデータ送信の確認は終了です。

即座にデータ送信をしたい場合は?
M5StackのBボタン(3つの並びの内真ん中のボタン)を押すと、即座にデータ送信されます。検証にお使いください。

M5Stackの電源がついている限り10秒に1度データ送信がされ、従量課金されます。クラウドにデータを送信せず、ローカルで温度測定をしたい場合は3つあるボタンのうち最も左にあるAボタンを押しながら再度電源を投入してください。

SORACOM Lagoon でダッシュボードを作成する (有効化まで)

SORACOM Harvest Data に蓄積されたデータを SORACOM Lagoon で活用していきます。

SORACOM Lagoon 用語解説

ここで SORACOM Lagoon で使われる用語を解説します。

用語意味
プランSORACOM Lagoon の契約プランです。機能と料金が異なります。SORACOM Lagoon のご利用料金に機能や料金の比較表があります。
メトリクス(メトリック)データが格納されている先です。SORACOM Lagoon では以下の4つの中から選び、その中からノード(SIMや回線)を選択します。
– Air = SORACOM Air for セルラー
– LoRa = SORACOM Air for LoRaWAN
– Sigfox = SORACOM Air for Sigfox
– Device = SORACOM Inventory デバイス
データソースメトリクスの参照先です。SORACOM Lagoon では “Harvest” (= SORACOM Harvest) を選ぶとメトリクスが展開されます。 Grafana ではテスト用のランダムデータが表示されます。
パネルパネルはデータを表示する領域です。データソースとメトリクスを指定すると、そのメトリクス(たとえばSIM)のデータをパネルで使えるようになります。様々なパネルが存在します。
ダッシュボード複数のパネルを束ねて「1枚の画面」にしたものがダッシュボードです。共有の単位となります。
SORACOM Lagoon ユーザー(Lagoon ユーザー)SORACOM Lagoon へログインするためのユーザー(IDとパスワードの組)SORACOM ユーザコンソールへのログインとは異なるユーザ一覧となり、皆さん自身で登録・削除が可能です。ダッシュボードやパネルを編集できる「編集可能」と表示専用の「読み取り」の2段階の権限を設定できます。作成可能数はプランによります。
データリフレッシュSORACOM Harvest から SORACOM Lagoon へデータが反映される事、もしくは反映タイミングとなります。反映タイミングはプランによります。
アラートメトリクスのデータに対して条件を設定し、その条件を満たしたら通知を行う仕組みの事です。

SORACOM Lagoonの有効化

SORACOM ユーザーコンソールの[Menu]>[データ収集・蓄積・可視化]>[SORACOM Lagoon]とクリックします。

[Lagoon の利用を開始する] ボタンをクリックします

プランのうち[Free]を選択したあと[次へ]をクリックします。

SORACOM Lagoon ユーザーの初期ユーザーに設定するパスワードを入力した後、[利用開始]をクリックします。

SORACOM Lagoon ユーザーの初期ユーザの ID は?
ID は SORACOM ユーザコンソールにログインしたときのメールアドレスが使われることになります。そのため、ここではパスワードのみ設定することになります。SORACOM ユーザコンソールへのログインとは異なるパスワードを設定する事を強くお勧めします。

利用開始がクリックできない場合は?
パスワードの条件が不足しています。全てに✔がつくようにパスワードを設定してください。

[Menu] → [データ蓄積・収集・可視化: SORACOM Lagoon] → [Lagoon にアクセス] から [SORACOM Lagoon3 コンソール] ボタンをクリックします

Lagoon 3 で SORACOM のデータを取り扱うために、Lagoon 3 の利用を開始すると、オペレーターごとに Lagoon 3 専用の SAM ユーザーが作成されます。ただし、標準では、この SAM ユーザーには「Harvest Files にファイルをアップロードする権限」が付与されていません。そのため、Soracom X/Y Image で Harvest Files に画像をアップロードできません。
本レシピでは必要ありませんが、Harvest Files に画像をアップロードする機能を利用するには、ユーザーコンソールのSORACOM Lagoon画面で、[アクセスを許可] をクリックして、必要な権限を付与してください。

メールアドレスと先ほど設定したパスワードを入力しログインします

ログインに成功すると、以下のような画面が表示されます。これが SORACOM Lagoon ログイン直後の画面です。ここから「ダッシュボード」や「パネル」を作成していきます。

背景を白くしたい
SORACOM Lagoon の標準では背景が黒となっています。これは設定で変更が可能です。右下のアイコンにカーソルを合わせて表示される[Preferences]をクリックします。

設定画面に “テーマ” を “Light” に変更して[保存]すると、背景が白くなります。

SORACOM Lagoon でダッシュボードを作成する (アラートの作成まで)

SORACOM Lagoon 3 をご利用の場合

以下では SORACOM Lagoon 2 の Alert 設定手順について記載しております。SORACOM Lagoon 3 では Alert 機能が大幅にアップデートされているため、ユーザードキュメントを参照ください。なお、レシピは随時 SORACOM Lagoon 3 の手順に変更予定です。

通知先を設定します。

アラートアイコンにカーソルを乗せると表示される「Alert」メニューから[Notification channels]をクリックします。

[New channel]をクリックします。

「Alerting」では以下の通りに入力します。

項目内容備考
名前email任意の名称で構いません。
タイプEmail
Email addresses通知の送付先メールアドレス; で複数指定が可能です。

入力し終わったら[Save]をクリックします。以下のように表示されていれば成功です。

[Test]について
保存の前に[Test]にてテストが行えます。テストを行うと以下のようなメールが届きますので、確認にご利用ください。
また、メールが届かない場合の確認ポイントは以下の通りです。
– メールアドレスが正しいこと
– no-reply@soracom.jp からのメールが迷惑メールに判定されてないこと、もしくは受信フィルタで拒否されていないこと

SORACOM Lagoon でダッシュボードを作成する (ダッシュボードのインポートまで)

あらかじめ設定が済んでいるテンプレートを使ってダッシュボードを作成します。

ダッシュボードアイコン にカーソルを乗せると表示される「Dashboards」メニューから[Manage]をクリックします。

[Import]をクリックします

[Import via panel json]のテキストボックスへ、以下のテキストを入力(貼り付け)します。

{
    "annotations": {
      "list": [
        {
          "builtIn": 1,
          "datasource": "-- Grafana --",
          "enable": true,
          "hide": true,
          "iconColor": "rgba(0, 211, 255, 1)",
          "name": "Annotations & Alerts",
          "type": "dashboard"
        }
      ]
    },
    "editable": true,
    "gnetId": null,
    "graphTooltip": 0,
    "id": 19,
    "links": [],
    "panels": [
      {
        "alert": {
          "alertRuleTags": {},
          "conditions": [
            {
              "evaluator": {
                "params": [
                  33
                ],
                "type": "gt"
              },
              "operator": {
                "type": "and"
              },
              "query": {
                "params": [
                  "A",
                  "1m",
                  "now"
                ]
              },
              "reducer": {
                "params": [],
                "type": "last"
              },
              "type": "query"
            }
          ],
          "executionErrorState": "keep_state",
          "for": "0m",
          "frequency": "1m",
          "handler": 1,
          "message": "",
          "name": "M5Stack OneWire alert",
          "noDataState": "no_data",
          "notifications": [
            {
              "uid": "jUA38eeGk"
            }
          ]
        },
        "aliasColors": {},
        "bars": false,
        "dashLength": 10,
        "dashes": false,
        "datasource": null,
        "fieldConfig": {
          "defaults": {
            "custom": {}
          },
          "overrides": []
        },
        "fill": 1,
        "fillGradient": 0,
        "gridPos": {
          "h": 8,
          "w": 12,
          "x": 0,
          "y": 0
        },
        "hiddenSeries": false,
        "id": 2,
        "legend": {
          "avg": false,
          "current": false,
          "max": false,
          "min": false,
          "show": true,
          "total": false,
          "values": false
        },
        "lines": true,
        "linewidth": 1,
        "nullPointMode": "null",
        "options": {
          "alertThreshold": true
        },
        "percentage": false,
        "pluginVersion": "7.5.0-pre",
        "pointradius": 2,
        "points": false,
        "renderer": "flot",
        "seriesOverrides": [],
        "spaceLength": 10,
        "stack": false,
        "steppedLine": false,
        "targets": [
          {
            "datatype": "standard",
            "devicetype": "subscribers",
            "properties": "temp",
            "refId": "A",
            "target": "440103197745415",
            "type": "timeseries"
          }
        ],
        "thresholds": [
          {
            "colorMode": "critical",
            "fill": true,
            "line": true,
            "op": "gt",
            "value": 33,
            "visible": true
          }
        ],
        "timeFrom": null,
        "timeRegions": [],
        "timeShift": null,
        "title": "M5Stack OneWire",
        "tooltip": {
          "shared": true,
          "sort": 0,
          "value_type": "individual"
        },
        "type": "graph",
        "xaxis": {
          "buckets": null,
          "mode": "time",
          "name": null,
          "show": true,
          "values": []
        },
        "yaxes": [
          {
            "format": "short",
            "label": null,
            "logBase": 1,
            "max": null,
            "min": null,
            "show": true
          },
          {
            "format": "short",
            "label": null,
            "logBase": 1,
            "max": null,
            "min": null,
            "show": true
          }
        ],
        "yaxis": {
          "align": false,
          "alignLevel": null
        }
      }
    ],
    "refresh": "10s",
    "schemaVersion": 27,
    "style": "dark",
    "tags": [],
    "templating": {
      "list": []
    },
    "time": {
      "from": "now-15m",
      "to": "now"
    },
    "timepicker": {
      "refresh_intervals": [
        "5s",
        "10s",
        "30s",
        "1m",
        "5m",
        "15m",
        "30m",
        "1h",
        "2h",
        "1d"
      ],
      "time_options": [
        "5m",
        "15m",
        "1h",
        "6h",
        "12h",
        "24h",
        "2d",
        "7d",
        "30d"
      ]
    },
    "timezone": "",
    "title": "水温センサー",
    "uid": "",
    "version": 10
  }
  

貼り付けたら[Load]をクリックします。

「Access denied to this dashboard」と表示されることがあります

[Folder] で「General」を選択して [Import] をクリックすると、「Access denied to this dashboard」と表示されますが、動作に影響はありません。

Loadをクリックすると”Not valid JSON”と表示される
テキストが上手く入力できていない場合は以下のように表示されます。

貼り付けたテキストを見直すようにしてください。

Options では以下のようになっていることを確認します。

項目名前
Name水温センサー
FolderGeneral
Unique Identifier (uid)<空欄>

確認できたら[Import]をクリックします。以下のように表示されていれば成功です。

SORACOM Lagoon でダッシュボードを作成する(パネルの編集からダッシュボードの保存まで)

“M5Stack OneWire” >[Edit]をクリックします。

“Query”タブで読み込むデータを設定します。”Query”タブが選択されている状態で、次のように設定します。

プルダウンに “M5Stack” が無い?
左から2番目のプルダウンについては、参考画像上では “M5Stack” と表示されていますが、これはSORACOM ユーザーコンソール上で SIM に名前を付けているからです。名前がついていない場合は SIM の IMSI (回線識別用の15桁の番号) が表示されます。

データが表示されていない?
データを蓄積し始めてから間もない場合、グラフの右端に「少しだけ」表示されている場合があります。これは、パネルの表示時間の標準が「過去6時間分」を表示するようになっているからです。

右上の「時間」ボタンで表示範囲を設定可能です。まずは「Last 15 minutes」くらいが良いでしょう。

それでもデータが表示されていない場合は、いったん SORACOM Harvest Data データの蓄積具合を確認するようにしてください。

“Alert” タブをクリックした後”Notifications”の部分までスクロールします。

[Send to]の をクリックし、先ほど作成した通知先 (例に沿っているなら email)を選択します。

画面右上の[Apply]ボタンをクリックします。

画面右上の をクリックすることでこれまでの作業を保存できます。

[Save]で保存します。

以上ですべての設定が終了です。

メールを確認してみる

温度センサーの先の銀色の部分を手で温め、33℃を超えると「Alerting」と書かれたメールがNotification Channelsで設定したメールアドレスに届きます。

また、温度センサーから手を離し、33℃未満になると以下のように「OK」と書かれたメールが届きます。

その他の機能や運用に向けた設定

ここからは、これまで紹介していなかった機能や、本番に向けた設定を紹介します。

アラートとなる距離の設定方法

本レシピの仕組みは水温センサーの温度を1分に1度評価し、最新の温度が33度を超える場合に通知をするようにしています。

下の図における赤いライン以上が通知される温度となります。

しかしながら、料理の種類や用途の違いによっては50℃を上回った時や、逆に20℃を下回った時を計りたい場合もあると思います。その場合、以下のように条件を変更することで対応できます。

最新の温度が50℃を上回った時

ConditionsがWHEN last() OF query(A, 1m, now) IS ABOVE 50となるようにします。

最新の温度が20℃を下回った時

ConditionsがWHEN last() OF query(A, 1m, now) IS BELOW 20となるようにします。

評価の頻度を10秒にしたい場合(Pro版のみ)

RuleがName M5Stack OneWire alert Evaluate every 10s For 0mとなるようにします。

Proプランにするとデータ更新間隔を最短5秒間隔にすることができます。
詳しくはこちらをご覧ください。

LCD (液晶)画面の消灯

M5Stack の LCD 画面は C ボタン(一番右のボタン)を押すことで、消灯することができます。消灯後、再度 C ボタンを押すと点灯します。

計測モード

温度センサーの動作を確認するための「計測モード」が実装されています。

M5Stack の A ボタン(一番左のボタン)を押しながら、電源ボタン(側面のボタン)を押してください。Skip fetching config from Cloud と表示され、すぐにメイン画面に移行します。(A ボタンはこの時点で離すことができます。

「Notification After」の表示が消え、クラウドとの通信が発生しません。

クラウドとの通信を行うモードにするためには、電源ボタンを押してリセットしてください。

実際に使ってみる

今回は富士市の学校給食名物「サイダーかん」を作っていきます。

参考にしたレシピ

富士市の学校給食名物「サイダーかん」(富士じかん)

本ページではサイダーかんの作り方の細かい部分については省略しています。詳細は上記URLのリンク先をご覧ください。

材料

材料分量
550ml
粉寒天12g
砂糖100g
メロンシロップ大さじ2
サイダー(加糖)350ml

事前準備

サイダーかんは、寒天液が約52℃になったタイミングでサイダー混ぜます。そこで、寒天液の温度が52℃に下がる前に通知することで、キッチンから離れていてもタイミングを逃さないようにします。今回は通知温度を55℃にすることで、キッチンへの移動の時間を確保するようにしました。

WHEN last() OF query(A, 1m, now) IS BELOW 55

寒天液を作る

水、粉寒天、砂糖、メロンシロップを混ぜた寒天液を作ります。

容器に寒天液を入れ、温度を計る

先ほど作った寒天液を容器に流し込み、作成した温度計の計測部を入れます。
この時、念のため計測部の先は消毒しておきましょう。

55℃以下になったところで次のような通知が送られてきます。

あとは、M5Stackのディスプレイに表示された温度を見つつ、52℃になったタイミングでサイダーをゆっくりと流し込みます。その後、粗熱が取れたら冷蔵庫に入れ、しっかり固まったら完成です。

ちなみに、私の環境では温度が52℃になるまで約15分かかりました。
温度変化のスナップショットはこちらから確認できます。

スナップショットとは
SORACOM Lagoonでは任意のダッシュボード・パネルを簡単に他のユーザーに公開することが可能です。

パネルタイトルをクリックし、[Share]をクリック

「Snapshot」タブを選択し、[Local Snapshot]をクリック

あとかたづけと注意事項

本レシピでは費用がかかるサービスを利用しています。
本項をよく読み、必要な操作や解除作業を行うようにして、想定外の費用が掛からないようにしてください。

費用について

ここで記載している金額は全て税込み、送料別となります。

SORACOM プラットフォームの利用料金

サービス/機能料金
SORACOM Air (plan-D)基本料: 11円/日通信料: 0.22円~/MB(今回の利用であれば 1MB 以内で収まる範囲)
SORACOM Harvest Data本機能を有効にしたグループに所属する1SIMあたり5.5円/日 (2000リクエスト/日/SIMを含む)2000リクエスト/日を超えた分は0.0044円/リクエスト
SORACOM Lagoon今回は Free プラン(無料)を使用しました。

無料利用枠について
SORACOM サービスでは一部サービスにおいて無料枠が設定されています。たとえば SORACOM Air for セルラーであればアカウント毎で30円/月の通信分や、SORACOM Funk であれば50,000リクエスト/月などです。料金詳細に「無料利用枠」として掲載されていますので、ご確認ください。

グループ解除

SORACOM Harvest Data 等、「機能が有効になっているグループに所属している SIM × 費用」となっているサービスにおいては、「機能を OFF にする」することで費用の発生を抑えることができます。またもう1つの方法として「グループに所属している SIM の数を減らす(= 解除する)」事でも費用を抑える事ができます。

また、SORACOM Funk はリクエスト発生時毎の従量課金となっているため、作成したグループ内の設定が SORACOM Funk のみとなっていれば、リクエストが発生しなければ費用は発生しません。

グループ解除の方法はグループからの解除 (JP)をご覧ください。

SORACOM Harvest Data のデータ削除(任意)

SORACOM Harvest Data は基本的にはデータ保管料は無料※です。そのため、保存しておいても害はありませんが、デモ等で利用する際にはデータを綺麗にしておく必要が出てくるため、データ削除について解説します。

※発生から40日を超えたデータは削除されます。40日以上データを保管したい場合はデータ保持期間延長オプション利用料金をご利用ください。

SORACOM Harvest Data 画面 ([操作]>[データを確認]) のデータテーブルで、削除したいデータのチェックボックスを付けた後に[削除]をクリックします。表示されたダイアログで改めて[削除]をクリックすると、削除されます。
※ 複数のデータにチェックをつければ一括で削除可能です。

データの復元はできませんのでご注意ください。

SORACOM Lagoon の解約

SORACOM Lagoon はオンラインで解約が可能です。 Free プランであれば有効化しておいても費用は発生しませんが、長期に渡って利用しない場合には解約も選択いただけます。

解約の方法はSORACOM Lagoon の解約(JP)をご覧ください。

次のステップ

本レシピでは、M5Stack と 3G 拡張ボードと 温度センサーを組み合わせた「調理用水温計」を作りました。

今回用いた温度センサーは-55℃から120℃まで対応しているということで、利用範囲は調理にとどまらず、お風呂の温度管理などにも応用できるのではないのでしょうか。

身の回りの温度管理が必要な場面でぜひ使ってみてください。

SORACOM IoT DIY レシピ »

レシピの達成、おめでとうございます!

達成したことをTwitterで呟く

普段の生活やビジネスに役立つ #IoTレシピ 「M5Stackで作る、設定温度で通知が届くIoT水温計」 を達成しました!
@SORACOM_PR https://soracom.jp/recipes_index/13732


ご質問などはこちらよりお問い合わせください。