知的好奇心 for IoT

IoT関連の知的好奇心を探求するブログです

ESP8266とDHT11とBlynkでスマホとWebにリアルタイムで温度と湿度を表示する

デザインフェスタでスマートミラーの展示だけで何も売り物がないのも寂しかったので、講座用に入手していたESP8266ボードとDHT11シールドを使ってなるべく一般向けの売り物を作ってみることにしました。

f:id:IntellectualCuriosity:20170810200644j:plain

部屋の壁に設置したWiFi温度・湿度計

 

Blynk

BlynkはiOSAndroidに対応したアプリで、ESP8266やRaspberry PiのIOをコントロールするパーツをアプリ上で組むことができるとっても便利なツールです。しかもデザインがとってもカッコイイんです。

f:id:IntellectualCuriosity:20170810202548p:plain

Blynkアプリで表示した温度と湿度

BlynkをESP8266で使うには「Blynk library」をサイトからダウンロードしてArduino IDEにコピー後、サンプルスケッチを参考にしてスケッチを書きます。

ESP8266のデータはBlynkのクラウドサーバー経由でBlynkアプリに渡されます。Blynkサーバープログラムはgithubで公開されているので、自分でBlynkサーバーを建ててセキュアな運用をすることもできます。

また、BlinkサーバーはHTTP RESTFUL APIも持っているので、Blynk libraryを組み込んだ機器やアプリ以外からでもデータのやり取りをすることができます。

なんてクールなんだ!

 

WiFi温度・湿度計

f:id:IntellectualCuriosity:20170811002112j:plain

高くなったため壁から本棚の横に場所を変えたWiFi温度・湿度計

壁に貼り付けてしばらく経ったころ、ふと温度が高くないか?と疑問に思って扇いでみると...なんと温度が下がるじゃないですか!

前回Milkcocoaを使ってベランダの温度と湿度を1時間に1回測る場合は問題にならなかったのですが、今回はずっと動かしっぱなしの状態だったため、ESP8266チップが発熱してその熱がセンサーまで伝わっていたんです!どうしたらいいんだろうと少し思案して出した答えが上の写真です。DHT11シールドに付けたのと同じピンソケットを間にかませてみました。

告知デザインフェスタWiFi温度・湿度計キットをお買い上げのお客様。ピンソケットをお送りしますのでhirothecat☆gmail.com(☆を@に置き換えてください)にお知らせください!

 

プログラムの工夫点

一般向けにする必要があったため、WiFi設定に必要なSSIDやパスワード、BlynkのAuth TokenをArduinoソースを書き換えてArduino IDEでESP8266ボードに転送するなんて事は現実的では無かったのです。

そこで、ESP8266の奥義であるWiFiアクセスポイント・WebServer・EEPROMを使って、プログラムの書き換えを伴わずにSSID・パスワード・Auth Tokenを設定できるようにジャンパーワイヤー接続の有無でプログラムを設定モードとセンサーモードの2つのモードで動かせるようにしました。

プログラムの動作モードの切り替え

プログラムの動作モードの切り替えは動作ログを後で確認できるようにするで既に行ったことがあります。今回はファンクションボタンの無いWeMos D1 miniを使っているということもあって、起動時にD1(GPIO5)ピンがLOWの時すなわちD1(GPIO5)とGNDをジャンパーケーブルで繋いでいるとプログラムが設定モードで動くようにしました。

f:id:IntellectualCuriosity:20170814040458p:plain

設定モード画面

設定モード画面にアクセスするには「WiFiTempHum」というSSIDWiFi接続して、ブラウザで「192.168.1.1」にアクセスするか、Androidで「Wi-Fiネットワークにログイン」通知をタッチするか、ブラウザに自動で表示されるかします。

WiFiアクセスポイント

ESP8266はWiFiのクライアントだけでなく、自らがアクセスポイントにもなることができるんです!

WebServer

ESP8266は簡単にWebServerを動かすことができます。HTMLを外部ファイル化して読み込むこともできますが、今回はプログラムの中にHTMLを記述して出力するようにしています。

EEPROM

EEPROMは電源を切っても消えないメモリーです。EEPROMを使って設定モードでSSID・パスワード・Auth TokenをEEPROMに書き込んで、センサーモードで読み込むようにすることでハードコードしなくてもいいようにしています。

 

Blynk設定

プログラムの前にBlynkの設定から始めましょう。

まずはBlynkのホームページにあるリンクからBlynkアプリをダウンロードします。

次にBlynkアプリを実行してアカウントを作成します。

f:id:IntellectualCuriosity:20170814031336p:plain

