Ⅶ. 完成品としての仕上げ(ESP-WROOM-02)

 ここまで5つのステップを通して計測・記録・サーバー機能と、ブラウザとのコミュニケーション・インターフェイスを検討してきました。
  ・espRTC.ino:NTPによる時刻自動補正とバッテリーバックアップを備えた時計の製作
  ・espI2C.ino:I2Cによる温湿度・気圧・照度センサーの組み込みと計測
  ・espSD.ino:SDカードの増設とテキストデータの入出力、およびアナログセンサーの増設
  ・espMeasure.ino:タイマー割り込みによる自動計測システム
  ・espWiFi.ino:Wi-Fi Web Serverの構築とコミュニケーション・インターフェイス
 今回はこれらの機能を統合して「所定の計測条件にしたがって計測を継続しながら、ブラウザからの要求にも応答する「AirMonitor」に仕上げます。また、MCUブレッドボードをユニバーサルプリント基板に組み付けて完成させます。

 ※スケッチのダウンロードは右側の[Download]ボタンをクリックしてください。    

1.今回の開発テーマ

(1)動作条件の設定・変更機能の追加

 AirMonitorの動作条件を弾力的に変更できるように、『Ⅴ. 定期自動計測とデータ記録』で検討した測定処理条件をSDカード上のファイル「AIRMON.PRM」に保持させることにします。必要に応じてその内容をメンテナンスできるように次の保守画面を新設します。

 保守画面を表示するために、コマンド入力画面に「mainte:動作条件を変更する」を追加します。

 コマンド入力画面の操作は前回説明したとおりですが、入力欄で「mainte」と入力して[実行]ボタンをクリックするか[Enter]キーをたたくと、新設した動作条件設定画面に切り替わります。条件設定画面ではSSIDやPasswordも変更できますが、入力内容が間違っていると次回の起動ができなくなるので注意が必要です。
 変更した内容はすぐにファイルに書き込まれますが、MCUボードのリセットスイッチを押した時点で有効になります。


(2)自動計測記録処理とサーバーの並行動作の実現

 AirMonitorは起動すると、システムクロックの「分」が「計測開始分」で設定した値になるまで待って計測を開始します。以後は設定された時間間隔ごとに測定して、測定値をSDカード上のファイルに記録します。さらに、指定された時間間隔でNTPサーバーと通信してシステムクロックの時刻合わせを行います。
 これらの自動化された処理と並行して、起動後はいつでもWebサーバーとして機能させる必要があります。計測中でも計測開始前でも、リアルタイム計測や動作条件の変更、記録されている計測データの検索表示ができなければなりません。ただし、動作条件変更後のシステムリセットについては、今回は手処理によることとします。

2.MCUボードの製作

 前回までブレッドボードに組み付けていたMCUの部分を、ユニバーサル基板(秋月電子通商、60円)に移行しました。ESP-WROOM-02はスイッチサイエンス社のコンパクトな「ピッチ変換済みモジュール《フル版》」を使用しました。執筆時点の価格は909円で、別途ヘッダーピンを購入して半田付けが必要です。

(1)回路図

 回路はいままでとほぼ同じですが、モード選択のスイッチをタクトスイッチから基板用スライドスイッチ(秋月電子通商、20円)に変更、LEDを3mmの小さいものに変更、MCUのRST(リセット)端子にプルアップ抵抗(10kΩ)を追加した点が異なっています。また、外部との配線用に6Pのピンソケット(秋月電子通商、20円)を追加しています。


(2)出来上がったMCUボード

 基板の四隅にスペーサーをビス止めして脚にしてみました。裏面の配線はかなりすさまじいですが、ご参考までに。
 左はPCに接続したMCUボードです。右は単独で計測中の姿です。スライドスイッチは、リセット用の赤色タクトスイッチ側にセットするとLEDが消灯して書き込みモードになり、反対にセットするとLEDが点灯して実行モードになります。RTCや計測センサー類も別の基板に組み込んで、電源部をコンパクトな3端子レギュレーターに置き換えれば、MCUボードと二層構造にしたAirMonitorになるのですが、今回はここまでとします。

3.ソフト開発のポイント

 基本的にはいままで個別に開発してきた機能を組み合わせるだけなのですが、処理開始の手順(スケッチのsetup部分の構成)や反復処理(loop部)での計測開始時の割込タイマー設定などはひと工夫が必要です。また、クライアント(ブラウザ)との通信を簡単にするために、サーバーのコールバック関数を的確に利用することが重要です。

(1)基幹部の処理

 最終的なスケッチのサイズは1,200行を超えますが、setup()とloop()で構成される基幹部の処理は、コメントや十分な空白行を含めて90行ほどのコンパクトなコードです。
 下図はそのフローチャートです。


 フローチャートを見ると、ブルーカラーで表示したまだ内容がわからない部分以外は、すでに説明済みなので容易に理解できると思います。「外部設定条件を読み込む(readParameterFile)」は文字通りSDカード上のファイルから設定条件を読み取る処理です。以下ではピンクのWebサーバー関連部分を再度確認し、初回設定処理(InitialProcess)を見ておきましょう。


(2)サーバー・コールバック関数

 server.onはクライアントからの要求を受けて実行させるメソッド、つまりサーバーのコールバック関数を指定するものです。「HTTPリクエストヘッダーが'GET'で通信してきたら、コマンド入力画面を表示するsendCommandScreen()を実行させる」、また「HTTPリクエストヘッダーが'GET'で通信してきたら、処理を切り分けるprocControl()メソッドを実行させる」ことを指定しています。
 loop内では、handleClient()でクライアントからの要求を待ち構えています。コマンド入力画面と動作条件設定画面は、それぞれHTML文のformタグでリクエストヘッダーに'POST'を指定しているので、要求があるとprocControlメソッドが呼び出されます。検索結果や計測結果、入力エラーなどの表示画面はリクエストヘッダーに'GET'を指定しているので、ボタンをクリックして要求を受けるとsendCommandScreenでコマンド入力画面が呼び出されます。
void setup() {
					:
					:
					:

    // Set server callback functions
    server.on("/", HTTP_GET, sendCommandScreen);
    server.on("/", HTTP_POST, procControl);
}

void loop() {
    server.handleClient();
}
 新たに登場したコールバック関数procControlのコードは次のようになっています。
 6行目のserver.arg("proc_kind")で、'POST'要求を発行した画面の種別を取得して、コマンド入力画面か動作条件設定画面かを判定しています。コマンド入力画面であれば即コマンド解析メソッドprocAnalyzeCommandを実行させます。動作条件設定画面であれば、さらに11行目のserver.arg("proc_mode")で[設定][中止]のいずれのボタンがクリックされたかを判別し、[設定]ならrewriteParameterFileメソッドで入力内容をパラメータファイルに書き出し、続いてコマンド入力画面を表示させています。
 このように呼び出し元の画面から'proc_kind'や'proc_mode'などの値を取得できるのは、それぞれの画面を表示するHTML文に仕掛けがあります。まず25行と45行に注目してください。どちらの画面もproc_kindという名前の非表示(type='hidden')タグを持ち、一方は'command'、もう一方は'mainte'という値を設定しています。これが6行目で取得されて、画面の種別判定に利用されているわけです。
 計測設定条件画面には、同様に名前proc_modeで'XYZ'という値をもった非表示タグが書かれています。この値は、43~44行目のボタンがクリックされた時に発生するonclickイベントを補足します。それぞれsetModeDo()またはsetModeCancel()を呼んで、proc_modeの値に'set'か'cancel'を設定します。具体的な設定は、39~40行のJavaScriptで行っています。先と同様に、11行目でこの値を取得してボタン種別を判定しています。
