測定クライアント
測定クライアントは、
で構成します。BME -280は気温・湿度・気圧を測定するセンサであり、ESP-WROOM-02 にI2Cで接続します。土壌湿度センサは土壌の湿り気を測定するセンサであり、ESP-WROOM-02 のアナログ入力端子に接続します。リアルタイムクロックはESP-WROOM-02 とI2Cで接続し、NTPサーバと同期が取れた時刻の保持と、ESP-WROOM-02 の起動制御を行います。
測定クライアントの回路図と、ユニバーサル基板に実装した写真は次の通りです。
なお、回路図には記載していませんが、電源は単3乾電池3本で、3端子レギュレータで3.3Vまで降圧しています。ESP-WROOM-02 はWiFi 接続時に大きな電流を必要とするので、それに耐え得る3端子レギュレータを選定します。
測定クライアント回路図 測定クライアント実装写真 写真では、測定クライアントのユニバーサル基板の下に、別記事
techrmc.hatenablog.com
で紹介したWiFi ドアセンサのESP-WROOM-02 が写っていますが、環境ログシステムとは無関係です。
電力消費量を削減するため、データ測定時以外は、ESP-WROOM-02 をスリープ状態にしておきます。スリープ状態のESP-WROOM-02 は非常に小さな電力消費となるため、乾電池駆動の長期化に役立ちます。
スリープ状態のESP-WROOM-02 は、リアルタイムクロックRTC-8564NBのアラーム割り込みによって起動トリガをかけます。ESP-WROOM-02 は測定が終わるとスリープ状態に入っていますが、所定のアラーム時刻にRTC-8564NBからトリガがかかると、ESP-WROOM-02 はスリープ状態から復帰し、BME280と土壌湿度センサからデータを読み取ります。読み取ったセンサデータはMQTTでpublishし、次の起動時刻をRTC-8564NBのアラーム時刻としてセットしたのち再びスリープ状態に入ります。測定クライアントの初期化時には、RTC-8564NBをインターネット上のNTPサーバと時刻同期しておきます。
なお、土壌湿度センサをVCCとGNDに直接接続すると、土壌湿度測定時以外も常時、土壌湿度センサに電流が流れるため、乾電池の寿命が短くなリます。そこで、測定時以外は消費電流を抑制できるよう、土壌湿度センサへの電流供給をトランジスタ 2SC1815によってスイッチングしています。
Arduino で作成したESP-WROOM-02 のプログラムを次に示します。なお、BME280用のプログラムは、スイッチサイエンスのサンプルプログラム
BME280 – スイッチサイエンス
をほぼそのまま利用させて頂きましたので、そちらをご覧ください。
メインプログラムmain.ino
#include <Wire.h>
#include <ESP8266WiFi.h>
#include <WiFiClientSecure.h>
#include <time.h>
extern "C" {
#include "user_interface.h"
}
#include <PubSubClient.h>
#include <ArduinoJson.h>
#define ESP8266_PLATFORM
#define PIN_MODE 12
#define PIN_LED 13
#define PIN_SPWR 14
#define NOERR 3
#define WIFIERRM 5
#define NTPERR 7
#define WIFIERRU 9
#define SLEEP_P 0
#define CYCLE_M 60
#define STH_H 930
#define STH_M 900
#define STH_L 870
int mode;
const char ssid4mqtt[] = "xxxx" ;
const char pass4mqtt[] = "xxxx" ;
const char ssid4util[] = "xxxx" ;
const char pass4util[] = "xxxx" ;
#define MQTTCONNECTWAIT 10000
#define MQTTMAXRETRY 2
const char mqttAddress[] = "xxx.xx.xx.xx" ;
const char mqttTopic[] = "xxxx" ;
const char mqttClientid[] = "xxxx" ;
const int mqttPort = 1883 ;
const char * host = "notify-api.line.me" ;
const int httpPort = 443 ;
const char * token = "xxxx" ;
WiFiClient client;
PubSubClient mqttClient (client);
WiFiClientSecure utilclient;
#define ATLEN 26
char at[ATLEN];
StaticJsonDocument<200 > jsondoc;
char msg[200 ];
void setup () {
Serial.begin (115200 );
pinMode (PIN_LED,OUTPUT);
digitalWrite (PIN_LED,LOW);
pinMode (PIN_SPWR,OUTPUT);
digitalWrite (PIN_SPWR,LOW);
pinMode (PIN_MODE,INPUT_PULLUP);
Wire.begin ();
mode = digitalRead (PIN_MODE);
if (mode==LOW) {
if (connectWiFi (ssid4mqtt, pass4mqtt)==false ) {
longLightMulti (WIFIERRM);
digitalWrite (PIN_SPWR,HIGH);
delay (5000 );
return ;
} else if (connectWiFi (ssid4util, pass4util)==false ) {
longLightMulti (WIFIERRU);
digitalWrite (PIN_SPWR,HIGH);
delay (5000 );
return ;
}
rtc_init ();
if (rtc_sync_ntp () < 2020 ) {
longLightMulti (NTPERR);
} else {
longLightMulti (NOERR);
}
rtc_read_regtbl ();
digitalWrite (PIN_SPWR,HIGH);
delay (5000 );
} else {
if (rtc_init ()==true ) {
if (connectWiFi (ssid4util, pass4util) == false ) {
rtc_read_regtbl ();
rtc_set_alarm_min (CYCLE_M);
sleep ();
}
rtc_sync_ntp ();
}
rtc_read_regtbl ();
rtc_set_alarm_min (CYCLE_M);
rtc_read_regtbl ();
digitalWrite (PIN_SPWR,HIGH);
delay (5000 );
bme280_Setup ();
soilbme280_mqtt ();
sleep ();
}
}
void loop () {
int value;
if (mode==LOW) {
value = analogRead (A0);
if (value > STH_H) {
intLight (100 );
} else if (value > STH_M) {
intLight (70 );
} else if (value > STH_L) {
intLight (40 );
} else {
intLight (10 );
}
} else {
sleep ();
}
}
void sleep (){
bme280_Sleep ();
digitalWrite (PIN_SPWR,LOW);
digitalWrite (PIN_LED,LOW);
delay (100 );
ESP.deepSleep (SLEEP_P,WAKE_RF_DEFAULT);
delay (1000 );
}
void longLight (){
digitalWrite (PIN_LED,HIGH);
delay (1000 );
digitalWrite (PIN_LED,LOW);
delay (100 );
}
void longLightMulti (int n) {
for (int i=0 ;i<n;i++) longLight ();
}
void intLight (int duty){
digitalWrite (PIN_LED,HIGH);
delay (10 *duty);
digitalWrite (PIN_LED,LOW);
delay (10 *(100 -duty)+10 );
}
bool connectWiFi (const char *ssid, const char *pass){
int waiting=0 ;
if (WiFi.status () == WL_CONNECTED) {
WiFi.disconnect ();
}
digitalWrite (PIN_LED,LOW);
WiFi.mode (WIFI_STA);
WiFi.begin (ssid, pass);
while (WiFi.status () != WL_CONNECTED){
delay (100 );
waiting++;
if (waiting > 300 ) {
digitalWrite (PIN_LED,LOW);
return false ;
}
}
digitalWrite (PIN_LED,LOW);
return true ;
}
bool send_to_LINE (char *value1, char *value2, char *value3) {
utilclient.setInsecure ();
if (connectWiFi (ssid4util, pass4util) == false ) {
return false ;
}
if (!utilclient.connect (host, httpPort)) {
return false ;
}
String message = String ("value1=" ) + String (value1)
+ String (", value2=" ) + String (value2)
+ String (", value3=" ) + String (value3);
String content = String ("message=" ) + message;
utilclient.print (String ("POST /api/notify HTTP/1.1 \r\n " )
+ "Host: " + host + " \r\n "
+ "Authorization: Bearer " + token + " \r\n "
+ "Content-Length: " + String (content.length ()) + " \r\n "
+ "Content-Type: application/x-www-form-urlencoded \r\n\r\n "
+ content + " \r\n " );
unsigned long timeout = millis ();
while (utilclient.available () == 0 ) {
if (millis () - timeout > 5000 ) {
utilclient.stop ();
return false ;
}
}
while (utilclient.available ()){
String line = utilclient.readStringUntil ('\r' );
}
return true ;
}
void soilbme280_mqtt (){
float temperature[5 ], max_temperature, min_temperature;
float humidity[5 ], max_humidity, min_humidity;
float pressure[5 ], max_pressure, min_pressure;
int moisture[5 ], max_moisture, min_moisture;
readData ();
temperature[0 ] = readTemperature ();
humidity[0 ] = readHumidity ();
pressure[0 ] = readPressure ();
moisture[0 ] = analogRead (A0);
if (isnan (temperature[0 ]) || isnan (humidity[0 ]) || isnan (pressure[0 ])) {
sleep ();
}
delay (100 );
max_temperature = temperature[0 ];
min_temperature = temperature[0 ];
max_humidity = humidity[0 ];
min_humidity = humidity[0 ];
max_pressure = pressure[0 ];
min_pressure = pressure[0 ];
max_moisture = moisture[0 ];
min_moisture = moisture[0 ];
for (int i=1 ;i<5 ;i++) {
readData ();
temperature[i] = readTemperature ();
humidity[i] = readHumidity ();
pressure[i] = readPressure ();
moisture[i] = analogRead (A0);
if (isnan (temperature[i]) || isnan (humidity[i]) || isnan (pressure[i])) {
sleep ();
}
if (temperature[i] > max_temperature)
max_temperature = temperature[i];
if (temperature[i] < min_temperature)
min_temperature = temperature[i];
if (humidity[i] > max_humidity)
max_humidity = humidity[i];
if (humidity[i] < min_humidity)
min_humidity = humidity[i];
if (pressure[i] > max_pressure)
max_pressure = pressure[i];
if (pressure[i] < min_pressure)
min_pressure = pressure[i];
if (moisture[i] > max_moisture)
max_moisture = moisture[i];
if (moisture[i] < min_moisture)
min_moisture = moisture[i];
delay (100 );
}
float ave_temperature=0 ;
float ave_humidity=0 ;
float ave_pressure=0 ;
int ave_moisture=0 ;
for (int j=0 ;j<5 ;j++) {
ave_temperature += temperature[j];
ave_humidity += humidity[j];
ave_pressure += pressure[j];
ave_moisture += moisture[j];
}
ave_temperature = (ave_temperature-max_temperature-min_temperature)/3 ;
ave_humidity = (ave_humidity-max_humidity-min_humidity)/3 ;
ave_pressure = (ave_pressure-max_pressure-min_pressure)/3 ;
ave_moisture = (ave_moisture-max_moisture-min_moisture)/3 ;
rtc_get_timestamp (at);
if (connectWiFi (ssid4mqtt, pass4mqtt) == false ) {
send_to_LINE ((char *)mqttClientid,"error" ,"WiFi_connection" );
sleep ();
}
mqttClient.setServer (mqttAddress, mqttPort);
int cnt = 0 ;
while (!mqttClient.connected ()) {
if (mqttClient.connect (mqttClientid)){
jsondoc["datetime" ] = at;
jsondoc["temperature" ] = ave_temperature;
jsondoc["humidity" ] = ave_humidity;
jsondoc["pressure" ] = ave_pressure;
jsondoc["soil_moisture" ] = ave_moisture;
serializeJson (jsondoc,msg);
mqttClient.publish (mqttTopic, msg);
jsondoc.clear ();
} else {
cnt++;
if (cnt <= MQTTMAXRETRY ) {
delay (MQTTCONNECTWAIT);
} else {
send_to_LINE ((char *)mqttClientid,"error" , "MQTT_connection" );
sleep ();
}
}
}
mqttClient.disconnect ();
}
RTC-8564NB周りのプログラムrtc8564nb.ino
#include <Wire.h>
#include <time.h>
#include <TZ.h>
#define RTC_ADDRESS 0x51
#define NTP_SERVER1 "xxx.xxx.xxx"
#define NTP_SERVER2 "xxx.xxx.xxx"
#define NTP_SERVER3 "ntp.nict.jp"
#define NTPMAXREPEAT 60
#define RTCREGLEN 16
int RegTbl[RTCREGLEN];
static const char *pszWDay[]={"Sun" ,"Mon" ,"Tue" ,"Wed" ,"Thu" ,"Fri" ,"Sat" };
#define ATLEN 26
bool rtc_init (){
bool isVL;
Wire.beginTransmission (RTC_ADDRESS);
Wire.write (0x02 );
Wire.endTransmission ();
Wire.requestFrom (RTC_ADDRESS,1 );
while (Wire.available () == 0 ){}
RegTbl[2 ] = Wire.read ();
if ((RegTbl[2 ] & 0x80 ) != 0 ){
isVL = true ;
} else {
isVL = false ;
}
Wire.beginTransmission (RTC_ADDRESS);
Wire.write (0x01 );
Wire.write (0x02 );
Wire.endTransmission ();
Wire.beginTransmission (RTC_ADDRESS);
Wire.write (0x09 );
Wire.write (0x00 | 0x80 );
Wire.write (0x00 | 0x80 );
Wire.write (0x01 | 0x80 );
Wire.write (0x00 | 0x80 );
Wire.write (0x00 | 0x03 );
Wire.endTransmission ();
return (isVL);
}
int rtc_sync_ntp (){
time_t timeNow;
struct tm* tmNow;
int i=0 ;
configTzTime (TZ_Asia_Tokyo, NTP_SERVER1, NTP_SERVER2, NTP_SERVER3);
do {
delay (1000 );
timeNow = time (NULL );
tmNow = localtime (&timeNow);
} while (((tmNow->tm_year+1900 ) < 2020 )&&(i<=NTPMAXREPEAT));
Wire.beginTransmission (RTC_ADDRESS);
Wire.write (0x00 );
Wire.write (0x00 );
Wire.write (0x02 );
Wire.write (DectoBCD (tmNow->tm_sec));
Wire.write (DectoBCD (tmNow->tm_min));
Wire.write (DectoBCD (tmNow->tm_hour));
Wire.write (DectoBCD (tmNow->tm_mday));
Wire.write (DectoBCD (tmNow->tm_wday));
Wire.write (DectoBCD (tmNow->tm_mon+1 )|0x80 );
Wire.write (DectoBCD (tmNow->tm_year-100 ));
Wire.endTransmission ();
return (tmNow->tm_year+1900 );
}
void rtc_set_alarm_min (int m) {
int now_hour, now_min;
int alarm_hour, alarm_min;
int wh, wm;
wh = m / 60 ;
wm = m % 60 ;
now_min = rtc_get_min ();
now_hour = rtc_get_hour ();
alarm_min = (now_min+wm)%60 ;
if (((now_min+wm)/60 )==0 ) {
alarm_hour = (now_hour + wh)%24 ;
} else {
alarm_hour = (now_hour + wh + 1 )%24 ;
}
rtc_set_alarm (alarm_hour, alarm_min);
}
void rtc_set_alarm (int h, int m){
Wire.beginTransmission (RTC_ADDRESS);
Wire.write (0x09 );
Wire.write (DectoBCD (m));
Wire.write (DectoBCD (h));
Wire.write (0x01 | 0x80 );
Wire.write (0x00 | 0x80 );
Wire.endTransmission ();
}
int rtc_get_hour (){
return (BCDtoDec (RegTbl[4 ] & 0x3F ));
}
int rtc_get_min (){
return (BCDtoDec (RegTbl[3 ] & 0x7F ));
}
void rtc_get_timestamp (char *at) {
String wts = String (BCDtoDec (RegTbl[8 ] & 0xFF )+2000 );
wts += '-' ;
wts += ((RegTbl[7 ] >> 4 ) & 0x01 );
wts += (RegTbl[7 ] & 0x0F );
wts += '-' ;
wts += ((RegTbl[5 ] >> 4 ) & 0x03 );
wts += (RegTbl[5 ] & 0x0F );
wts += 'T' ;
wts += ((RegTbl[4 ] >> 4 ) & 0x03 );
wts += (RegTbl[4 ] & 0x0F );
wts += ':' ;
wts += ((RegTbl[3 ] >> 4 ) & 0x07 );
wts += (RegTbl[3 ] & 0x0F );
wts += ':' ;
wts += ((RegTbl[2 ] >> 4 ) & 0x07 );
wts += (RegTbl[2 ] & 0x0F );
wts += "+09:00" ;
wts.toCharArray (at, ATLEN);
}
void rtc_read_regtbl (){
Wire.beginTransmission (RTC_ADDRESS);
Wire.write (0x00 );
Wire.endTransmission ();
Wire.requestFrom (RTC_ADDRESS,16 );
for (int i=0 ; i<RTCREGLEN; i++){
while (Wire.available () == 0 ){}
RegTbl[i] = Wire.read ();
}
}
byte BCDtoDec (byte value){
return ((value >> 4 ) * 10 ) + (value & 0x0F ) ;
}
byte DectoBCD (int value) {
return ((value/10 *0x10 ) | (value%10 ));
}
そろそろ記事が長くなってきたので、続きは別記事とします。
続きはこちらへ。
techrmc.hatenablog.com