アカウントを作成したら「New Project」をタッチしてプロジェクトを作成します。左の画面で「Create」をタッチするとそのプロジェクトのAuth Tokenがメールで送られます。右の画面はデザインフェスタ用から少しバージョンアップした完成画面です。

f:id:IntellectualCuriosity:20170814031928p:plain

Widget配置画面(方眼紙のような画面)をタッチするとWidget Boxが表示されます。温度用のGauege、湿度用のGuage、温度と湿度のHistory Graphを配置してください。2,000Energy分はアプリのインストールボーナスとしてもらえるので、その範囲内であれば課金なしで使えます。

f:id:IntellectualCuriosity:20170814032803p:plain

f:id:IntellectualCuriosity:20170814033330p:plain

 

Arduinoプログラム

Arduinoプログラムは3ファイルに分けました。それぞれの役割はこんな感じです。

  • DHT11Blynk.ino --- メイン、センサーモード
  • eeprom.ino --- EEPROM書き込み・読み込み
  • server.ino --- 設定モード

DHT11Blynk.ino ※2017/9/2プログラム更新

Blynkに用意されているBlynk Examples Sketch BuilderでDHT11のExampleを表示させて、そのコードに必要な変更を加えました。

/*
 * DHT11Bylnk
 * September 2, 2017
 * By Hiroyuki ITO
 * http://intellectualcuriosity.hatenablog.com/  
 * MIT Licensed.
 */

/* Comment this out to disable prints and save space */
#define BLYNK_PRINT Serial

#include <esp8266wifi.h>
#include <blynksimpleesp8266.h>
#include <dht.h>

// EEPROM
char eep_auth[33];
char eep_ssid[33];
char eep_pass[33];

// RUN MODE
#define MODE_PIN 5        // WeMos D1 mini assined pin D1
#define MODE_SENSOR 0
#define MODE_SERVER 1
int run_mode;

// DHT
#define DHTPIN 2          // What digital pin we're connected to

// Uncomment whatever type you're using!
#define DHTTYPE DHT11     // DHT 11
//#define DHTTYPE DHT22   // DHT 22, AM2302, AM2321
//#define DHTTYPE DHT21   // DHT 21, AM2301

DHT dht(DHTPIN, DHTTYPE);
BlynkTimer timer;

/*
 * Send Sensor Data to Blynk
 */
void sendSensor()
{
  float h = dht.readHumidity();
  float t = dht.readTemperature(); // or dht.readTemperature(true) for Fahrenheit

  if (isnan(h) || isnan(t)) {
    BLYNK_LOG("Failed to read from DHT sensor!");
    return;
  }

  // You can send any value at any time.
  // Please don't send more that 10 values per second.
  Blynk.virtualWrite(V5, (int)round(h));
  Blynk.virtualWrite(V6, (int)round(t));
  BLYNK_LOG("Temperature:%d'C Humidity:%d%%", (int)t, (int)h);
}

/*
 * Main Setup
 */
void setup()
{
  delay(500);
  Serial.begin(74880);
  BLYNK_LOG2("Reset Reason: ", ESP.getResetReason());

  // EEPROM and Server Mode
  pinMode(MODE_PIN, INPUT_PULLUP);
  ReadEEPROM();
  BLYNK_LOG2("Auth:", eep_auth);
  BLYNK_LOG2("SSID:", eep_ssid);
  BLYNK_LOG2("Pass:", eep_pass);
  // Not set EEPROM or Set jumper wire
  if (eep_ssid[0] == 0 || (digitalRead(MODE_PIN) == LOW)) {
    // Server mode
    setup_server();
    run_mode = MODE_SERVER;
    return;
  }

  // Sensor mode
  BLYNK_LOG("*** Sensor mode ***");
  Blynk.begin(eep_auth, eep_ssid, eep_pass);

  dht.begin();

  // Setup a function to be called every 2 seconds
  timer.setInterval(2000L, sendSensor);

  run_mode = MODE_SENSOR;
}

/*
 * Main Loop
 */
void loop()
{
  if (run_mode == MODE_SERVER) {
    loop_server();
  } else {
    Blynk.run();
    timer.run();
  }
}

eeprom.ino ※2017/9/2プログラム更新

/*
 * EEPROM
 * September 2, 2017
 * By Hiroyuki ITO
 * http://intellectualcuriosity.hatenablog.com/  
 * MIT Licensed.
 */

#include <EEPROM.h>

/*
 * Read EEPROM
 */
