とある日、どこかで聞こえた課題
「IoTでセンシングとかやりたいんだけど、やりたい場所に電源とか無いんだよね〜」
「ソーラーパネルつけてもいいけど、結構大がかりだよね〜」
「車の12Vバッテリーでどのくらい長持ちするんだろう…」
そう、IoTの課題、それは電源
デバイスは用意できた
ネットワークもLTEで無線接続できた
クラウドも準備できた
しかし!デバイスに電源がないとデータは上がってこない。
電源はIoTの重要な要素。
そんな電源問題を解決するためには?
たぶん、2つのアプローチがある。
-
とにかく電池を大量に用意すればいいんだよ派
- 12Vバッテリーを超並列にして半年は持たせるんだよ、的なアプローチ
- こまけぇことはいいんだよ、とにかく並列で並べればいいんだよ
-
とにかく電源を消費しないようにすればいいんだよ派
- ディープスリープを駆使して低消費電力で頑張ろうぜ、的なアプローチ
- 技術あるなら使うのが技術者だろ、コードでなんとかするんだよ
でもそんなことできないよ…
技術も無ぇ、ソーラーも無ぇ、電池もそれほど繋いでねぇ
置き場もねぇ、近くねぇ、運用毎日ぐーるぐる…
オラこんなIoTいやだ!!!
そんな迷える子羊に救世主が
Seeedさんより待望のWio Extension - RTCが発売された!
(SeeedさんHPより引用)
こいつはすげぇ、何がすごいかって、コードに下記の数行を追加するだけで
#include "WioRTC.h"
WioRTC RTC;
void loop(){
RTC.SetWakeupPeriod(60);
RTC.Shutdown();
while(1){}
}
処理したら電源オフ、の省電力化が実現できる!!!
これで電池をたくさん用意しなくても、難しい技術を使わなくても
簡単にWio LTEを省電力運用できるようになる!!
で、どういう仕組みなの?
簡単に言えば、Wio LTEに外付けする目覚ましタイマー。下記を繰り返す。
- Wio LTEが「俺を1分後に起こしてくれ」「俺を寝かせて」と言う
- RTCはその依頼を受けて、Wio LTEへの電源供給を止める
- RTCは超低消費電力(<1uA!)で、時間をカウントする
- RTCが時間になったら「Wio LTE!起きて!」と電源供給を開始する
どう使えばいいの?
GitHub - Seeed-Studio/Wio_Extension_RTC にある通り
- Arduino IDEで「Preferences > 追加のボードマネージャのURL: 」に貼り付け http://www.seeed.co.jp/package_SeeedJP_index.json
- 「ツール > ボード > ボードマネージャ」で「wio」を入れて「Seeed STM32F4 Board(JP mirror) by Seeed K.K.」の最新版をインストール
- 「ツール > ライブラリを管理」で「wio」を入れて「Wio LTE for Arduino by Seeed K.K.」の最新版をインストール
- サンプルコードをダウンロードし「wiortc-sample.ino」を開き、書き込む
※D38にブザーかRed LEDの、デジタル出力に対応したセンサーを繋いでおく
すぐ用意できなければ、内蔵LEDで代替するため、下記のコードに書き換える
SerialUSB.println("Beep.");
pinMode(WIO_D38, OUTPUT);
//digitalWrite(WIO_D38, HIGH);
Wio.LedSetRGB(255, 0, 0);
delay(200);
//digitalWrite(WIO_D38, LOW);
Wio.LedSetRGB(0, 0, 0);
※EEPROMへの読み書きが不要なら、loop()内の下記コードをコメントアウトしておく
//uint8_t val;
//RTC.EepromRead(0, &val, sizeof(val));
//SerialUSB.print("EEPROM value is ");
//SerialUSB.println(val);
//val++;
//RTC.EepromWrite(0, &val, sizeof(val));
- 電池などを接続し、RTCのスイッチを「B」側にして、テスト
※モバイルバッテリーなどで自動電源オフ機能がある場合、RTCの1uAを検出できずに電源がオフになってしまう場合があるため、そのような機能のないバッテリーを使用する(なので、乾電池で作ったのもある)
作ってみたよ
Wio LTEから一定間隔で、温湿度センサーの値をSORACOM Harvestにアップロードするサンプルを作りました。(と言ってもリファレンスからの引用がほとんどですが)
間隔はSORACOMのメタデータサービスから取得するため、情報を細かく取りたいときはSORACOMコンソールから変更できます。
http://metadata.soracom.io/v1/subscriber.tags.conf の値は下記の通り
(SIMの設定の「タグ」から変更できます)
名前 | 値 |
---|---|
conf | {"boot_interval_sec":30, "receive_timeout_sec":10} |
サンプルコード
#include <WioLTEforArduino.h>
#include <ArduinoJson.h>
#include <stdio.h>
#include "WioRTC.h"
#include "DHT.h"
////////////////////////////////////////////////////////////////////////////////
// Defines
#define METADATA_URL "http://metadata.soracom.io/v1/subscriber.tags.conf"
#define ENDPOINT_URL "uni.soracom.io"
#define ENDPOINT_PORT (23080)
#define DHTPIN (WIOLTE_D38) // what pin we're connected to
//#define DHTTYPE DHT11 // DHT 11 (blue)
#define DHTTYPE DHT22 // DHT 22 (AM2302:white)
////////////////////////////////////////////////////////////////////////////////
// Global variables
WioLTE Wio;
WioRTC Rtc;
DHT dht(DHTPIN, DHTTYPE);
//default sec(This value is overwritten by SORACOM metadata)
int boot_interval_sec = 60; // [sec.]
int receive_timeout_sec = 10; // [sec.]
//for metadata
char metadata[2048];
////////////////////////////////////////////////////////////////////////////////
// setup and loop
void setup()
{
delay(200);
SerialUSB.begin(115200);
SerialUSB.println("");
SerialUSB.println("--- START ---------------------------------------------------");
////////////////////////////////////////
// Low-level initialize
SerialUSB.println("### I/O Initialize.");
Wio.Init();
SerialUSB.println("### Power supply Grove ON.");
Wio.PowerSupplyGrove(true);
delay(500);
////////////////////////////////////////
// Device initialize
SerialUSB.println("### Device initialize.");
Wire.begin();
Rtc.begin();
dht.begin();
////////////////////////////////////////
// LTE modem initialize
SerialUSB.println("### Power supply LTE ON.");
Wio.PowerSupplyLTE(true);
delay(500);
SerialUSB.print("### Turn on or reset.");
if (!Wio.TurnOnOrReset())
{
SerialUSB.println(" -> NG");
return;
}
else
{
SerialUSB.println(" -> OK");
}
////////////////////////////////////////
// Connect to SORACOM
SerialUSB.print("### Connecting to \"soracom.io\".");
if (!Wio.Activate("soracom.io", "sora", "sora"))
{
SerialUSB.println(" -> NG");
return;
}
else
{
SerialUSB.println(" -> OK");
}
////////////////////////////////////////
// Get metadata(interval) from SORACOM
SerialUSB.print("### Get Interval.");
if (Wio.HttpGet(METADATA_URL, metadata, sizeof(metadata)) < 0)
{
SerialUSB.println(" -> NG");
return;
}
else
{
SerialUSB.println(" -> OK");
SerialUSB.print("### Metadata:");
SerialUSB.print(metadata);
SerialUSB.println("");
}
////////////////////////////////////////
// Parse metadata
StaticJsonBuffer<2048> jsonBuffer;
JsonObject &root = jsonBuffer.parseObject(metadata);
SerialUSB.print("### Json Parse.");
if (!root.success())
{
SerialUSB.println(" -> NG");
return;
}
else
{
SerialUSB.println(" -> OK");
boot_interval_sec = atoi(root["boot_interval_sec"]);
receive_timeout_sec = atoi(root["receive_timeout_sec"]);
SerialUSB.print("boot_interval_sec:");
SerialUSB.println(boot_interval_sec);
SerialUSB.print("receive_timeout_sec:");
SerialUSB.println(receive_timeout_sec);
}
////////////////////////////////////////
// Completed
SerialUSB.println("--- COMPLETE ------------------------------------------------");
}
void loop()
{
////////////////////////////////////////
// EEPROM R/W
//uint8_t val;
//Rtc.EepromRead(0, &val, sizeof(val));
//SerialUSB.print("EEPROM value is ");
//SerialUSB.println(val);
//val++;
//Rtc.EepromWrite(0, &val, sizeof(val));
////////////////////////////////////////
// Upload to SORACOM
SerialUSB.println("Upload to soracom.");
uploadToSoracom();
////////////////////////////////////////
// Set to RTC
SerialUSB.print("Shutdown:start in ");
SerialUSB.print(boot_interval_sec);
SerialUSB.println(" seconds.");
Rtc.SetWakeupPeriod(boot_interval_sec);
Rtc.Shutdown();
while (1) {}
}
void uploadToSoracom(){
char data[100];
float temp = dht.readTemperature();
float humi = dht.readHumidity();
if (isnan(temp) || isnan(humi)){
Serial.println("Failed to read from DHT");
temp=0.0;
humi=0.0;
}
////////////////////////////////////////
// Socket Open
SerialUSB.print("### Socket Open.");
int connectId;
connectId = Wio.SocketOpen(ENDPOINT_URL, ENDPOINT_PORT, WIOLTE_UDP);
if (connectId < 0) {
SerialUSB.println(" -> NG");
SerialUSB.println("### Connection error.");
}else{
SerialUSB.println(" -> OK");
}
////////////////////////////////////////
// Make data
SerialUSB.print("json value:");
sprintf(data, "{\"temp\":%f,\"humi\":%f}", temp, humi);
SerialUSB.print(data);
SerialUSB.println("");
////////////////////////////////////////
// Send to SORACOM
SerialUSB.print("### Send to SORACOM.");
if (!Wio.SocketSend(connectId, data)) {
SerialUSB.println(" -> NG");
goto err_close;
}else{
SerialUSB.println(" -> OK");
}
SerialUSB.print("### Receive.");
int length;
length = Wio.SocketReceive(connectId, data, sizeof (data), receive_timeout_sec*1000);
if (length < 0) {
SerialUSB.println(" -> NG (can't connect)");
goto err_close;
}
if (length == 0) {
SerialUSB.println(" -> NG (receive timeout)");
goto err_close;
}else{
SerialUSB.println(" -> OK");
SerialUSB.print("Receive:");
SerialUSB.print(data);
SerialUSB.println("");
}
err_close:
SerialUSB.print("### Close.");
if (!Wio.SocketClose(connectId)) {
SerialUSB.println(" -> NG");
SerialUSB.println("### Connection error.");
}else{
SerialUSB.println(" -> OK");
}
}
実際に動かしてみたよ
Maker Faire Tokyoで東京ビックサイトの温湿度を30秒間隔でアップロードしてみたら、2日間しっかり動いてくれました。
東京ビックサイトの人の混雑を温湿度の変化で可視化できるかなと思ったのですが、実際にやってみると、空調管理がしっかりできていて変化は捕らえられませんでした。
その代わり、CO2濃度を測られていた方(@zoe6120)がいて、そちらはバッチリ変化していました。
なお、昨日8/3は2200周。がんばりました #IoT鉄道模型 CO2は最大1850ppm。開始時の760ppmから来場者と共に急上昇。 EnOceanのCO2温湿度センサー使用。 pic.twitter.com/DHryiUetSl
— Tomohisa Yamazoe (@zoe6120) August 4, 2019
まとめ
Wio LTEは、Wio Extension - RTCを活用することで、簡単に低消費電力で運用できる!
IoTはやってみないと分からない!
やってみることで見える課題がある!
Just Do IoT!