/*
 * Process controll.
 */
void procControl()
{
    String procKind = server.arg("proc_kind");
    if (procKind == "command") {
        procAnalyzeCommand();
    }
    else if (procKind == "mainte") {
        String sMode = server.arg("proc_mode");
        if (sMode == "set")
            rewriteParameterFile();
        procAnalyzeCommand();
    }
}

void sendCommandScreen()
{
				:
				:
	String strBuf = "<!DOCTYPE html><html>"
				:
				:
		"<input type='hidden' name='proc_kind' value='command' />"
				:
				:
		"</html>";
    server.send(200, "text/html", strBuf);
}

void sendMainteScreen()
{
				:
				:
	String strBuf = "<!DOCTYPE html><html>"
				:
				:
        "<script>function setModeDo() {document.getElementById('proc_mode').value='set';}"
        "function setModeCancel() {document.getElementById('proc_mode').value='cancel';}"</script>
				:
				:
		"<input type='submit' name='SUBMIT' value=' 設 定 ' onclick='setModeDo();' /> "
        "<input type='submit' value=' 中 止 ' onclick='setModeCancel();' />"
		"<input type='hidden' name='proc_kind' value='mainte' />"
		"<input type='hidden' name='proc_mode' id='proc_mode' value='XYZ' />"
				:
				:
		"</html>";
    server.send(200, "text/html", strBuf);
}


(3)初回設定処理(InitialProcess)

 計測開始時刻になると呼ばれるので、まず初回の計測を行います。続いてタイマー割り込みTickerの2つのインスタンスに対して、動作条件設定ファイルから取得した値を使って設定を行います。ひとつは計測始動メソッドkickRoutineWorkで、計測間隔ごとにタイマー割り込みを発生させるように指定しています。もうひとつは時刻合わせ始動メソッドのkickTimeAdjustで、時刻調整間隔ごとに実行するよう指定しています。
 kickRoutineWorkメソッドではdoMeasurementで計測を行い、editMeasuredResultで計測結果を編集させて、writeMeasurementResultで編集結果をログファイルに書き込んでいます。
 kickTimeAdjustは単にbReadyTickerをtrueに設定するだけです。この値が同じloop内でチェックされて、時刻合わせのメソッドadjustTimeが実行されます。
/*
 * Run only once at the beginning.
 */
void InitialProcess()
{
    // 1st measuement
    kickRoutineWork();

    // Timer interrupt time and event setting.
    ticker1.attach(iIntervalTime, kickRoutineWork);
    delay(1000);
    ticker2.attach(iNtpInterval, kickTimeAdjust);
}

/****************************< Interrupt handler >****************************/
/*
 * Timer interrupt event handler1
 *    
 */
void  kickRoutineWork()
{
    // Measurment & write file
    doMeasurement();
    String buf =editMeasuredResult(rstMeasured);
    writeMeasurementResult(LOG_FILE, buf);
    Serial.println(buf);
}

/*
 * Timer interrupt event handler2
 *    
 */
void kickTimeAdjust()
{
    bReadyTicker = true;
}

(4)全体の制御構造について

 すでにお分かりのように、基幹部以外のすべての処理は、基幹部または関連メソッドで設定する割り込みハンドラーとコールバック関数に委ねられています。クライアント(ブラウザ)とのやりとりはサーバーのコールバック関数が担当し、そのコールバック関数に書いたコードによって計測や記録、検索やコミュニケーションが進んで行きます。設定条件に従った定期的な計測はタイマーのコールバック関数がコントロールしています。システムクロックの時刻合わせも、タイマーのコールバック関数の役目です。
 MCU自体はいつもほとんど待ち状態であり、割り込みが発生した瞬間だけ必要な処理を行います。このように割り込み処理を利用すると複雑な手順制御が不要なので、スケッチのロジックを簡潔に組み立てることができます。また、割り込みが発生すると処理を一時中断してコールバック関数を実行するので、見かけ上マルチスレッドのような動作が可能になります。


4.連載の終わりにあたって

 『超小型格安チップ「ESP-WROOM-02(ESP8266)」はどこまで使えるか?』と題した7回の連載はこれで終了です。稚拙な内容ですが、長らくお付き合いいただきありがとうございました。これがきっかけになって、新たな取り組みへのご参考になれば嬉しいです。
 私事になりますが、山歩きで肉離れを起こしたのが原因で久々に向き合ったArduinoに始まり、ESP-WROOM-02 (ESP8266)にめぐり会っての3カ月ばかり、実に楽しい時間を過ごすことができました。最初は戸惑いながら購入したピッチ変換されただけのMCUでしたが、それぞれのピンの機能を確かめたり回路を考える上で、この選択は良かったと思っています。また今回組み立てたMCUボードは、この時期をこのように取り組んだ記念の品になりました。
 できれば今後も、このMCUボードを使っていろいろやってみたいのですが、「山は招いている」し、限られた時間でどこまで進めることができるかは不明です。なおいくつもの改良すべき事項を置き去りにしますが、限られた時間であれば、これにて「チョ~ン!」。


●スケッチ全景

 最後になりましたが、今までの流儀にしたがってスケッチのすべてを掲載します。前回同様、コードを利用する場合はこの画面をコピー&ペーストするのではなく、冒頭に配置しているボタンでダウンロードしてください。またコードを実行する際には、ssidとpasswordに環境に合った値を設定してください。当初は動作条件設定ファイルが作成されていないので、まずmainteで動作条件設定画面を表示して「NTPサーバー名」以降を設定します。MCUボードのスライドスイッチをLEDが点灯する側(実行モード)にセットして、リセットボタンを押すと実行開始です。
/*
 * File:      AirMonitor.ino   (Version 1.00)
 * Function:  Measure the temperature, humidity, atmospheric pressure, illuminance and soil
 *            moisture according to specified conditions and record on the SD card device.
 *            This acts as a Web server and responds to the following requests from the client.
 *              ・Answer the measurement result at the time of request immediately.
 *              ・Extract the measurement data recorded in the file from the designated position
 *                  by the specified number and distribute it on WiFi.
 *              ・Extract the measurement data recorded in the file by the specified date range
 *                  and distribute it on WiFi.
 *              ・Displays command usage.
 *            Those with fluctuating operating conditions depending on the environment can be
 *            described in an external file so that they can be changed.
 *            It has also automatic time adjustment function of RTC (DS1307) using NTP.
 *            To correct the date, key in into the serial monitor input box as the following
 *            format.
 *                  yymmdd or yymmddw or yy/mm/dd or yy/mm/dd/w
 * 
 * Hardware   MCU:  ESP-WROOM-02(ESP8266)
 *            RTC:  DS1307 I2C Real time clock with battery backup
 *            BME280: Combined temperature, humidity and pressure sensor
 *            BH1750: Digital illuminance sensor
 *            YL-38 &YL-69: Soil moisture sensor
 *            SD card device: Hirose DM3AT series
 *
 * Remarks:   Measurement and calibration codes of BME 280
 *            uses SWITCH SCIENCE's sample sketch "BME280_I2C.zip".
 *                 https://trac.switch-science.com/wiki/BME280
 * 
 * Date:      2017/02/10
 * Author:    Marchan  http://marchan.e5.valueserver.jp/cabin/comp/
 *                     Mail to: softlabo@nifty.com
 */