void ReadEEPROM() {
  EEPROM.begin(sizeof(eep_auth) + sizeof(eep_ssid) + sizeof(eep_pass));

  // read eep_auth
  for (int i=0; i<sizeof(eep_auth); i++) {
    eep_auth[i] = EEPROM.read(i); 
  }
  eep_auth[sizeof(eep_auth)-1] = 0;

  // read eep_ssid
  for (int i=0; i<sizeof(eep_ssid); i++) {
    eep_ssid[i] = EEPROM.read(i + sizeof(eep_auth)); 
  }
  eep_ssid[sizeof(eep_ssid)-1] = 0;

  // read eep_pass
  for (int i=0; i<sizeof(eep_pass); i++) {
    eep_pass[i] = EEPROM.read(i + sizeof(eep_auth) + sizeof(eep_ssid)); 
  }
  eep_pass[sizeof(eep_pass)-1] = 0;

  EEPROM.end();
}

/*
 * Write EEPROM
 */
void WriteEEPROM() {
  EEPROM.begin(sizeof(eep_auth) + sizeof(eep_ssid) + sizeof(eep_pass));

  // write eep_auth
  for (int i=0; i<sizeof(eep_auth); i++) {
    EEPROM.write(i, eep_auth[i]);
  }

  // write eep_ssid
  for (int i=0; i<sizeof(eep_ssid); i++) {
    EEPROM.write(i + sizeof(eep_auth), eep_ssid[i]);
  }

  // write eep_pass
  for (int i=0; i<sizeof(eep_pass); i++) {
    EEPROM.write(i + sizeof(eep_auth) + sizeof(eep_ssid), eep_pass[i]);
  }

  EEPROM.end();
}

server.ino ※2017/9/2プログラム更新

/*
 * Server
 * September 2, 2017
 * By Hiroyuki ITO
 * http://intellectualcuriosity.hatenablog.com/  
 * MIT Licensed.
 */

#include <esp8266wifi.h>
#include <esp8266webserver.h>
#include <dnsserver.h>
#include <stdlib.h>

#define AP_SSID "WiFiTempHum"
const IPAddress AP_IP(192, 168, 1, 1);
DNSServer dnsServer;
ESP8266WebServer webServer(80);

/*
 * Setup Server
 */
void setup_server() {
  BLYNK_LOG("*** Server mode ***");
  
  // Setting webServer
  webServer.begin();
  webServer.on("/", HTTP_GET, config_page);
  webServer.on("/response_page", HTTP_POST, response_page);
  webServer.on("/response_page", HTTP_GET, response_page);
  webServer.onNotFound([]() {
    webServer.sendHeader("Location", "/", true);
    webServer.send(302, "text/plain", "");
  });
  
  // Setting  WiFi
  WiFi.mode(WIFI_AP);
  WiFi.softAPConfig(AP_IP, AP_IP, IPAddress(255, 255, 255, 0));
  WiFi.softAP(AP_SSID);
  dnsServer.start(53, "*", AP_IP);
}

/*
 * Server Loop
 */
void loop_server() {
  dnsServer.processNextRequest();
  webServer.handleClient();
}

/*
 * Send Configuration Page
 */
void config_page() {
  webServer.send(200, "text/html",
    String("<!DOCTYPE html><html><head>") +
    "<meta name='viewport' content='width=device-width, initial-scale=1'>" +
    "<title>WiFi Thermo-hygrometer Configuration</title></head><body>" +
    "<h2>WiFi Thermo-hygrometer Configuration</h2>" +
    "<hr>" +
    "<form action='/response_page' method='post'>" +
    "<h3>[Configuration]</h3>" +
    "Blynk Auth Token: <input type='text' name='Auth' value='" + String(eep_auth) + "' size='32'><br />" +
    "WiFi SSID : <input type='text' name='SSID' value='" + String(eep_ssid) + "' size='32'><br />" +
    "WiFi Pass : <input type='text' name='Pass' value='" + String(eep_pass) + "' size='32'><br />" +
    "<input type='submit' value='Set'>" +
    "</form>" +
    "<form action='/response_page' method='post'>" +
    "<hr>" +
    "<h3>[Reset]</h3>" +
    "<input type='submit' name='RESET' value='Reset'>" +
    "</form>" +
    "<hr>" +
    "</body></html>");
}

/*
 * Send Response Page
 */
