ESP-WROOM-02 を使った リモート温度・湿度・気圧計の製作

ESP-WROOM-02-1

今年はほんとに毎日暑い日が続きました。汗をかきながらWebを閲覧していると偶然ikesato氏の「ESP-WROOM-02 の Arduino 環境で I2C 制御」にたどりつきました。

初めて聞く名前なので早速検索をかけ調べると「技適OKな中華IoTモジュールを使いこなす」こちらのサイトに詳しい内容が書かれていました。

ESP-WROOM-02についてわかった事は

(1)ESP8266EXを搭載したWiFiモジュール。WiFi接続機能とCPU・フラッシュROMが一体になっていて日本の技適マークも取得済みである。3.3V動作。

(2)デフォルトではATコマンドベースの無線モジュールだがArduino IDEを使ってプログラムすることも出来る。

要はセンサー又は表示器と電源だけ用意すれば簡単に動作するWiFiに繋がるチップだということですね。以前私が実験したNRF24L01+と違い日本の法律に抵触せずに工作することができ、しかも単価が安い!私は無意識のうちに通販サイトの購入ボタンをポッチとしていました。

とりあえず2.54mmピッチに変換されている完成品、SWITCH SCIENCE社のESP-WROOM-02ピッチ変換済みモジュール《フル版》 1,080円を1個購入してしまいました。

センサーは手持ちにあった気圧センサー 秋月電子通商 SCP1000モジュール(SPI接続)と何処から購入したか分からなくなってしまった気温・湿度センサー SENSIRION SHT10を繋ぎます。電源は前回も使用した秋月電子製StepUP 3.3v DC/DCコンバータによる単三乾電池×2本による動作としました。

前回通りDebian自宅サーバにてHIGHCHARTS社製 非商用利用無料のHIGHSTOKライブラリを使用しwebから過去データを含め時系列に表示します。

結局、以前の記事の焼き直しです。オリジナリティが全く無い製作記事です。(わざわざご訪問いただき誠に申し訳ありません。<(_ _)> )

まずESP-WROOM-02のArduino IDEによる開発のセッティングですがこちらのサイト https://github.com/esp8266/Arduino に載っている通りに行います。

・Arduno Wevsite http://www.arduino.cc/en/main/software よりArduino 1.6.5をダウンロードしてインストール
・Arduino 1.6.5を起動して ファイル – 環境設定 と進みAdditional Board Manager URLs欄にhttp://arduino.esp8266.com/stable/package_esp8266com_index.json を入力
・再起動後 ツール – ボード – ボードマネージャー より下のほうのにあるesp8266 by ESP8266 Comunity をクリックしてインストール (※ 手順が抜けていました 10/25追記)
・ツール – ボード よりGeneric ESP2866 Moduleを選択

詳しくはこちらのサイト「技適済み格安高性能Wi-FiモジュールESP8266をArduinoIDEを使ってIoT開発する為の環境準備を10分でやる方法 」に書かれています。

スケッチの書き込みには秋月電子 FT232RL USBシリアル変換モジュール(AE-UM232R)を使用しました。全体の電圧が3.3VなのでVCCIOも3.3Vにします。

ここで注意すべきことは、AduinoIDEでスケッチを書き込んでしまうとデフォルトのATコマンドベースの動作はしなくなってしまうことです。元に戻すことは無いかもしれませんが、念のためにその方法を押えて置きましょう。「ねむいさんのぶろぐ」こちらに詳しく書かれています。

「ねむいさんのぶろぐ」に書かれていますが、スケッチを書き込む前にESP-WROOM-02本体のMACアドレスやSSIDなどをATコマンドを使い調べて置く方が良いと思います。AduinoIDEのシリアルモニタでボーレートを115200bps,改行コードをCRおよびLFに設定して通信します。

[ATコマンドの例]
AT+CIFSR
+CIFSR:APIP,"192.168.4.1"
+CIFSR:APMAC,"1a:fe:34:xx:xx:xx"
OK
AT+CWSAP?
+CWSAP:"ESP_xxxxxx","",1,0
OK
AT+GMR
AT version:0.25.0.0(Jun 24 2015 18:02:27)
SDK version:1.1.2
compile time:Jun 24 2015 18:15:29