#include <Wire.h>
#include <SPI.h>
#include <SdFat.h>
#include <ESP8266WiFi.h>
#include <ESP8266mDNS.h>
#include <NTPClient.h>
#include <WiFiClient.h>
#include <ESP8266WebServer.h>
#include <Ticker.h>

// I2C Address
#define DS1307_ADDRESS 0x68   // Realtime clock
#define BME280_ADDRESS 0x76   // Humidity, Pressure and Temperature sensor
#define BH1750_ADDRESS 0x23   // Illuminance sensor

// Process mode
#define MODE_CURRENT  1       // Response current values
#define MODE_RECORDS  2       // Send records from SD limited the range of record position
#define MODE_DATE     3       // Send records from SD limited the range of date or date-time
#define MODE_MAINTE   11      // Create maintenance screen

// Analog input port
#define ANALOG_PIN     A0     // TOUT pin

// SD card drive & File name
#define SDCARD_DRIVE   16     // SD card chip select number
#define LOG_FILE    "logfile.txt"
#define PRM_FILE    "AIRMON.PRM"

// Time adjust
WiFiUDP ntpUDP;
NTPClient timeClient(ntpUDP);
char sNtpUrl[26] = {"ntp.nict.jp"};
long iNtpOffset = 32400;      // UTC + 9h (9H * 3600)sec.
long iNtpInterval = 43200;    // Time adjust interval(12H * 3600)sec.

// Timer interruption control
Ticker ticker1;               // For measurement
Ticker ticker2;               // For time adjustment
bool bReadyTicker = false;

// SD card control
SdFat SD;
bool bSD_Enabled = false;

// Measurement conditions
bool bWait = true;
char strStartTime[3] = {"00"};  // "00"~"59" (min.)
long iIntervalTime = 3600;      // Measurement time interval(60M * 60)sec.

// WiFi connection
char ssid[33]     = {"your_ssid"};
char password[64] = {"your_password"};

// WiFi response control
ESP8266WebServer server(80);
String strFromDate, strToDate;
long iRecPos, iRecNo;

// WiFi Response constants
const String strResponseHeader = "HTTP/1.1 200 OK\r\nContent-Type: text/html\r\n\r\n";
const String strHeader = "<!DOCTYPE html>\r\n"
                "<html><head><meta charset=\"utf-8\"><title>AirMonitor</title>"
                "<style>body{line-height:120%;font-family:monospace;}</style>"
                "</head><body><form name='resultform' target='_self' method='get'>";
const String strFooter = "   <INPUT type='submit' value=' 戻 る ' autofocus /></form></body></html>";
const String strTitle = "No., Temp(C), Press(hPa), Hum(%), Illum(lx), Date Time";

// DS1307 Clock data
struct ClockData {
    byte year;
    byte month;
    byte day;
    byte week;
    byte hour;
    byte minute;
    byte sec;
    byte ctrl;
};
ClockData dtClock;

//BME280 Global variables
unsigned long int hum_raw, temp_raw, pres_raw;
signed long int t_fine;

//BME280 Calibration variables
uint16_t dig_T1;
 int16_t dig_T2;
 int16_t dig_T3;
uint16_t dig_P1;
 int16_t dig_P2;
 int16_t dig_P3;
 int16_t dig_P4;
 int16_t dig_P5;
 int16_t dig_P6;
 int16_t dig_P7;
 int16_t dig_P8;
 int16_t dig_P9;
 int8_t  dig_H1;
 int16_t dig_H2;
 int8_t  dig_H3;
 int16_t dig_H4;
 int16_t dig_H5;
 int8_t  dig_H6;
 
//Measured result
struct MeasuredResult {
    double  temperature;
    double  pressure;
    double  humidity;
    int     illuminance;
    int     soil_moisture;
    String  datetime;
};
MeasuredResult rstMeasured;

// Parameter keyword
String kwd_ssid="ssid=";
String kwd_pwd="pwd=";
String kwd_ntp_svr="ntp_server=";
String kwd_ntp_int="ntp_interval=";
String kwd_ntp_off="ntp_time_offset=";
String kwd_start="measuring_start_time=";
String kwd_interval="measuring_interval=";

/*****************************************************************************
 *                          Predetermined Sequence                           *
 *****************************************************************************/
void setup() {
    Serial.begin(115200);

    // Prepare I2C protocol.
    Wire.begin();
    delay(50);

    // Prepare SD card unit.
    if (SD.begin(SDCARD_DRIVE))
        bSD_Enabled = true;
    else {
        bSD_Enabled = false;
        Serial.println("SD Drive does'nt work!");
        return;
    }
    Serial.println("SD enabled!");

    // Read parameter file and set variables.
    readParameterFile();

    // Set dateTime callback function to provide timestamp.
    SdFile::dateTimeCallback(dateTime);

    // Prepare WiFi system.
    WiFi.begin(ssid, password);
    while (WiFi.status() != WL_CONNECTED) {
        delay(500);
        Serial.print(".");
    }
    server.begin();
    Serial.print("\nServer started!  IP: ");
    Serial.println(WiFi.localIP());

    // Set local DNS
    MDNS.begin("airmon");

    // Create NTPClient object.
    timeClient = NTPClient(ntpUDP, sNtpUrl, iNtpOffset);

    // Prepare measurment
    prepareBME280();      // Humidity, Pressure and Temperature sensor
    delay(500);
    prepareBH1750(BH1750_ADDRESS);  // Illuminance sensor

    // Dummy measurement
    doMeasurement();

    // Tuning clock
    adjustTime();
    getDateTime(&dtClock);
    Serial.print("Start : "); Serial.println(editDateTime(dtClock));

    // Set server callback functions
    server.on("/", HTTP_GET, sendCommandScreen);
    server.on("/", HTTP_POST, procControl);
    Serial.print("Just wait until next time(minuets) =  "); Serial.println(strStartTime);
}

void loop() {
    if (!bSD_Enabled) {
        Serial.println("Can't work, the SD drive is disabled!");
        delay(60000);
        return;
    }
    // Check date input ('hh/mm/dd/w') from serial buffer.
    if (Serial.available() >= 8) {
        String date = Serial.readString();
        setDate(date);
    }

    // Wait until the specified time and start up
    if (bWait) {
        getDateTime(&dtClock);
        String sTime = editTime(dtClock);
        if (sTime.substring(3, 5) == strStartTime) {
            bWait = false;
            Serial.println("It is just in time!!");
            InitialProcess();
            return;
        }
        else
            delay(200);
    }

    /* [Timer interrupt process] */
    if (bReadyTicker) {
        adjustTime();
        bReadyTicker = false;
    }
    server.handleClient();
}

/*
 * Run only once at the beginning.
 */
void InitialProcess()
{
  Serial.println(">> InitialProcess()");
    // 1st measuement
    kickRoutineWork();

    // Timer interrupt time and event setting.
    ticker1.attach(iIntervalTime, kickRoutineWork);
    delay(1000);
    ticker2.attach(iNtpInterval, kickTimeAdjust);
}