void response_page() {
  // Update EEPROM
  if (webServer.hasArg("RESET")) {
    eep_auth[0] = 0;
    eep_ssid[0] = 0;
    eep_pass[0] = 0;
  } else {
    String st  = webServer.arg("Auth");
    for (int i=0; i < sizeof(eep_auth); i++) {
      eep_auth[i] = st.charAt(i);
    }
    st  = webServer.arg("SSID");
    for (int i=0; i < sizeof(eep_ssid); i++) {
      eep_ssid[i] = st.charAt(i);
    }
    st  = webServer.arg("Pass");
    for (int i=0; i < sizeof(eep_pass); i++) {
      eep_pass[i] = st.charAt(i);
    }
  }
  WriteEEPROM();

  // Send HTML Response
  webServer.send(200, "text/html",
    String("<!DOCTYPE html><html><head>") +
    "<meta name='viewport' content='width=device-width, initial-scale=1'>" +
    "<title>WiFi Thermo-hygrometer Configuration</title></head><body>" +
    "<h2>Configuration complete.</h2>" +
    "<p>device will be connected to \"" + String(eep_ssid) + "\" after the restart." +
    "</body></html>");

  // Restart ESP8266
  ESP.restart();
}

 

Blynk HTTP RESTful API

PCでこのブログにアクセスすると、サイドバーの「部屋の温度と湿度」からぼくの部屋に設置したWiFi温度・湿度計のデータを見ることができます。

Blynk HTTP RESTful APIの「Get pin value」を使うと、簡単にBlink libraryをインストールした機器のPINデータを取得できます。

ブラウザで次のようなURLにアクセスすると

http://blynk-cloud.com/{auth_token}/get/V6
({auth_token}を取得したものに置き換えてくださいね)

ブラウザに次のように温度が表示されます。

["28"]

サイドバーの「部屋の温度と湿度」では、機器のステータス(オンライン/オフライン)・温度・湿度をJavaScriptAjaxを使って2秒毎に更新して表示するようにしました。

部屋の温度と湿度のソース

<div id="blynk_status">ステータス取得中...</div>
<div id="blynk_temperature">温度取得中...</div>
<div id="blynk_humidity">湿度取得中...</div>

<script type="text/javascript">// <![CDATA[
setInterval(getDHT11byBlynk,2000);

function getDHT11byBlynk() {
  var authToken = "YourAuthToken";      // Blynkの認証トークン
  var temperaturePin = "V6";            // 温度仮想ピン
  var humidityPin = "V5";               // 湿度仮想ピン

  // ステータス取得
  var xhrStatus = new XMLHttpRequest();
  xhrStatus.onreadystatechange = function() {
    if (this.readyState == 4) {
      var status = JSON.parse(this.responseText);
      if (status) {
          statusHTML = "<font color ='blue'>オンライン</font>";
      } else {
          statusHTML = "<font color ='red'>オフライン</font>";
      }
      document.getElementById("blynk_status").innerHTML = statusHTML;
    }
  };
  xhrStatus.open("GET", "http://blynk-cloud.com/" + authToken + "/isHardwareConnected", true);
  xhrStatus.send();

  // 温度取得
  var xhrTemp = new XMLHttpRequest();
  xhrTemp.onreadystatechange = function() {
    if (this.readyState == 4) {
      var temperature = JSON.parse(this.responseText);
      document.getElementById("blynk_temperature").innerHTML = "温度:" + temperature + "℃";
    }
  };
  xhrTemp.open("GET", "http://blynk-cloud.com/" + authToken + "/get/" + temperaturePin, true);
  xhrTemp.send();

  // 湿度取得
  var xhrHum = new XMLHttpRequest();
  xhrHum.onreadystatechange = function() {
    if (this.readyState == 4) {
      var humidity = JSON.parse(this.responseText);
      document.getElementById("blynk_humidity").innerHTML = "湿度:" + humidity + "%";
    }
  };
  xhrHum.open("GET", "http://blynk-cloud.com/" + authToken + "/get/" + humidityPin, true);
  xhrHum.send();
}

// ]]></script>

 

Arduino IDE

プログラムの書き込みはArduino IDEから行います。Arduino IDEに関しては結構記事が多いのでここでは簡単に。

環境設定

f:id:IntellectualCuriosity:20170815223920p:plain

追加のボードマネージャのURLに以下を記述します。

http://arduino.esp8266.com/stable/package_esp8266com_index.json

スケッチブックの保存場所に書かれているフォルダにBlynk libraryをコピーします。

ライブラリマネージャ

次の2つのライブラリを追加します。詳しい方法はぼくの以前の記事で。

  • Adafruit Unified Sensor
  • DHT sensor library

ボードの設定

ボードマネージャでesp8266をインストールします。

f:id:IntellectualCuriosity:20170817025031p:plain

その後、ボートに応じた設定をします。

WiFi温度・湿度計のボードはWeMos D1 miniなので左の設定。ESP-WROOM-02などは右の設定を使います。

f:id:IntellectualCuriosity:20170815233451p:plain

雑な説明ですが、Google先生が助けてくれます。健闘を祈る!

 

おわり