ねむいさん謹製のESP-WROOM-02ピン配置表 https://dl.dropboxusercontent.com/u/34019941/images/2015/20150623/20150623_006.pngはとても役にたちました。ありがとうございました。(ただしGND PINのアサインがSWITCH SCIENCE社のブレークアウトボードでは違っていますので注意が必要です。)

回路は上記の「ESP-WROOM-02 の Arduino 環境で I2C 制御」を参考にさせていただきました。

回路図

回路図

SW1を押しながらSW2を押してリセットをかけ、SW2,SW1の順に離すとファームウエアの書き込みモードになるのでスケッチを書き込むことができます。

スケッチの書き込み

スケッチの書き込み

ESP-WROOM-02をArduino IDEで使うことが始めてでしたので、まずsht10センサーの値の読み込みが動作するか確認しました。こちらからダウンロードして解凍したたファイルSHTx1-masterをArduinoをインストールしたディレクトリ下にあるlibariesのなかにコピーしますとReadSHT1xValuesをスケッチの例から読み出すことができるようになります。

ここは良く分からなかったのですが、このようなパラメータで設定しました

良く分からなかったのですが、とりあえずこんな感じで

ArduinoIDE にセットすべきパラメータに分からない項目がありましたが、とりあえず図のようにして書き込みました。

今回はSHT10のdataPin がESP-WROOM-02の IO2に、同じくclockPinが IO4に繋がりますのでReadSHT1xValuesスケッチのその部分を変更します。
測定結果がシリアルモニタに表示されました。よかったです。

/**
* ReadSHT1xValues
*
* Read temperature and humidity values from an SHT1x-series (SHT10,
* SHT11, SHT15) sensor.
*
* Copyright 2009 Jonathan Oxer &lt;jon@oxer.com.au&gt;
* www.practicalarduino.com
*/


#include <SHT1x.h>

// Specify data and clock connections and instantiate SHT1x object
#define dataPin 2    //オリジナルから変更
#define clockPin 4   //オリジナルから変更
SHT1x sht1x(dataPin, clockPin);

void setup()
{
Serial.begin(38400); // Open serial connection to report values to host
Serial.println("Starting up");
}

void loop()
{
float temp_c;
float temp_f;
float humidity;

// Read values from the sensor
temp_c = sht1x.readTemperatureC();
temp_f = sht1x.readTemperatureF();
humidity = sht1x.readHumidity();

// Print the values to the serial port
Serial.print("Temperature: ");
Serial.print(temp_c, DEC);
Serial.print("C / ");
Serial.print(temp_f, DEC);
Serial.print("F. Humidity: ");
Serial.print(humidity);
Serial.println("%");

delay(2000);
}

SCP1000 気圧センサー部分も同様に
https://github.com/PaulStoffregen/Arduino-1.6.1-Teensyduino/blob/master/hardware/arduino/avr/libraries/SPI/examples/BarometricPressureSensor/BarometricPressureSensor.inoを参考にさせていただきました。

SPI通信のPIN結線ですが今回は

DRDY— NC (使用しない)
CSB — 5
MOSI — 13
MISO — 12
SCK — 14

と結線しています。スケッチ中の該当する値を下記の通り変更します。

//const int dataReadyPin = 6; //オリジナルから変更
const int chipSelectPin = 5;

dataReadyPinを使わないのでその部分を修正して変わりにディレイをいれています。必要な時間の長さが分からなかったのでとりあえず 1000mmSとしてしまいました。(お気付きになられたかと思いますが「とりあえず」を多用するのが私の特徴です、さらに大抵の場合その後、変化はありません。そう、予想通りです。)

上記のスケッチをまるまる使用させていただき、さらにArduino IDE のスケッチの例からはESP8266WiFi-WiFiClientを修正して目的の下記スケッチが完成いたしました。(ほとんど全て copy & pasteです。作者の皆様ありがとうございました。)

#include <SPI.h>
#include <SHT1x.h>
#include <ESP8266WiFi.h>

//SCP1000 Sensor's memory register addresses:
const int PRESSURE = 0x1F;      //3 most significant bits of pressure
const int PRESSURE_LSB = 0x20;  //16 least significant bits of pressure
const int TEMPERATURE = 0x21;   //16 bit temperature reading
const byte READ = 0b11111100;     // SCP1000's read command
const byte WRITE = 0b00000010;   // SCP1000's write command

//SCP1000 pins used for the connection with the sensor
// the other you need are controlled by the SPI library):
//const int dataReadyPin = 6;
const int chipSelectPin = 5;