/****************************< Interrupt handler >****************************/
/*
 * Timer interrupt event handler1
 *    <Start measurement>
 */
void  kickRoutineWork()
{
    // Measurment & write file
    doMeasurement();
    String buf =editMeasuredResult(rstMeasured);
    writeMeasurementResult(LOG_FILE, buf);
    Serial.println(buf);
}

/*
 * Timer interrupt event handler2
 *    <Start time adgustment>
 */
void kickTimeAdjust()
{
    bReadyTicker = true;
}

/************************< Server callback functions >************************/
/*
 * Display initial screen.
 */
void sendCommandScreen()
{
    String strBuf = "<!DOCTYPE html><html><head><meta charset=\'utf-8\'><title>AirMonitor</title>"
        "<style>body{line-height:150%;font-family:monospace;}"
        "table.tbl{font-size:13px;color:#0;margin-left:20px;border-collapse: collapse;}table.tbl tr {border: 1px solid #888888;text-align: left;}table.tbl td {border: 1px solid #888888;text-align: left;}table.tbl tr.ttl {background-color:#e6e6fa;}div{color:red;}</style>"
        "<script>function setCursor() {var obj = document.getElementsByName('COMMAND')[0];obj.focus();obj.value+= '';}"
        "function checkForm(){var data=document.getElementsByName('COMMAND')[0];if(data.value.trim()=='')return false; else return true;}</script></head>"
        "<body onload='setCursor();'><form name='mainform' target='_self' method='post' onsubmit='return checkForm();'><br>"
        "<input type='hidden' name='proc_kind' value='command' />"
        "<table  class='tbl'><caption><font color='blue' size='+1'><b>  【コマンド入力】</b></font><br></caption>"
        "<tr class='ttl'><td width='220'><b>   コマンド</b></td><td width='420'><b>   機能・動作</b></td></tr>"
        "<tr><td> now</td><td> 現在の計測値を表示する</td></tr>"
        "<tr><td> date={date}</td><td> 指定日付の計測データをすべて表示する</td></tr>"
        "<tr><td> date={from_date},{to_date}</td><td> 日付範囲のデータを表示する</td></tr>"
        "<tr><td> rec={position}</td><td> 指定レコード位置からファイル終端までのデータを表示する</td></tr>"
        "<tr><td> rec={position},{records}</td><td> 指定レコード位置から指定したレコード数だけ表示する</td></tr>"
        "<tr><td> mainte</td><td> 動作条件を変更する</td></tr></font></table>"
        "<table border='0'><tr><td width='20'> </td><td width='220'>→ <input type='text' name='COMMAND' maxlength='25' value='' autofocus' /></td>"
        "<td><input type='submit' name='SUBMIT' value=' 実 行 ' /></td></tr></table><br>"
        "<font color='darkcyan'> (注記)<br>    =記号の右辺には{ }の内容を指定します。<br>    dateは年月日をyy/mm/ddのように、positionとrecordsは数字を、それぞれ半角で指定してください。</font>"
        "</form></body></html>";
    server.send(200, "text/html", strBuf);
}

/*
 * Process controll.
 */
void procControl()
{
    String procKind = server.arg("proc_kind");
    if (procKind == "command") {
        procAnalyzeCommand();
    }
    else if (procKind == "mainte") {
        String sMode = server.arg("proc_mode");
        if (sMode == "set")
            rewriteParameterFile();
        procAnalyzeCommand();
    }
}
/*****************************************************************************/
 
/*
 * Analyze command  and control execution.
 */
void procAnalyzeCommand()
{
    String cmd = server.arg("COMMAND");
    if (cmd == "") {
        sendCommandScreen();
        return;
    }
    Serial.print("cmd: ");
    Serial.println(cmd);
    int iMode = testProcessMode(cmd);

    switch (iMode) {
        case MODE_CURRENT:
            doMeasurement();
            sendMeasuredResult(rstMeasured);
            break;
        case MODE_DATE:
            sendDataRecord(LOG_FILE, strFromDate, strToDate);
            break;
        case MODE_RECORDS:
            sendDataRecord(LOG_FILE, iRecPos, iRecNo);
            break;
        case MODE_MAINTE:
            sendMainteScreen();
            break;
        case 0:
            break;
        case -1:
            sendFormatError();
            break;
    }
}
/*****************************************************************************/

/*
 * Adjust the clock time.
 */
void adjustTime()
{
    while(1) {
        if (timeClient.update()) {
            String strJST = timeClient.getFormattedTime();
            Serial.print("JST time = "); Serial.println(strJST);
            setTime(strJST);
            return;
        }
        delay(50);
    }
}

/*
 * Analyze HTTP request parameter and decide process mode.
 *    Argument: (String)Command string.
 *    Return:   Process mode(MODE_CURRENT / MODE_DATE / MODE_RECORDS / -1(Error))
 */
int testProcessMode(String strParam)
{
    //strParam.trim();
    int iPos, iPos2;

    // Analyze the process
    int iMode = 0;
    if (strParam.indexOf("now") != -1) {
        iMode = MODE_CURRENT;
        Serial.println("Mode: Current data.");
    }
    else if ((iPos = strParam.indexOf("date=")) != -1) {
        iMode = MODE_DATE;
        strParam = strParam.substring(iPos);
        Serial.print("Param: ");
        Serial.println(strParam);
        if ((iPos2 = strParam.indexOf(",")) == -1) {
            strFromDate = strParam.substring(5);
            strToDate = strFromDate;
        } else {
            strFromDate = strParam.substring(5, iPos2);
            strToDate = strParam.substring(iPos2 + 1);
        }
        Serial.print(" From:");
        Serial.print(strFromDate);
        Serial.print(", To:");
        Serial.println(strToDate);
        if (!(strFromDate.length() == 8 && strToDate.length() == 8))
            return -1;
        if (!(strFromDate.charAt(2) == '/' && strFromDate.charAt(5) == '/'
                && strToDate.charAt(2) == '/' && strToDate.charAt(5) == '/'))
            return -1;
        if (!(isNumeric(strFromDate.substring(0, 2))
                && isNumeric(strFromDate.substring(3, 5))
                && isNumeric(strFromDate.substring(6, 8))
                && isNumeric(strToDate.substring(0, 2))
                && isNumeric(strToDate.substring(3, 5))
                && isNumeric(strToDate.substring(6, 8))))
            return -1;
        strFromDate = "20" + strFromDate;
        strToDate = "20" + strToDate;
    }
    else if ((iPos = strParam.indexOf("rec=")) != -1) {
        iMode = MODE_RECORDS;
        strParam = strParam.substring(iPos);
        Serial.print("Param: "); Serial.println(strParam);
        if ((iPos2 = strParam.indexOf(",")) == -1) {
            if (!(isNumeric(strParam.substring(4)) && strParam.substring(4).length() > 0)) {
                return -1;
            }
            iRecPos = (strParam.substring(4)).toInt();
            iRecNo = 10000;
        } else {
            if (!(isNumeric(strParam.substring(iPos + 4, iPos2))
                    && isNumeric(strParam.substring(iPos2 + 1))
                    && strParam.substring(iPos + 4, iPos2).length() > 0
                    && strParam.substring(iPos2 + 1).length() > 0)) {
                return -1;
            }
            iRecPos = (strParam.substring(iPos + 4, iPos2)).toInt();
            iRecNo = (strParam.substring(iPos2 + 1)).toInt();
        }
        if (iRecPos < 1)
            iRecPos = 1;
        if (iRecNo > 10000)
            iRecNo = 10000;
    }
    else if (strParam.indexOf("mainte") != -1)
        iMode = MODE_MAINTE;
    else
        iMode = -1;
    return iMode;
}

