概要
メディカル・バイオ系の研究室には必ずあるフリーザーやディープフリーザー。特に、ディープフリーザーの温度上昇は今までの研究資産を失う可能性が有ります。それ故、巷では様々なメーカーからフリーザーの遠隔監視・警報システムが販売されていますが、それなりの価格がします。
そこで本記事では、比較的低価格(~6,500円)で簡易の温度遠隔監視・警報システムを構築しました。
- (注)記事の最後にAtomS3 LiteとUnit-KmeterISOユニットを使ったはんだ付け不要(しかも安い~4,500円)バージョンも追記しました。
- (注2)NanoC6とUnit-KmeterISOユニットを使った(~4,000円)バージョンも追記しました。
使用したモジュール
- M5Atom lite (M5STACK-C008)
- ATOMICプロトキット (M5STACK-A077)
- K型熱電対プローブ
- Amazonなどでも購入できます
- MAX31855使用 K型熱電対アンプモジュール
- Amazonなどでも購入できます
開発環境
Arduino IDE v2.2.1
- 追加のボード:
- 追加ライブラリ:
- M5Atomライブラリ
https://github.com/m5stack/M5Atom - Adafruit_MAX31855ライブラリ
https://github.com/Seeed-Studio/Seeed_MAX31850K - ThingSpeakライブラリ
https://github.com/mathworks/thingspeak-arduino
- M5Atomライブラリ
実装したこと
K型熱電対+熱電対アンプモジュールで温度を計測し、M5AtomLiteでwifiで3分ごとにThingSpeakに温度データを送信・蓄積し、設定温度以上で警報をLineNotifyで通知するシステムを構築した。同時に、ThingSpeakが一定時間データを受信しなかったときの警報と毎日の定時連絡機能も付加した。
ThingSpeakの初期設定
- ThingSpeakのページを開いて新しいアカウントを作成。Sign Upをクリック。ユーザID・メールアドレス・タイムゾーン・パスワードを設定しCreate Accountボタンをクリック。
- チャンネルの作成。チャンネルにはデータ・場所・状態を登録できる(8個まで)。今回は2個(庫内温度・モジュール温度)のセンサデータを書き込むためのチャンネルを作成。
- メニューバーの Channels > MyChannels をクリック。
- New Channel ボタンをクリック。
- 以下の情報を入力。
- Name;(チャンネルの名前)
- Field1; Temp(フィード1の名前)
- Field2: AmbTemp(フィード2の名前)
- Make Public(一般公開しても良いならチェック)
- Save Channel ボタンで保存
- 書き込み用のAPIキーの取得とチャンネルID
スケッチにはチャンネルIDとAPIキーが必要なので、
- メニューバーの Channels > MyChannels をクリックして、先ほど作成したチャンネルを開く。
- Channel ID と書かれた7桁の数字がチャンネルID。
- API Keys をクリックして Write API Key と書かれた英数字の文字列が書き込み用のAPIキー。
上記2つをメモしておく
結線
-
アンプモジュールに付属のターミナルブロック(熱電対接続部)をはんだ付けします。
-
ATOMICプロトキットの上半分に熱電対アンプモジュールをはめ込みますので、プロトキット付属の基盤の上部をニッパーなどで切り離します。
-
熱電対の黄色のコネクタを外し、金属線をむき出しにしてアンプモジュールのターミナルブロックに繋げます。極性がありますので、コネクタを外すときにどちらがプラス(+)でどちらがマイナス(-)だったか分かるようにした方が後々楽です。もし分からなくなったら、中身の被覆に赤糸ある方がプラスです。
プログラム
- Arduino IDEのインストール方法やマイコンボードの追加、ライブラリの追加、Atom Liteへのスケッチの書き込み方法等はネット上にあふれていますので、各自でお願いします。
- 下記、M5atom_MAX31855_SPI_ThingSpeak.inoの
WiFi設定部分
const char ssid[] = "your_SSID";
const char pass[] = "your_password";
と
ThingSpeak設定部分
const unsigned long myChannelNumber = your_Ch_##;
const char * myWriteAPIKey = "your_API_Key";
を各自の環境に書き換えてAtom Liteに書き込みます。
M5atom_MAX31855_SPI_ThingSpeak.ino
#include <M5Atom.h>
#include <ThingSpeak.h>
#include <SPI.h>
#include <WiFi.h>
#include <Adafruit_MAX31855.h>
// Wifi設定(Wifi環境に合わせて変更)
const char ssid[] = "your_SSID";
const char pass[] = "your_password";
// ネットワーク設定(必要ならネットワーク環境に合わせて変更)
// IPAddress ip(192, 168, 100, 100);
// IPAddress gateway(192, 168, 100, 1);
// IPAddress subnet(255, 255, 255, 0);
// ThingSpeak設定(チャンネルIDと書き込み用APIキーを変更)
const unsigned long myChannelNumber = your_Ch_##;
const char * myWriteAPIKey = "your_API_Key";
// ThingSpeak接続用
WiFiClient client;
// MAX31855のソフトウエアSPIのIOピン番号を指定
// M5Atom lite用
#define MAXDO 23
#define MAXCS 19
#define MAXCLK 22
// initialize the Thermocouple
Adafruit_MAX31855 thermocouple(MAXCLK, MAXCS, MAXDO);
void setup() {
M5.begin(true, false, true);
// 起動メッセージ出力
//Serial.begin(9600);
//Serial.println("Hello M5atom!");
// 温度データ取得
float temperature = thermocouple.readCelsius() - 14; //測定値-14℃, 適宜変更
float int_temperature = thermocouple.readInternal();
//温度取得に失敗したら再起動
if (isnan(temperature)) {
ESP.restart();
}
//LED制御
if (temperature < -65) {
M5.dis.drawpix(0, 0x00ff00); // green
} else {
M5.dis.drawpix(0, 0xff0000); // red
}
// ステーションモードに設定
WiFi.mode(WIFI_STA);
// IPアドレス設定
// WiFi.config(ip, gateway, subnet);
// 接続開始(繋がるまで40回繰り返す)
int lpcnt = 0 ;
WiFi.disconnect(true, true);
delay(500);
WiFi.begin(ssid, pass);
while (WiFi.status() != WL_CONNECTED) {
M5.dis.drawpix(0, 0xFF00FF); // purple
delay(2 * 1000);
M5.dis.clear(); //LED消灯
delay(3 * 1000);
lpcnt += 1 ;
if (lpcnt > 40) {
ESP.restart();
}
}
// センサーデータアップロード開始
ThingSpeak.begin(client);
M5.dis.drawpix(0, 0x0000ff); // blue
// フィードに値を設定
ThingSpeak.setField(1, temperature);
ThingSpeak.setField(2, int_temperature);
// ThingSpeakに送信
//Serial.println();
//Serial.print("Write fields...");
int x = ThingSpeak.writeFields(myChannelNumber, myWriteAPIKey);
// Check the return code
if (x == 200) {
//Serial.println("done");
M5.dis.clear(); //LED消灯
delay(1000); //これ入れないとLED消えないでdeepsleepする
// 2.5 min のディープスリープタイマー設定(usec)
esp_sleep_enable_timer_wakeup(150 * 1000 * 1000);
// GPIO39(M5atomのHOMEボタン)がLOWになったらディープスリープから復帰
pinMode(GPIO_NUM_39, INPUT_PULLUP);
esp_sleep_enable_ext0_wakeup(GPIO_NUM_39, LOW);
// ディープスリープ
esp_deep_sleep_start();
}
else {
ESP.restart();
}
}
void loop () {
}
確認
- ここまでできたら、ThingSpeakのMyChannelのウェブページを開くと熱電対で読み取った温度データが見れるはずです。
設置
- 裸の熱電対をそのまま超低温冷凍庫に入れると、霜がついて正確な温度計測ができないので下図のように1.5mLチューブの蓋に穴を開けて熱電対を差し込んだものを超低温冷凍庫に設置します。
- 設置方法は、フリーザー購入時であれば取り込み口から、すでに稼働中であれば扉の隙間から奥に差し込みます。
- 冷凍庫の表示温度と値がずれている場合には
float temperature = thermocouple.readCelsius() - 14
の補正値の値を変更してプログラムを再度書き込みます。
LINE Notifyの設定
異常検知や定時連絡をLINEで受け取るためのLINE Notifyの設定をします。
やるべきことは、LINE Notifyアカウントと友達になって、アクセストークンを発行することです。
LINE Notifyの初め方等を参照してやってみてください。
(注)このアクセストークンは二度と表示されないので、必ずメモしてください。再発行するとトークン番号が変わります。
ThingSpeakでのイベントトリガーとThingHTTPの設定
温度が閾値を超えたときの設定(ThingHTTP)
- ThingSpeakページ上部の「Apps」プルダウンから「ThingHTTP」を選びます。
- 緑色の「New ThingHTTP」ボタンを押します。
Name: 適当な名前を入れます
URL: https://notify-api.line.me/api/notify
Method: POST
Content Type: application/x-www-form-urlencoded
HTTP Version: 1.1
Headers (Name: Authorization
Headers (Value: Bearer your_LINE Notify_token (Bearer[半角スペース]先ほど発行されたLINE Notifyのアクセストークン)
Body: message=に続きここに書いた内容がそのままLINE Notifyに通知されます。%%で挟まれた部分は置き換えキーでメッセージ送信時のデータを引数にできます。日本語でもOK。
- 入力が終わったら、「Save ThingHTTP」ボタンを押して保存します。
温度が閾値を超えたときの設定(React)
- ThingSpeakページ上部の「Apps」プルダウンから「React」を選びます。
- 緑色の「New React」ボタンを押します。
React Name: 適当な名前を入れます
Condition Type: Numeric
Test Frequency: On Data Insertion
Condition (If channel: 温度検知したいChannelを選びます
Condition (field: 温度検知したいfield番号を選びます
Condition (field: is greater than
Condition (field: これ以上で知らせて欲しい温度(私は-65℃にしています)
Action: ThingHTTP
then perform ThingHTTP: 先ほど作成したThingHTTPを指定します。
Options: Run action only the first time the condition is met(上記設定した閾値を超えたら1回だけ通知がきます)
- 入力が終わったら、「Save React」ボタンを押して保存します。
- ここまでで、温度異常時の通知設定は終了です。
定時連絡設定(ThingHTTP)
- ThingSpeakページ上部の「Apps」プルダウンから「ThingHTTP」を選びます。
- 緑色の「New ThingHTTP」ボタンを押します。
- 「Body」部分以外は温度異常時の設定と変わりません。
Body: 定時連絡用のメッセージを記入します。
定時連絡設定(TimeControl)
- ThingSpeakページ上部の「Apps」プルダウンから「TimeControl」を選びます。
- 緑色の「New TimeControl」ボタンを押します。
Name: 適当な名前を入れます
Frequency: Recurring
Recurrence: Day
Time: 好みの時刻(例えば、10:00 pm)
Action: ThingHTTP
then perform ThingHTTP: 先ほど作成したThingHTTPを指定します。 - 入力が終わったら、「Save TimeControl」ボタンを押して保存します。
- ここまでで、定時連絡の通知設定は終了です。
ThingSpeakへのデータ不達時の通知設定(ThingHTTP)
- ThingSpeakページ上部の「Apps」プルダウンから「ThingHTTP」を選びます。
- 緑色の「New ThingHTTP」ボタンを押します。
- 「Body」部分以外は温度異常時の設定と変わりません。
Body: データ不達連絡用のメッセージを記入します。
ThingSpeakへのデータ不達時の通知設定(React)
- ThingSpeakページ上部の「Apps」プルダウンから「React」を選びます。
- 緑色の「New React」ボタンを押します。
React Name: 適当な名前を入れます
Condition Type: No Data Check
Test Frequency: Every 10 minutes(好みでどうぞ)
Condition (If channel: データ不達検知したいChannelを選びます
Condition (has not been updated for: 15min(データ不達時間, 好みでどうぞ)
Action: ThingHTTP
then perform ThingHTTP: 先ほど作成したThingHTTPを指定します。
Options: Run action only the first time the condition is met
LINE Notifyに通知が来るかの確認
- ThingSpeakページ上部の「Apps」プルダウンから「ThingHTTP」を選びます。
- 通知テストをしたいThingHTTPの「View」ボタンを押します。
- 右上部に四角で囲まれたGETから始まるhttps://....(下図)がありますので、GETを含めず「https:」以下をコピーしてブラウザで開くと設定した通知が送られて、ブラウザには「{"status":200,"message":"ok"}」と表示されるはずです。
実際に使ってみて
コロナ禍が始まりラボに頻繁に行けなくなった時期の2020年4月からシステムの稼働を始めました、2024年1月に超低温冷凍庫が故障し、アラーム発報のおかげで中身を迅速に移し替えることができました。現在は、新しい超低温冷凍庫で順調に稼働しています。
追記
この記事を書きながらM5Stack社のページを閲覧してたら、MAX31855モジュールに熱電対のソケットが付いたUnitを見つけました。M5Stack社の製品は開発の回転が早く当該ユニットがEOPになる可能性もありますが、煩わしいはんだ付けが必要ないので需要はあると思いESP32マイコンも最新のAtomS3 Liteに変更したバージョンを以下に記します。(変更が必要な部分のみ追記しました)
(追記版)使用したモジュール
- AtomS3 lite (M5STACK-C124)
- Unit-KmeterISO (M5STACK-U133-V11)
- K型熱電対プローブ
- Unit-KmeterISO付属の緑色被覆の熱電対(メーカースペック, -50℃~250℃)でも測れます。
- Amazonなどでも購入できます。
(追記版)開発環境
Arduino IDE v2.3.2
- 追加のボード:
- 追加ライブラリ:
- M5AtomS3ライブラリ
https://github.com/m5stack/M5AtomS3 - M5Unit-KMeterISOライブラリ
https://github.com/m5stack/M5Unit-KMeterISO - ThingSpeakライブラリ
https://github.com/mathworks/thingspeak-arduino
- M5AtomS3ライブラリ
(追記版)結線
- Unit-KmeterISOに付属のGroveケーブル(黒赤黄白のケーブル)でAtomS3 LiteのGroveポートとUnit-KmeterISOのGroveポートを接続します。(ノッチがあるので挿し間違いは無いです)
- Unit-KmeterISOの熱電対ソケットに熱電対を挿し込みます。(+が細くて-が太いです)
(追記版)プログラム
- 下記、AtomS3_KmeterISO_ThingSpeak.inoの
WiFi設定部分
const char ssid[] = "your_SSID";
const char pass[] = "your_password";
と
ThingSpeak設定部分
const unsigned long myChannelNumber = your_Ch_##;
const char * myWriteAPIKey = "your_API_Key";
を各自の環境に書き換えてAtomS3 Liteに書き込みます。
AtomS3_KmeterISO_ThingSpeak.ino
#include <M5AtomS3.h>
#include <ThingSpeak.h>
#include <WiFi.h>
#include <M5UnitKmeterISO.h>
// Wifi設定(Wifi環境に合わせて変更)
const char ssid[] = "your_SSID";
const char pass[] = "your_password";
// ネットワーク設定(固定IP用, ネットワーク環境に合わせて変更)
// IPAddress ip(192, 168, 100, 100);
// IPAddress gateway(192, 168, 100, 1);
// IPAddress subnet(255, 255, 255, 0);
// ThingSpeak設定(チャンネルIDと書き込み用APIキーを変更)
const unsigned long myChannelNumber = your_Ch_##;
const char* myWriteAPIKey = "your_API_Key";
// ThingSpeak接続用
WiFiClient client;
//KmeterISOユニットと通信
M5UnitKmeterISO kmeter;
void setup() {
AtomS3.begin(true); // Init M5AtomS3Lite.
AtomS3.dis.setBrightness(50);
// Serial.begin(115200);
//Unit KmeterISOの接続確認
while (!kmeter.begin(&Wire, KMETER_DEFAULT_ADDR, 2, 1, 100000L)) {
//Serial.println("Unit KmeterISO not found");
AtomS3.dis.drawpix(0XFFFFFF); // white
AtomS3.update();
}
AtomS3.dis.clear(); //LED消灯
// 温度データ取得
float temperature = (kmeter.getCelsiusTempValue() / 100.0) - 12.0; //測定値-12℃, 適宜変更
float int_temperature = kmeter.getInternalCelsiusTempValue() / 100.0;
//温度取得に失敗したら再起動
if (isnan(temperature)) {
ESP.restart();
}
//LED(-65C以上なら赤, 以下なら緑)
if (temperature < -65) {
AtomS3.dis.drawpix(0x00ff00); // green
AtomS3.update();
} else {
AtomS3.dis.drawpix(0xff0000); // red
AtomS3.update();
}
// ステーションモードに設定
WiFi.mode(WIFI_STA);
// IPアドレス設定(固定IP用)
// WiFi.config(ip, gateway, subnet);
// WiFi接続開始
int lpcnt = 0;
WiFi.disconnect(true, true);
delay(500);
WiFi.begin(ssid, pass);
while (WiFi.status() != WL_CONNECTED) {
AtomS3.dis.drawpix(0xFF00FF); // purple
delay(2 * 1000);
AtomS3.update();
AtomS3.dis.clear(); //LED消灯
delay(3 * 1000);
lpcnt += 1;
if (lpcnt > 40) {
ESP.restart();
}
}
// センサーデータアップロード開始
ThingSpeak.begin(client);
AtomS3.dis.drawpix(0x0000ff); // blue
AtomS3.update();
// フィールドに値を設定
ThingSpeak.setField(1, temperature);
ThingSpeak.setField(2, int_temperature);
// ThingSpeakに送信
//Serial.println();
//Serial.print("Write fields...");
int x = ThingSpeak.writeFields(myChannelNumber, myWriteAPIKey);
// Check the return code
if (x == 200) {
//Serial.println("done");
AtomS3.dis.clear(); //LED消灯
AtomS3.update(); //これ入れないとLED消えないでdeepsleepする
// 2.5 min のディープスリープタイマー設定(usec)
esp_sleep_enable_timer_wakeup(150 * 1000 * 1000);
// ディープスリープ
esp_deep_sleep_start();
}
else {
ESP.restart();
}
}
void loop() {
}
- 冷凍庫の表示温度と値がずれている場合には
float temperature = (kmeter.getCelsiusTempValue() / 100.0) - 12.0
の補正値の値を変更してプログラムを再度書き込みます。
追記2
M5 NanoC6対応のArduino core for the ESP32のstableバージョン3(3.0.0)がリリースされましたので、NanoC6版のスケッチも追記します。
(追記2版)使用したモジュール
(追記2版)開発環境
Arduino IDE v2.3.2
- 追加のボード:
- esp32 (v.3.0.0~) -> ESP32C6 Dev Module
https://espressif.github.io/arduino-esp32/package_esp32_index.json
- esp32 (v.3.0.0~) -> ESP32C6 Dev Module
- 追加ライブラリ:
- M5Unit-KMeterISOライブラリ
https://github.com/m5stack/M5Unit-KMeterISO - ThingSpeakライブラリ
https://github.com/mathworks/thingspeak-arduino
- M5Unit-KMeterISOライブラリ
(追記2版)プログラム
- 下記、NanoC6_KmeterISO_ThingSpeak.inoの
WiFi設定部分
const char ssid[] = "your_SSID";
const char pass[] = "your_password";
と
ThingSpeak設定部分
const unsigned long myChannelNumber = your_Ch_##;
const char * myWriteAPIKey = "your_API_Key";
を各自の環境に書き換えてNanoC6に書き込みます。
NanoC6_KmeterISO_ThingSpeak.ino
#include <ThingSpeak.h>
#include <WiFi.h>
#include <M5UnitKmeterISO.h>
#include <Wire.h>
// Wifi設定(Wifi環境に合わせて変更)
const char ssid[] = "your_SSID";
const char pass[] = "your_password";
// ネットワーク設定(固定IP用, ネットワーク環境に合わせて変更)
// IPAddress ip(192, 168, 100, 100);
// IPAddress gateway(192, 168, 100, 1);
// IPAddress subnet(255, 255, 255, 0);
// ThingSpeak設定(チャンネルIDと書き込み用APIキーを変更)
const unsigned long myChannelNumber = your_Ch_##;
const char* myWriteAPIKey = "your_API_Key";
// ThingSpeak接続用
WiFiClient client;
//KmeterISOユニットと通信
M5UnitKmeterISO kmeter;
#define RGB_BRIGHTNESS 32 // Change brightness (max 255)
void setup() {
// Serial.begin(115200);
pinMode(GPIO_NUM_19, OUTPUT); // EN(RGBPWR)
pinMode(GPIO_NUM_20, OUTPUT); // RGBLED
digitalWrite(GPIO_NUM_19, HIGH);
Wire.begin(2, 1);
while (!kmeter.begin(&Wire, KMETER_DEFAULT_ADDR, 2, 1, 100000L)) {
//Serial.println("Unit KmeterISO not found");
neopixelWrite(GPIO_NUM_20, RGB_BRIGHTNESS, RGB_BRIGHTNESS, RGB_BRIGHTNESS); // white
delay(100);
}
neopixelWrite(GPIO_NUM_20, 0, 0, 0); //LED消灯
delay(100);
// 温度データ取得
float temperature = (kmeter.getCelsiusTempValue() / 100.0) - 12.0;
float int_temperature = kmeter.getInternalCelsiusTempValue() / 100.0;
delay(100);
//温度取得に失敗したら再起動
if (isnan(temperature)) {
ESP.restart();
}
//LED制御
if (temperature < -65.0) {
neopixelWrite(GPIO_NUM_20, 0, RGB_BRIGHTNESS, 0); // green
delay(100);
} else {
neopixelWrite(GPIO_NUM_20, RGB_BRIGHTNESS, 0, 0); // red
delay(100);
}
// ステーションモードに設定
WiFi.mode(WIFI_STA);
// IPアドレス設定
// WiFi.config(ip, gateway, subnet);
// 接続開始
int lpcnt = 0;
WiFi.disconnect(true, true);
delay(500);
WiFi.begin(ssid, pass);
while (WiFi.status() != WL_CONNECTED) {
neopixelWrite(GPIO_NUM_20, RGB_BRIGHTNESS, 0, RGB_BRIGHTNESS); // purple
delay(1 * 1000);
neopixelWrite(GPIO_NUM_20, 0, 0, 0); //LED消灯
delay(1 * 1000);
lpcnt += 1;
if (lpcnt > 40) {
ESP.restart();
}
}
neopixelWrite(GPIO_NUM_20, 0, 0, 0); //LED消灯
delay(100);
// センサーデータアップロード開始
ThingSpeak.begin(client);
neopixelWrite(GPIO_NUM_20, 0, 0, RGB_BRIGHTNESS); // blue
delay(100);
// フィードに値を設定
//ThingSpeak.setField(1, String(temperature, 2)); // 小数点以下2桁まで
//ThingSpeak.setField(2, String(int_temperature, 2)); // 小数点以下2桁まで
ThingSpeak.setField(1, temperature);
ThingSpeak.setField(2, int_temperature);
// ThingSpeakに送信
//Serial.println();
//Serial.print("Write fields...");
int x = ThingSpeak.writeFields(myChannelNumber, myWriteAPIKey);
// Check the return code
if (x == 200) {
//Serial.println("done");
neopixelWrite(GPIO_NUM_20, 0, 0, 0); //LED消灯
digitalWrite(GPIO_NUM_19, LOW);
delay(100);
// 2m50s のディープスリープタイマー設定(usec)
esp_sleep_enable_timer_wakeup(170 * 1000 * 1000);
// ディープスリープ
esp_deep_sleep_start();
}
else {
ESP.restart();
}
}
void loop() {
}
- 冷凍庫の表示温度と値がずれている場合には
float temperature = (kmeter.getCelsiusTempValue() / 100.0) - 12.0
の補正値の値を変更してプログラムを再度書き込みます。
投稿者の人気記事
-
SJK
さんが
2024/05/22
に
編集
をしました。
(メッセージ: 初版)
-
SJK
さんが
2024/05/22
に
編集
をしました。
-
SJK
さんが
2024/05/24
に
編集
をしました。
-
SJK
さんが
2024/05/24
に
編集
をしました。
-
SJK
さんが
2024/05/24
に
編集
をしました。
-
SJK
さんが
2024/06/05
に
編集
をしました。
(メッセージ: M5 NanoC6版スケッチ追加)
ログインしてコメントを投稿する