// Specify data and clock connections and instantiate SHT1x object
#define dataPin  2
#define clockPin 4
SHT1x sht1x(dataPin, clockPin);  

// wifi
const char* ssid     = "エスエスid";
const char* password = "パスword";
 
const char* host = "自宅server IP-address";

void setup()
{

 delay(10);
 
  // We start by connecting to a WiFi network
 
  Serial.println();
  Serial.println();
  Serial.print("Connecting to ");
  Serial.println(ssid);
 
  WiFi.begin(ssid, password);
 
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }
 
  Serial.println("");
  Serial.println("WiFi connected");
  Serial.println("IP address: ");
  Serial.println(WiFi.localIP());

//SCP100 start the SPI library:
  SPI.begin();
// initalize the  data ready and chip select pins:
//  pinMode(dataReadyPin, INPUT);
  pinMode(chipSelectPin, OUTPUT);

  //Configure SCP1000 for low noise configuration:
  writeRegister(0x02, 0x2D);
  writeRegister(0x01, 0x03);
  writeRegister(0x03, 0x02);
  // give the sensor time to set up:
  delay(100);

   Serial.begin(115200); // Open serial connection to report values to host
   Serial.println("Starting up");
}

int value = 0;

void loop()
{
  delay(5000);
  ++value;

//SHT10
  float temp_c;
  float temp_f;
  float humidity;

  // Read values from the sensor
  temp_c = sht1x.readTemperatureC();
  temp_f = sht1x.readTemperatureF();
  humidity = sht1x.readHumidity();

  // Print the values to the serial port
  Serial.print("Temperature: ");
  Serial.print(temp_c, DEC);
  Serial.print("C / ");
  Serial.print(temp_f, DEC);
  Serial.print("F. Humidity: ");
  Serial.print(humidity);
  Serial.println("%");


//SCP1000

 //Select High Resolution Mode
  writeRegister(0x03, 0x0A);

    delay(1000);
    //Read the temperature data
    int tempData = readRegister(0x21, 2);

    // convert the temperature to celsius and display it:
    float realTemp = (float)tempData / 20.0;
    Serial.print("Temp[℃]=");
    Serial.print(realTemp);


    //Read the pressure data highest 3 bits:
    byte  pressure_data_high = readRegister(0x1F, 1);
    pressure_data_high &= 0b00000111; //you only needs bits 2 to 0

    //Read the pressure data lower 16 bits:
    unsigned int pressure_data_low = readRegister(0x20, 2);
    //combine the two parts into one 19-bit number:
    long pressure = ((pressure_data_high << 16) | pressure_data_low) / 4;

    // display the temperature:
    Serial.println("\tPressure [Pa]=" + String(pressure));
    
  // Use WiFiClient class to create TCP connections
  Serial.print("connecting to ");
  Serial.println(host);
 
  WiFiClient client;
  const int httpPort = 80;
  if (!client.connect(host, httpPort)) {
    Serial.println("connection failed");
    return;
  }
 
  // We now create a URI for the request
  String url = "/tempera/tempera_log.php";
         url = url + "?tempera=" +String( temp_c);
         url = url + "&humidi=" + String(humidity);
         url = url + "&puress=" + String(pressure);
  Serial.print("Requesting URL: ");
  Serial.println(url);
 
  // This will send the request to the server
  client.print(String("GET ") + url + " HTTP/1.1\r\n" +
               "Host: " + host + "\r\n" +
               "Connection: close\r\n\r\n");
  delay(10);
 
  // Read all the lines of the reply from server and print them to Serial
  while(client.available()){
    String line = client.readStringUntil('\r');
    Serial.print(line);
  }
 
  Serial.println();
  Serial.println("closing connection");

 //SCP1000 Select Powere Down Mode
  writeRegister(0x03, 0x00);
 //ESP8266 Deep Sleap mode
 //1:μ秒での復帰までのタイマー時間設定 (30分)  2:復帰するきっかけの設定(モード設定)
  ESP.deepSleep(30 * 60 * 1000 * 1000 , WAKE_RF_DEFAULT);  

  //deepsleepモード移行までのダミー命令
  delay(1000);
}