/*
 * Display Maintenance Screen
 */
void sendMainteScreen()
{
    String wsSsid = ssid;
    String wsPassword = password;
    String wsNtpUrl = "";
    String wsNtpInterval = "";
    String wsNtpOffset = "";
    String wsStartTime = "";
    String wsIntervalTime = "";
    char buf[10];

    if (readParameterFile()) {
        wsSsid = ssid;
        wsPassword = password;
        wsNtpUrl = sNtpUrl;
        wsNtpInterval = printNum(buf, iNtpInterval, 6);
        wsNtpOffset = printNum(buf, iNtpOffset, 6);
        wsStartTime = strStartTime;
        wsIntervalTime = printNum(buf, iIntervalTime, 6);
        wsNtpInterval.trim();  wsNtpOffset.trim();  wsIntervalTime.trim();
    }
    String strBuf = "<!DOCTYPE html><html><head><meta charset=\'utf-8\'><title>AirMonitor</title>"
        "<style>body{line-height:120%;font-family:monospace;}</style></head>"
        "<script>function setModeDo() {document.getElementById('proc_mode').value='set';}"
        "function setModeCancel() {document.getElementById('proc_mode').value='cancel';}"
        "function setCursor() {var obj = document.getElementsByName('ssid')[0];obj.focus();obj.value += '';}</script>"
        "<body onload='setCursor();'><form name='mainteform' target='_self' method='post'><br>"
        "<font color='blue' size='+1'><b>     【AirMonitor 動作条件の設定】</b></font><br><br>"
        "<table border='0'><tr><td width='20'> </td><td width='160'><b> 設定項目</b></td><td width='200'><b> 入 力 欄</b></td></tr>"
        "<tr><td></td><td>・SSID:</td><td><input name='ssid' size='15' maxlength='32' value='"+ wsSsid + "' autofocus /></td></tr>"
        "<tr><td></td><td>・Password:</td><td><input name='password' size='50' maxlength='63' value='" + wsPassword + "' /></td></tr>"
        "<tr><td></td><td>・NTPサーバー名:</td><td><input name='ntp_server' maxlength='25' size='30' value='" + wsNtpUrl + "' /></td></tr>"
        "<tr><td></td><td>・時刻調整間隔(秒):</td><td><input name='ntp_interval' maxlength='8' size='8' value='" + wsNtpInterval + "' /></td></tr>"
        "<tr><td></td><td>・UTFとの時差(秒):</td><td><input name='ntp_offset' maxlength='8' size='8' value='" + wsNtpOffset + "' /></td></tr>"
        "<tr><td></td><td>・計測開始分(00~59分):</td><td><input name='mes_start' maxlength='2' size='1' value='" + wsStartTime + "' /></td></tr>"
        "<tr><td></td><td>・計測間隔(秒):</td><td><input name='mes_interval' maxlength='8' size='8' value='" + wsIntervalTime + "' /></td></tr>"
        "<tr><td></td><td></td><td><input type='submit' name='SUBMIT' value=' 設 定 ' onclick='setModeDo();' /> "
        "<input type='submit' value=' 中 止 ' onclick='setModeCancel();' /></td></tr>"
        "<input type='hidden' name='proc_kind' value='mainte' /><input type='hidden' name='proc_mode' id='proc_mode' value='XYZ' /></table></form></body></html>"
        "<font color='darkcyan'><br> <b>(注意)</b><br>    設定した内容は<b>MCUボードのリセットボタンを押下</b>することで有効になります。</font>";
    server.send(200, "text/html", strBuf);
}

/*
 * Send HTTP response <MODE_RECORDS: Data record>
 *    Argument: (String)File name,
 *              (int)Target record position, (int)How many records.
 */
void sendDataRecord(String strName, int iRecPos, int iRecords)
{
    char buf[256], num[20];
    
    // Send title with a standard http response header
    String strBuf = strResponseHeader + strHeader + strTitle + "<br>";
    server.sendContent(strBuf);

    // Open data file
    File fc = SD.open(strName, FILE_READ);
    // Skip records
    bool bNodata = false;
    for (int i=1; i<iRecPos; i++) {
        int len = readln(&fc, buf, 250);
        if (len < 1) {
            bNodata = true;
            break;
        }
    }
    if (bNodata) {
        server.sendContent("Nothing!<br></bod</html>");
        return;
    }

    // Read and transfer each record until not EOF or condition is true
    int iCnt = iRecPos;
    int n = 0;
    strBuf = "";
    while (fc.available())
    {
        int len = readln(&fc, buf, 250);
        String number = printNum(num, iCnt++, 4);
        strBuf += number +  ", " + buf + "<br>";
        if (n % 50 == 0) {
            strBuf.replace(" ", " ");
            server.sendContent(strBuf);
            strBuf = "";
        }
        if (++n >= iRecords)
            break;
    }
    // Close file and sned trailer
    fc.close();
    if (n < 1) 
        strBuf = "Nothing!";
    strBuf.replace(" ", " ");
    strBuf += "\r\n" + strFooter;
    server.sendContent(strBuf);
}

/*
 * Send HTTP response <MODE_DATE: Data record>
 *    Argument: (String)File name,
 *              (String)Date from, (String)Date to.
 */
void sendDataRecord(String strName, String strFrom, String strTo)
{
    char buf[256], num[20];
    int iLines = 0;
    int n = 0;
    String strRec, number;

    // Send title with a standard http response header
    String strBuf = strResponseHeader + strHeader + strTitle + "<br>";
    server.sendContent(strBuf);

    // Open data file
    File fc = SD.open(strName, FILE_READ);
    bool bAppeared = false;

    // Search top record
    while (fc.available()) {
        int len = readln(&fc, buf, 250);
        iLines++;
        strRec = buf;
        int iPos = strRec.lastIndexOf(',');
        String strWork = strRec.substring(iPos+1, iPos+12);
        strWork.trim();
        int iRes = strWork.compareTo(strFrom);
        
        if (iRes < 0)
            continue;
        else if (iRes > 0)
            goto rtn_end;
        else {
            break;
        }
    }
    number = printNum(num, iLines, 4);
    strBuf = number + ", " + buf + "<br>";
    n = 1;

    // Read and transfer each record until not EOF or condition is true
    while (fc.available())
    {
        int len = readln(&fc, buf, 250);
        if (len < 1)
            break;
        iLines++;  n++;
        strRec = buf;
        int iPos = strRec.lastIndexOf(',');
        String strWork = strRec.substring(iPos+1, iPos+12);
        strWork.trim();
        int iRes = strWork.compareTo(strTo);
        if (iRes > 0)
            break;
        bAppeared = true;
        number = printNum(num, iLines, 4);
        strBuf += number +  ", " + buf + "<br>";
        if (n % 50 == 0) {
            strBuf.replace(" ", " ");
            server.sendContent(strBuf);
            strBuf = "";
        }
    }
    // Close file and sned trailer
rtn_end:
    fc.close();
    if (!bAppeared)
        strBuf = "Nothing!";
    strBuf.replace(" ", " ");
    strBuf += "<br>" + strFooter;
    server.sendContent(strBuf);
}

