デザインフェスタでスマートミラーの展示だけで何も売り物がないのも寂しかったので、講座用に入手していたESP8266ボードとDHT11シールドを使ってなるべく一般向けの売り物を作ってみることにしました。
Blynk
BlynkはiOSとAndroidに対応したアプリで、ESP8266やRaspberry PiのIOをコントロールするパーツをアプリ上で組むことができるとっても便利なツールです。しかもデザインがとってもカッコイイんです。
Blynkアプリで表示した温度と湿度
BlynkをESP8266で使うには「Blynk library」をサイトからダウンロードしてArduino IDEにコピー後、サンプルスケッチを参考にしてスケッチを書きます。
ESP8266のデータはBlynkのクラウドサーバー経由でBlynkアプリに渡されます。Blynkサーバープログラムはgithubで公開されているので、自分でBlynkサーバーを建ててセキュアな運用をすることもできます。
また、BlinkサーバーはHTTP RESTFUL APIも持っているので、Blynk libraryを組み込んだ機器やアプリ以外からでもデータのやり取りをすることができます。
なんてクールなんだ!
WiFi温度・湿度計改
高くなったため壁から本棚の横に場所を変えた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をジャンパーケーブルで繋いでいるとプログラムが設定モードで動くようにしました。
設定モード画面
設定モード画面にアクセスするには「WiFiTempHum」というSSIDにWiFi接続して、ブラウザで「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アプリを実行してアカウントを作成します。
アカウントを作成したら「New Project」をタッチしてプロジェクトを作成します。左の画面で「Create」をタッチするとそのプロジェクトのAuth Tokenがメールで送られます。右の画面はデザインフェスタ用から少しバージョンアップした完成画面です。
Widget配置画面(方眼紙のような画面)をタッチするとWidget Boxが表示されます。温度用のGauege、湿度用のGuage、温度と湿度のHistory Graphを配置してください。2,000Energy分はアプリのインストールボーナスとしてもらえるので、その範囲内であれば課金なしで使えます。
Arduinoプログラム
Arduinoプログラムは3ファイルに分けました。それぞれの役割はこんな感じです。
- DHT11Blynk.ino --- メイン、センサーモード
- eeprom.ino --- EEPROM書き込み・読み込み
- server.ino --- 設定モード
DHT11Blynk.ino ※2017/10/1更新 ヘッダーファイル名が全て小文字になっていました
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/10/1更新 ヘッダーファイル名が全て小文字になっていました
/*
* 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"]
サイドバーの「部屋の温度と湿度」では、機器のステータス(オンライン/オフライン)・温度・湿度をJavaScriptとAjaxを使って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に関しては結構記事が多いのでここでは簡単に。
環境設定
追加のボードマネージャのURLに以下を記述します。
http://arduino.esp8266.com/stable/package_esp8266com_index.json
スケッチブックの保存場所に書かれているフォルダにBlynk libraryをコピーします。
ライブラリマネージャ
次の2つのライブラリを追加します。詳しい方法はぼくの以前の記事で。
- Adafruit Unified Sensor
- DHT sensor library
ボードの設定
ボードマネージャでesp8266をインストールします。
その後、ボートに応じた設定をします。
WiFi温度・湿度計のボードはWeMos D1 miniなので左の設定。ESP-WROOM-02などは右の設定を使います。
雑な説明ですが、Google先生が助けてくれます。健闘を祈る!
おわり