//Read from or write to register from the SCP1000:
unsigned int readRegister(byte thisRegister, int bytesToRead ) {
  byte inByte = 0;           // incoming byte from the SPI
  unsigned int result = 0;   // result to return
//  Serial.print(thisRegister, BIN);
//  Serial.print("\t");
  // SCP1000 expects the register name in the upper 6 bits
  // of the byte. So shift the bits left by two bits:
  thisRegister = thisRegister << 2;
  // now combine the address and the command into one byte
  byte dataToSend = thisRegister & READ;
//  Serial.println(thisRegister, BIN);
  // take the chip select low to select the device:
  digitalWrite(chipSelectPin, LOW);
  // send the device the register you want to read:
  SPI.transfer(dataToSend);
  // send a value of 0 to read the first byte returned:
  result = SPI.transfer(0x00);
  // decrement the number of bytes left to read:
  bytesToRead--;
  // if you still have another byte to read:
  if (bytesToRead > 0) {
    // shift the first byte left, then get the second byte:
    result = result << 8;
    inByte = SPI.transfer(0x00);
    // combine the byte you just got with the previous one:
    result = result | inByte;
    // decrement the number of bytes left to read:
    bytesToRead--;
  }
  // take the chip select high to de-select:
  digitalWrite(chipSelectPin, HIGH);
  // return the result:
  return(result);
}
//Sends a write command to SCP1000

void writeRegister(byte thisRegister, byte thisValue) {

  // SCP1000 expects the register address in the upper 6 bits
  // of the byte. So shift the bits left by two bits:
  thisRegister = thisRegister << 2;
  // now combine the register address and the command into one byte:
  byte dataToSend = thisRegister | WRITE;

  // take the chip select low to select the device:
  digitalWrite(chipSelectPin, LOW);

  SPI.transfer(dataToSend); //Send register location
  SPI.transfer(thisValue);  //Send value to record into register

  // take the chip select high to de-select:
  digitalWrite(chipSelectPin, HIGH);
}

約30分に1回自宅サーバにアクセスしてデータを渡します。ディープスリープモードに関しては

ESP8266の真骨頂Deep-Sleepモードの使い方こちらを参考にさせていただきました。ありがとうございました。

サーバーサイドのスクリプトはESP-WROOM-02からデータを受け取ってファイルに書き込むphpファイルとデータをグラフにして表示させるhtmlファイル2個です。当然ですがwebサーバが稼動していてPHPが使えなければなりません。

PHPファイルですが、 NRF24L01+ と DS18B20+と Arduinoでリモート温度計 3 で書かれている物を手直ししました。

tempera_log.php

<?php
//   ini_set( 'display_errors', 1 );
   $strGetTempera = time();
   $strGetTempera .= ",";
   $strGetTempera .= $_GET['tempera'];
   $strGetTempera .= ",";
   $strGetTempera .= $_GET['humidi'];
   $strGetTempera .= ",";
   $strGetTempera .= $_GET['puress'];
   $strGetTempera .= "\n";
   $strDataFilePath = '/ファイルのpath/atmosphere_log.csv';  //ログを記録するファイルの指定です
   $fp = fopen($strDataFilePath, "ab");
   fwrite($fp, $strGetTempera);
   fclose($fp);
?>

動作確認のためにブラウザから

http://サーバアドレス/パス/tempera_log.php?tempera=1234.567&humidi=12&puress=34
とアドレス欄に入力して atmosphere_log.csvが作成され、中身が下記の様になるか確かめます。

1439546219,1234.567,12,34 //左から アクセスした時のUniX時間,temperaの値,humidiの値,puressの値

グラフを表示させるhtmlファイルも前回作成したものをベースにしています。


