『超小型格安チップ「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);
}