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でコマンド入力画面が呼び出されます。
12345678910111213 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行目でこの値を取得してボタン種別を判定しています。
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051 /*
* 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);
}
1 2 3 4 5 6 7 8 9 10 11 12 13 | void setup() { : : : // Set server callback functions server.on( "/" , HTTP_GET, sendCommandScreen); server.on( "/" , HTTP_POST, procControl); } void loop() { server.handleClient(); } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 | /* * 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が実行されます。
12345678910111213141516171819202122232425262728293031323334353637 /*
* 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
* <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
;
}
</start></start>
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 | /* * 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 * <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 ; } </start></start> |
(4)全体の制御構造について
すでにお分かりのように、基幹部以外のすべての処理は、基幹部または関連メソッドで設定する割り込みハンドラーとコールバック関数に委ねられています。クライアント(ブラウザ)とのやりとりはサーバーのコールバック関数が担当し、そのコールバック関数に書いたコードによって計測や記録、検索やコミュニケーションが進んで行きます。設定条件に従った定期的な計測はタイマーのコールバック関数がコントロールしています。システムクロックの時刻合わせも、タイマーのコールバック関数の役目です。
MCU自体はいつもほとんど待ち状態であり、割り込みが発生した瞬間だけ必要な処理を行います。このように割り込み処理を利用すると複雑な手順制御が不要なので、スケッチのロジックを簡潔に組み立てることができます。また、割り込みが発生すると処理を一時中断してコールバック関数を実行するので、見かけ上マルチスレッドのような動作が可能になります。
4.連載の終わりにあたって
『超小型格安チップ「ESP-WROOM-02(ESP8266)」はどこまで使えるか?』と題した7回の連載はこれで終了です。稚拙な内容ですが、長らくお付き合いいただきありがとうございました。これがきっかけになって、新たな取り組みへのご参考になれば嬉しいです。
私事になりますが、山歩きで肉離れを起こしたのが原因で久々に向き合ったArduinoに始まり、ESP-WROOM-02 (ESP8266)にめぐり会っての3カ月ばかり、実に楽しい時間を過ごすことができました。最初は戸惑いながら購入したピッチ変換されただけのMCUでしたが、それぞれのピンの機能を確かめたり回路を考える上で、この選択は良かったと思っています。また今回組み立てたMCUボードは、この時期をこのように取り組んだ記念の品になりました。
できれば今後も、このMCUボードを使っていろいろやってみたいのですが、「山は招いている」し、限られた時間でどこまで進めることができるかは不明です。なおいくつもの改良すべき事項を置き去りにしますが、限られた時間であれば、これにて「チョ~ン!」。
●スケッチ全景
最後になりましたが、今までの流儀にしたがってスケッチのすべてを掲載します。前回同様、コードを利用する場合はこの画面をコピー&ペーストするのではなく、冒頭に配置しているボタンでダウンロードしてください。またコードを実行する際には、ssidとpasswordに環境に合った値を設定してください。当初は動作条件設定ファイルが作成されていないので、まずmainteで動作条件設定画面を表示して「NTPサーバー名」以降を設定します。MCUボードのスライドスイッチをLEDが点灯する側(実行モード)にセットして、リセットボタンを押すと実行開始です。
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264 /*
* 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".
*
* 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);
}
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912 913 914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929 930 931 932 933 934 935 936 937 938 939 940 941 942 943 944 945 946 947 948 949 950 951 952 953 954 955 956 957 958 959 960 961 962 963 964 965 966 967 968 969 970 971 972 973 974 975 976 977 978 979 980 981 982 983 984 985 986 987 988 989 990 991 992 993 994 995 996 997 998 999 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011 1012 1013 1014 1015 1016 1017 1018 1019 1020 1021 1022 1023 1024 1025 1026 1027 1028 1029 1030 1031 1032 1033 1034 1035 1036 1037 1038 1039 1040 1041 1042 1043 1044 1045 1046 1047 1048 1049 1050 1051 1052 1053 1054 1055 1056 1057 1058 1059 1060 1061 1062 1063 1064 1065 1066 1067 1068 1069 1070 1071 1072 1073 1074 1075 1076 1077 1078 1079 1080 1081 1082 1083 1084 1085 1086 1087 1088 1089 1090 1091 1092 1093 1094 1095 1096 1097 1098 1099 1100 1101 1102 1103 1104 1105 1106 1107 1108 1109 1110 1111 1112 1113 1114 1115 1116 1117 1118 1119 1120 1121 1122 1123 1124 1125 1126 1127 1128 1129 1130 1131 1132 1133 1134 1135 1136 1137 1138 1139 1140 1141 1142 1143 1144 1145 1146 1147 1148 1149 1150 1151 1152 1153 1154 1155 1156 1157 1158 1159 1160 1161 1162 1163 1164 1165 1166 1167 1168 1169 1170 1171 1172 1173 1174 1175 1176 1177 1178 1179 1180 1181 1182 1183 1184 1185 1186 1187 1188 1189 1190 1191 1192 1193 1194 1195 1196 1197 1198 1199 1200 1201 1202 1203 1204 1205 1206 1207 1208 1209 1210 1211 1212 1213 1214 1215 1216 1217 1218 1219 1220 1221 1222 1223 1224 1225 1226 1227 1228 1229 1230 1231 1232 1233 1234 1235 1236 1237 1238 1239 1240 1241 1242 1243 1244 1245 1246 1247 1248 1249 1250 1251 1252 1253 1254 1255 1256 1257 1258 1259 1260 1261 1262 1263 1264 | /* * 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". * * 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); } |