/*
 * Send HTTP response <MODE_CURRENT: Real time measured result>
 *    Argument: (struct)Measured result.
 */
void sendMeasuredResult(MeasuredResult data)
{
    char num[20];
    String strBuf = strHeader + strTitle + "<br>";
    String number = printNum(num, 0, 4);
    number += ", " + (editMeasuredResult(data)) + "<br>";
    number.replace(" ", " ");
    strBuf += number +  "<br>" + strFooter;
    server.send(200, "text/html", strBuf);
}

/*
 * Send HTTP response <MODE missing: Input error message>
 */
void sendFormatError()
{
    String strBuf = strHeader + " <font color='red'>パラメータの様式に誤りがあります!</font>" + strFooter;
    server.send(200, "text/html", strBuf);
}

/*
 * Read parameter file and setup variables.
 *    Return:   (bool)true(success)/false(file not found)
 */
bool readParameterFile()
{
    Serial.println("** Input parameter from .prm file **");
    if (!SD.exists(PRM_FILE)) {
        Serial.println("Parameter file not found!");
        return false;
    }

    File fc = SD.open(PRM_FILE, FILE_READ);
    String buf;

    while (fc.available()) {
        int pos;
        char ch = fc.read();
        if (ch != '\n') {
            if (ch != '\r')
                buf += ch;
            continue;
        }
        if (buf.indexOf(kwd_ssid) >= 0 && (pos = buf.indexOf("=")) > 0)
            buf.substring(++pos).toCharArray(ssid, 40);
        if (buf.indexOf(kwd_pwd) >= 0 && (pos = buf.indexOf("=")) > 0)
            buf.substring(++pos).toCharArray(password, 40);
        if (buf.indexOf(kwd_ntp_svr) >= 0 && (pos = buf.indexOf("=")) > 0)
            buf.substring(++pos).toCharArray(sNtpUrl,40);
        if (buf.indexOf(kwd_ntp_int) >= 0 && (pos = buf.indexOf("=")) > 0)
            iNtpInterval = buf.substring(++pos).toInt();
        if (buf.indexOf(kwd_ntp_off) >= 0 && (pos = buf.indexOf("=")) > 0)
            iNtpOffset = buf.substring(++pos).toInt();
        if (buf.indexOf(kwd_start) >= 0 && (pos = buf.indexOf("=")) > 0)
            buf.substring(++pos).toCharArray(strStartTime, 3);
        if (buf.indexOf(kwd_interval) >= 0 && (pos = buf.indexOf("=")) > 0)
            iIntervalTime = buf.substring(++pos).toInt();
        buf.remove(0, buf.length());
    }
    fc.close();
    Serial.print("   > ssid:          ["); Serial.print(ssid); Serial.println("]");
    Serial.print("   > password:      ["); Serial.print(password); Serial.println("]");
    Serial.print("   > NTP Server:    ["); Serial.print(sNtpUrl); Serial.println("]");
    Serial.print("   > NTP Interval:  ["); Serial.print(iNtpInterval); Serial.println("]");
    Serial.print("   > NTP Offset:    ["); Serial.print(iNtpOffset); Serial.println("]");
    Serial.print("   > Start Time:    ["); Serial.print(strStartTime); Serial.println("]");
    Serial.print("   > Interval Time: ["); Serial.print(iIntervalTime); Serial.println("]");
    return true;
}

/*
 * Rewrite parameter file from setup variables.
 */
void rewriteParameterFile()
{
    String wsSsid = server.arg("ssid");
    String wsPassword = server.arg("password");
    String wsNtpServer = server.arg("ntp_server");
    String wsNtpInterval = server.arg("ntp_interval");
    String wsNtpOffset = server.arg("ntp_offset");
    String wsMesStart = server.arg("mes_start");
    String wsMesInterval = server.arg("mes_interval");
    wsSsid.trim();
    wsPassword.trim();
    wsNtpServer.trim();
    wsNtpInterval.trim();
    wsNtpOffset.trim();
    wsMesStart.trim();
    wsMesInterval.trim();
    if (!(isNumeric(wsNtpInterval) && isNumeric(wsNtpOffset) 
            && isNumeric(wsMesStart) && isNumeric(wsMesInterval) && wsMesStart.length() == 2)) {
        sendFormatError();
        return;
    }
    if (wsSsid.length() < 6 || wsPassword.length() < 8 || wsNtpServer.length() < 8
            || wsNtpInterval.length() < 1 || wsNtpOffset.length() < 1 || wsMesInterval.length() < 1) {
        sendFormatError();
        return;
    }

    if (SD.exists(PRM_FILE))
        SD.remove(PRM_FILE);
    File sd = SD.open(PRM_FILE, FILE_WRITE);
    sd.println(kwd_ssid + wsSsid);
    sd.println(kwd_pwd + wsPassword);
    sd.println(kwd_ntp_svr + wsNtpServer);
    sd.println(kwd_ntp_int + wsNtpInterval);
    sd.println(kwd_ntp_off + wsNtpOffset);
    sd.println(kwd_start + wsMesStart);
    sd.println(kwd_interval + wsMesInterval);
    sd.close();
}

/*
 * Measure and  calibrate BME280, also illuminance, soil_moisture
 *    Result stored into rstMeasured structure.
 */
void doMeasurement()
{
    // Read BME280 measured result
    readData();
    // Calibration
    long temp_cal = calibration_T(temp_raw);
    long press_cal = calibration_P(pres_raw);
    long hum_cal = calibration_H(hum_raw);

    // Measurement illuminance
    int val1 = measureIlluminance(BH1750_ADDRESS);

    // Measurement soi_moisture
    int val2 = measureSoilMoisture(ANALOG_PIN);

    // Get date and time data from DS1307
    getDateTime(&dtClock);
    
    //Store into measured resut structure
    rstMeasured.temperature = (double)temp_cal / 100.0;
    rstMeasured.pressure = (double)press_cal / 100.0;
    rstMeasured.humidity = (double)hum_cal / 1024.0;
    rstMeasured.illuminance = val1;    
    rstMeasured.soil_moisture = val2;
    rstMeasured.datetime = editDateTime(dtClock);
}

/*
 * Edit measured result data.
 *    Argument: (struct)Measured result.
 *    Return:   Edited result.
 */
String editMeasuredResult(MeasuredResult data)
{
    char wbuf[20];
    String strWork = "";
    printNum(wbuf, data.temperature, 4, 1);
    strWork.concat(wbuf);
    strWork.concat(",");
    printNum(wbuf, data.pressure, 7, 1);
    strWork.concat(wbuf);
    strWork.concat(",");
    printNum(wbuf, data.humidity, 5, 1);
    strWork.concat(wbuf);
    strWork.concat(",");
    printNum(wbuf, data.illuminance, 6);
    strWork.concat(wbuf);
    strWork.concat(",");
    printNum(wbuf, data.soil_moisture, 4);
    strWork.concat(wbuf);
    strWork.concat(", ");
    strWork.concat(data.datetime);
    return strWork;
}

/*
 * Write measurment result to SD.
 *    Argument: (String)File name, String result.
 */