<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "xhtml11.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title>Highstocks temperature-humidity-pressure</title>
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js" type="text/javascript"></script>
<script src="http://code.highcharts.com/stock/highstock.js"></script>
<script type="text/javascript"> 
var chart1; // globally available
$(document).ready(function() {
$.get('atmosphere_log.csv', function(data) {
    // Split the lines
        var lines = data.split('\n');
        var temperature = [];
        var humidity = [];
        var pressure = [];
        var js_time 
    // Iterate over the lines and add categories or series
    $.each(lines, function(lineNo, line) {
        var items = line.split(',');
	if(parseInt(items[0],10)){
         js_time = parseInt(items[0])*1000+ (9*60*60*1000) ;
	temperature.push([js_time,parseFloat(items[1])]);
	humidity.push([js_time,parseFloat(items[2])]);
	pressure.push([js_time,parseInt(items[3]/100)]);
        }
    });
    var seconds = (js_time /1000)%(24 * 60 * 60);
    var hour  =  Math.floor(seconds / (60 * 60));
    var minit =  Math.floor((seconds % (60 * 60))/60);
    var options = {
     title : {
       text : '[ ' + hour +'時'+ minit +'分 現在] ' + '気温: ' + Math.floor(temperature[temperature.length -1][1]*10)/10 + "℃ "
                       + '湿度: ' + Math.round(humidity[humidity.length -1][1]) + "% "
                     + '気圧: ' + pressure[pressure.length -1][1] + "hPa" ,
       style : {
            color: '#6Faa6F',
            fontSize: '20px'
            }
        },
     navigator: {
         baseSeries: 2
         },
     legend: {
         enabled: true,
         align: 'left',
         backgroundColor: '#EFFFC5',
         borderColor: 'black',
         borderWidth: 2,
        layout: 'vertical',
        verticalAlign: 'top',
        y: 100,
        shadow: true
        }, 
    chart: {
        renderTo: 'container' ,
        zoomType: 'x'
    },
    xAxis: {
        type: 'datetime',
        range:  24*3600 * 1000 // 24 hour
    },
     yAxis: [{
       title: {
         text: '湿度 (%)'
       },            
       opposite: true
    },{
       title: {
              text: '気圧 (hPa)'
              },
       offset : 10,
       opposite: true
    },{
       title: {
         text: '気温 (°C)'
       },
      opposite: false
	}],
    series:[{
             yAxis: 0,
	     data: humidity,
         name: '湿度',
         type: 'area',
	     color: '#ccccff',
             tooltip: {
                    valueDecimals: 1,
                    valueSuffix: '%'
                      }
           },{
             yAxis: 1,
	     data: pressure,
         name: '気圧',
         type: 'spline',
	     color: '#000000',
         tooltip: {
                    valueDecimals: 1,
                    valueSuffix: 'hPa'
                      },
         dashStyle: 'shortdot',
           },{
             yAxis: 2,
	         data: temperature,
             name: '気温',
             color: '#00bbff',
             lineWidth:3,
	         tooltip: {
                    valueDecimals: 1,
                    valueSuffix: '°C'
                   }
	      }]
};
    var chart1 = new Highcharts.StockChart(options);
});
});
</script>
</head>
<body>
<div id="container" style="width: 100%; height: 400px"></div>
</body>
</html>

いままで大気圧などは気にしたことがなかったのですが、今回の件で少しだけ学習しました。大気圧は海抜が低い所では標高差8mで1hPa変化するそうです。私の住んでいるところは東京都下ですが標高約200m弱のところにあります。その割合で計算すると確かに千代田区にある気象庁の発表する値と大体一致します。また、しばし観測した結果、同じ場所では気圧の変動幅はあまり広くないことがわかりました。気圧に関しては大気の状態による変動よりも、標高差による変動がとても大きくなるようです。蛇足ですが血圧計の単位 mmHgはトリチェリの水銀柱由来だそうです。知りませんでした。

何ヶ所かのディレイの値を調整することとWiFiに接続を固定IPにすることにより消費電力をもう少し抑えることができると思います。今回はこの設定で百円ショップで購入した単三電池がどれだけ長持ちするか観察いたします。(2015/8/17より稼動)
※ 2015/10/14 追記
数日前に異常な湿度の値が記録されていたので怪しげに思っていたのですが、2015/10/13午前6時過ぎに電池が切れてしまったようです。上記の設定で単三乾電池2本により動作させ約57日間稼動しました。
※ 2016/04/03 追記
後日3端子レギュレターを追加して電池からAC電源に変更した際分ったのですが、ESP-WROOM-02は連続動作させると結構発熱します。ESP-WROOM-02のそばにセンサーがあると正確に温度を測れない可能性があります。この記事では、Sleepモードを使った間欠動作だったので気付きませんでした。ご注意ください。

こちらに拡大したグラフ

Highstocks temperature-humidity-pressure


 

ESP-WROOM-02 を使った リモート温度・湿度・気圧計の製作” への1件のコメント

  1. ピンバック: Check! IoT ができるようになるまで ~ ESP-WROOM-02 にプログラムを焼く | cloudpack.media

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です