void writeMeasurementResult(String fname, String data)
{
    char buf[40];
    File df = SD.open(fname, FILE_WRITE);
    if (df)
    {
        df.println(data);
        df.close();
    }
}

/*
 * Read text line from SD card.
 *    Argument: File* handle, char* buffer, int Maximum text length.
 *    Return:   Size of read data.
 */
int readln(File* df, char* buf, int len)
{
    *buf = '\0';
    int pos = 0;    
    
    while (df->available()) {
        char ch = df->read();
        *(buf + pos) = ch;
        if (ch == 0x0a) {
            *(buf + pos + 1) = '\0';
            return pos;
        }
        pos++;
        if (pos >= len) {
            Serial.println("Overflowed!!");
            *buf = '\0';
            return 0;
        }
    }
}

/*
 * Print numeric (double, float)
 *    Argument: char* buffer, double data, int edit-length, int decimal.
 */
char *printNum(char *buf, double field, int len, int decimal)
{
    *buf ='\0';
    char bufw[40];
    dtostrf(field, len, decimal, bufw);
    strcpy(buf, bufw);
    return buf;
}

/*
 * Print numeric (int, long)
 *    Argument: char* buffer, int data, int edit-length.
 */
char *printNum(char *buf, int field, int len)
{
    *buf = '\0';
    char bufw[40];
    sprintf(bufw, "%d", field);
    int lenw = strlen(bufw);
    int i;
    for (i = 0; i < len - lenw; i++) {
        *(buf + i) = ' ';
    }
    *(buf + i) = '\0';
    strcat(buf, bufw);
    return buf;
}

/*
 * Test field is numeric or not.
 *    Argument: (String)Date string.
 *    Return:   (bool)true(numeric)/false(not numeric)
 */
bool isNumeric(String data)
{
    for (int i=0; i<data.length(); i++) {
        if (data.charAt(i) < '0' || data.charAt(i) > '9')
            return false;
    }
    return true;
}

/* ======================== DS1307 Clock Control ============================*/
/*
 * Read data from DS1307 Register 
 */
void getDateTime(ClockData *dt)
{
    int iValue = 0;
  
    Wire.beginTransmission(DS1307_ADDRESS);
    Wire.write(iValue);
    Wire.endTransmission();
    Wire.requestFrom(DS1307_ADDRESS, 7);
    dt->sec = Wire.read();
    dt->minute = Wire.read();
    dt->hour = Wire.read();
    dt->week = Wire.read();
    dt->day = Wire.read();
    dt->month = Wire.read();
    dt->year = Wire.read();
}

/*
 * Tell day of week from code.
 */
String tellDayOfWeek(byte num)
{
    static String week[] = {"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"};
  
    if (num >= 1 && num <= 7)
      return week[num-1];
    else
      return "";
}

/*
 * Set DS1307 time register.
 *    Argument: (String)Time string 'hh:mm:ss'.
 */
void setTime(String sTime)
{
    byte bValue = 0x00;   //Top Address
    String buf = sTime;
    buf.replace(":", "");  buf.trim();
    if (buf.length() == 6) {
        byte hour = (buf.charAt(0) << 4) + (buf.charAt(1) & 0x0f);
        byte minute = (buf.charAt(2) << 4) + (buf.charAt(3) & 0x0f);
        byte sec = (buf.charAt(4) << 4) + (buf.charAt(5) & 0x0f);
        Wire.beginTransmission(DS1307_ADDRESS);
        Wire.write(bValue);
        Wire.write(sec);
        Wire.write(minute);
        Wire.write(hour);
        Wire.endTransmission();
    }
}

/*
 * Set DS1307 date register.
 *    Argument: (String)Time string 'yy/mm/dd/w'.
 */
void setDate(String sDate)
{
    byte bValue = 0x03;   //Top Address
    String buf = sDate;
    buf.replace("/", "");  buf.trim();
    if (buf.length() >= 6) {
        byte year = (buf.charAt(0) << 4) + (buf.charAt(1) & 0x0f);
        byte month = (buf.charAt(2) << 4) + (buf.charAt(3) & 0x0f);
        byte day = (buf.charAt(4) << 4) + (buf.charAt(5) & 0x0f);
        byte week = 0x00;
        if (buf.length() == 7) {
            week = (buf.charAt(6) & 0x07);
          if (week < 0x01 || week > 0x07)
            week = 0x01;
        }
        Wire.beginTransmission(DS1307_ADDRESS);
        Wire.write(bValue);
        Wire.write(week);
        Wire.write(day);
        Wire.write(month);
        Wire.write(year);
        Wire.endTransmission();
    }
}

/*
 * Edit DS1307 date and time.
 */
String editDateTime(ClockData dt)
{
    return editDate(dt) + " " + editTime(dt);
}

/*
 * Edit time register.
 */
String editTime(ClockData dt)
{
    String buf = "";
    char wbuf[12];
    sprintf(wbuf, "%02x:%02x:%02x", (int)dt.hour, (int)dt.minute, (int)dt.sec);
    buf.concat(wbuf);
    return buf;
}

/*
 * Edit date register.
 */
String editDate(ClockData dt)
{
    String buf = "20";
    char wbuf[12];
    sprintf(wbuf, "%02x/%02x/%02x", (int)dt.year, (int)dt.month, (int)dt.day);
    buf.concat(wbuf);
    return buf;
}

/*
 * Callback function to provide file timestamp.
 */
void dateTime(uint16_t* date, uint16_t* time)
{
    uint16_t year;
    uint8_t month, day, hour, minute, second;
    
    getDateTime(&dtClock);
    year = 2000 + ((int)(dtClock.year >> 4)) * 10 + (int)(dtClock.year & 0x0f);
    month = ((int)(dtClock.month >> 4)) * 10 + (int)(dtClock.month & 0x0f);
    day = ((int)(dtClock.day >> 4)) * 10 + (int)(dtClock.day & 0x0f);
    hour = ((int)(dtClock.hour >> 4)) * 10 + (int)(dtClock.hour & 0x0f);
    minute = ((int)(dtClock.minute >> 4)) * 10 + (int)(dtClock.minute & 0x0f);
    second = ((int)(dtClock.sec >> 4)) * 10 + (int)(dtClock.sec & 0x0f);
    *date = FAT_DATE(year, month, day);
    *time = FAT_TIME(hour, minute, second);
}

/* ====================== BME280 T-P-H Measurement ==========================*/
/*
 * Initiate BME280 and get calibration variables.
 */
void prepareBME280()
{
    uint8_t osrs_t = 1;             //Temperature oversampling x 1
    uint8_t osrs_p = 1;             //Pressure oversampling x 1
    uint8_t osrs_h = 1;             //Humidity oversampling x 1
    uint8_t mode = 3;               //Normal mode
    uint8_t t_sb = 5;               //Tstandby 1000ms
    uint8_t filter = 0;             //Filter off 
    uint8_t spi3w_en = 0;           //3-wire SPI Disable
    
    uint8_t ctrl_meas_reg = (osrs_t << 5) | (osrs_p << 2) | mode;
    uint8_t config_reg    = (t_sb << 5) | (filter << 2) | spi3w_en;
    uint8_t ctrl_hum_reg  = osrs_h;

    writeReg(0xF2,ctrl_hum_reg);
    writeReg(0xF4,ctrl_meas_reg);
    writeReg(0xF5,config_reg);
    readTrim();
}

/*
 * Read calibration variables into structure.
 */
void readTrim()
{
    uint8_t data[32],i=0;
    Wire.beginTransmission(BME280_ADDRESS);
    Wire.write(0x88);
    Wire.endTransmission();
    Wire.requestFrom(BME280_ADDRESS,24);
    while(Wire.available()){
        data[i] = Wire.read();
        i++;
    }
    
    Wire.beginTransmission(BME280_ADDRESS);
    Wire.write(0xA1);
    Wire.endTransmission();
    Wire.requestFrom(BME280_ADDRESS,1);
    data[i] = Wire.read();
    i++;
    
    Wire.beginTransmission(BME280_ADDRESS);
    Wire.write(0xE1);
    Wire.endTransmission();
    Wire.requestFrom(BME280_ADDRESS,7);
    while(Wire.available()){
        data[i] = Wire.read();
        i++;    
    }
    dig_T1 = (data[1] << 8) | data[0];
    dig_T2 = (data[3] << 8) | data[2];
    dig_T3 = (data[5] << 8) | data[4];
    dig_P1 = (data[7] << 8) | data[6];
    dig_P2 = (data[9] << 8) | data[8];
    dig_P3 = (data[11]<< 8) | data[10];
    dig_P4 = (data[13]<< 8) | data[12];
    dig_P5 = (data[15]<< 8) | data[14];
    dig_P6 = (data[17]<< 8) | data[16];
    dig_P7 = (data[19]<< 8) | data[18];
    dig_P8 = (data[21]<< 8) | data[20];
    dig_P9 = (data[23]<< 8) | data[22];
    dig_H1 = data[24];
    dig_H2 = (data[26]<< 8) | data[25];
    dig_H3 = data[27];
    dig_H4 = (data[28]<< 4) | (0x0F & data[29]);
    dig_H5 = (data[30] << 4) | ((data[29] >> 4) & 0x0F);
    dig_H6 = data[31];   
}

/*
 * Write data into BME280 register.
 *    Argument: (uint8_t)Register address, (uint8_t)data.
 */
void writeReg(uint8_t reg_address, uint8_t data)
{
    Wire.beginTransmission(BME280_ADDRESS);
    Wire.write(reg_address);
    Wire.write(data);
    Wire.endTransmission();    
}

/*
 * Read data from BME280 register.
 */
void readData()
{
    int i = 0;
    uint32_t data[8];
    Wire.beginTransmission(BME280_ADDRESS);
    Wire.write(0xF7);
    Wire.endTransmission();
    Wire.requestFrom(BME280_ADDRESS,8);
    while(Wire.available()){
        data[i] = Wire.read();
        i++;
    }
    pres_raw = (data[0] << 12) | (data[1] << 4) | (data[2] >> 4);
    temp_raw = (data[3] << 12) | (data[4] << 4) | (data[5] >> 4);
    hum_raw  = (data[6] << 8) | data[7];
}

/*
 * Calibration [Temperature]
 */
signed long int calibration_T(signed long int adc_T)
{
    signed long int var1, var2, T;
    var1 = ((((adc_T >> 3) - ((signed long int)dig_T1<<1))) * ((signed long int)dig_T2)) >> 11;
    var2 = (((((adc_T >> 4) - ((signed long int)dig_T1)) * ((adc_T>>4) - ((signed long int)dig_T1))) >> 12) * ((signed long int)dig_T3)) >> 14;
    
    t_fine = var1 + var2;
    T = (t_fine * 5 + 128) >> 8;
    return T; 
}

/*
 * Calibration [Pressure]
 */
unsigned long int calibration_P(signed long int adc_P)
{
    signed long int var1, var2;
    unsigned long int P;
    var1 = (((signed long int)t_fine)>>1) - (signed long int)64000;
    var2 = (((var1>>2) * (var1>>2)) >> 11) * ((signed long int)dig_P6);
    var2 = var2 + ((var1*((signed long int)dig_P5))<<1);
    var2 = (var2>>2)+(((signed long int)dig_P4)<<16);
    var1 = (((dig_P3 * (((var1>>2)*(var1>>2)) >> 13)) >>3) + ((((signed long int)dig_P2) * var1)>>1))>>18;
    var1 = ((((32768+var1))*((signed long int)dig_P1))>>15);
    if (var1 == 0)
    {
        return 0;
    }    
    P = (((unsigned long int)(((signed long int)1048576)-adc_P)-(var2>>12)))*3125;
    if(P<0x80000000)
    {
       P = (P << 1) / ((unsigned long int) var1);   
    }
    else
    {
        P = (P / (unsigned long int)var1) * 2;    
    }
    var1 = (((signed long int)dig_P9) * ((signed long int)(((P>>3) * (P>>3))>>13)))>>12;
    var2 = (((signed long int)(P>>2)) * ((signed long int)dig_P8))>>13;
    P = (unsigned long int)((signed long int)P + ((var1 + var2 + dig_P7) >> 4));
    return P;
}

/*
 * Calibration [Humidity]
 */
unsigned long int calibration_H(signed long int adc_H)
{
    signed long int v_x1;
    
    v_x1 = (t_fine - ((signed long int)76800));
    v_x1 = (((((adc_H << 14) -(((signed long int)dig_H4) << 20) - (((signed long int)dig_H5) * v_x1)) + 
              ((signed long int)16384)) >> 15) * (((((((v_x1 * ((signed long int)dig_H6)) >> 10) * 
              (((v_x1 * ((signed long int)dig_H3)) >> 11) + ((signed long int) 32768))) >> 10) + (( signed long int)2097152)) * 
              ((signed long int) dig_H2) + 8192) >> 14));
    v_x1 = (v_x1 - (((((v_x1 >> 15) * (v_x1 >> 15)) >> 7) * ((signed long int)dig_H1)) >> 4));
    v_x1 = (v_x1 < 0 ? 0 : v_x1);
    v_x1 = (v_x1 > 419430400 ? 419430400 : v_x1);
    return (unsigned long int)(v_x1 >> 12);   
}

/* ========================= BH1750 Illuminance =============================*/
/*
 * Initiate BH1750
 */
void prepareBH1750(int i2cAddress)
{
    Wire.beginTransmission(i2cAddress);  
    Wire.write(0x10); 
    Wire.endTransmission();
  delay(180);
}

/*
 * Measure illuminance
 */
int measureIlluminance(int i2cAddress)
{
    uint16_t val = 0;
    byte buf[2]; 
    if (measureIlluminance(i2cAddress, buf) == 2){
        val = ((buf[0] << 8) | buf[1]) / 1.2;   // Calculate
        return (int)val;
    }
    return 0;
}  
int measureIlluminance(int i2cAddress, byte *buff)
 {
    int i = 0;
    *buff = 0x00;
    Wire.beginTransmission(i2cAddress); 
    Wire.requestFrom(i2cAddress, 2);  
    while (Wire.available())  
    { 
        *(buff + i) = Wire.read();
        i++;
    }
    Wire.endTransmission();  
    return i; 
 }

/* ========================= YL-38 Soil moisture ============================*/
/*
 * Measure soil moisture
 *    Return: moisture value(0~100)
 */
int measureSoilMoisture(int pin_no)
{
    int val = 1023 - analogRead(pin_no);
    return (int)map(val, 56, 545, 0, 100);
}

 
Copyright (C) 2011-2024 Marchan, All rights reserved.