我們最終總結一下所有,得到以下功能
- WeMos D1 mini 爬氣象局天氣資料(氣溫、風速、濕度)
- 爬環保署的空氣品質資料( AQI 、 PM2.5 、 PM10)
- 利用天氣資料計算出體感溫度
- 使用 BLYNK 作為手機端的顯示介面(上述資料)
- 使用 IFTTT to Line 在每天的某一個可設定時間發送目前的氣溫、體感溫度、空氣品質 AQI 給使用者( 小時設定區間限定 : 01~23;分鐘設定區間限定 : 03~56 )
- 新增若資料抓取不完整或失敗,就不更新至 BLYNK
- 針對環保署空品這個不穩定的資料來源進行特別的處理
7.1 修改 get_AQI() 的寫法,增加等待數據時間最長為 300 sec
7.2 增加 flag ,並用一個 30 mins 的 timer 對此 flag 重置
7.3 當 flag 為 1 時,每 330秒就會觸發 330 secs timer ,在這 timer 中觸發 get_AQI()。如此一來整個收 AQI 的邏輯變成 "若在 30 分鐘內沒有收到 AQI data ,則不斷的嘗試觸發 get_AQI() "
結合整個 WeMos D1 mini NodeMCU + JSON + BLYNK + IFTTT to Line 的以下系列文章
從 NodeMCU 教學 - 01:WeMos D1 mini NodeMCU 使用 Arduino IDE 開發環境設置 開始
到 NodeMCU 教學 - 08:使用 WeMos D1 mini NodeMCU 網路爬蟲 抓取 JSON 空氣品質 並結合手機 BLYNK
可以按此來進行檢索
到 NodeMCU 教學 - 08:使用 WeMos D1 mini NodeMCU 網路爬蟲 抓取 JSON 空氣品質 並結合手機 BLYNK
可以按此來進行檢索
程式碼放在文末,因為太長了。我懶得將一些可以合併起來的地方進行合併,就把前面幾章的那些全部兜起來做成這個最終應用。
#define BLYNK_PRINT Serial
#include "ESP8266WiFi.h"
#include "ArduinoJson.h"
#include "BlynkSimpleEsp8266.h"
#include "SimpleTimer.h"
#include "WidgetRTC.h"
bool get_Weather();
bool showWeather(char *json);
bool get_AQI();
bool showAQI(char *json);
void send_event(const char *event);
void showTime();
// ===== Wifi setup =====
const char *ssid = "你的wifi名稱";
const char *pass = "你的wifi密碼";
// ===== Weather. setup =====
const char *privateKey = "你的識別碼";
const char *dataid = "O-A0003-001";
const char *locationName = "新竹"; //改這個地方至你要的地區
const char *elementName = "TEMP,HUMD,WDSD";//擷取溫度、濕度、風速
const char *host = "opendata.cwb.gov.tw";
static char respBuf[2048];
// ===== AQI. setup =====
const char *token = "你的識別碼";
const char *sourceid = "355000000I-000259";
const char *_limit = "1"; //limit不要動
const char *_offset = "49"; //改這個地方至你要的地區
const char *AQIhost = "opendata.epa.gov.tw";
static char AQIrespBuf[2048];
volatile int flag = 0; //在30分鐘內是否有成功抓取到 API 資料
// ==== BLYNK ====
char auth[] = "你的識別碼";
volatile int AQI;
volatile int PM2_5;
volatile int PM10;
volatile float temp;
volatile float perceived_temp; // 體感溫度
SimpleTimer timer;
WidgetRTC rtc;
// ===== IFTTT setup =====
const char *IFTTTprivateKey = "你的識別碼";
const char *event = "你的事件名稱";
const char *IFTTThost = "maker.ifttt.com";
const int Notify_hour = 7; // 早上七點
const int Notify_min = 3; // 三分
// ===== Hardware setup =====
const int ledPin = D4; // the number of the LED pin
/* =====================*/
// ==== 330 secs timer ====
/* =====================*/
void timer_330000(){
if ( !flag ){
if( get_AQI() ){
Blynk.connect();
Serial.println("Send AQI. data to BLYNK !!");
Serial.println(AQI);
Serial.println(PM2_5);
Serial.println(PM10);
Blynk.virtualWrite(V3, AQI);
Blynk.virtualWrite(V4, PM2_5);
Blynk.virtualWrite(V5, PM10);
Serial.println("===== get_AQI() DONE ! ===== ");
delay(1000);
}
}
}
/* =====================*/
// ===== 60secs timer ====
/* =====================*/
void timer_60000(){
showTime(); // 顯示目前時間於串列通訊埠
static int event_flag = 0;
if( (hour() == (Notify_hour - 1)) ) event_flag = 1;
if( (hour() == Notify_hour) && (minute() >= (Notify_min - 3) ) && (minute() <= (Notify_min + 3) ) && event_flag )
{
event_flag = 0;
send_event(event);
}
}
/* =====================*/
// ==== 20 mins timer ====
/* =====================*/
void timer_1200000(){
if( get_Weather() ){
Blynk.connect();
Serial.println("Send Temperature data to BLYNK !!");
Blynk.virtualWrite(V1, temp);
Blynk.virtualWrite(V2, perceived_temp);
Serial.println("===== get_Weather() DONE ! ===== ");
delay(1000);
}
}
/* =====================*/
// ==== 30 mins timer ====
/* =====================*/
void timer_1800000(){
flag = 0;
}
/* =====================*/
// ===== setup ====
/* =====================*/
void setup()
{
pinMode(ledPin, OUTPUT);
Serial.begin(115200);
delay(10);
digitalWrite(ledPin,HIGH);
Blynk.connectWiFi(ssid, pass); //連線至 Wi-Fi
Blynk.config(auth); //輸入Auth,顯示 BLYNK on Arduino 歡迎訊息
Blynk.connect(); // 連線至 BLYNK ,成功後會顯示 Ready
rtc.begin();
attachInterrupt(digitalPinToInterrupt(buttonPin), buttonISR, FALLING);
timer.setInterval(1200000L, timer_1200000); //for get_weather(), 20 mins timer
timer.setInterval(60000L, timer_60000); // for time, 1 mins timer
timer.setInterval(1800000L, timer_1800000); // for get_AQI(), 30 mins timer
timer.setInterval(330000L, timer_330000); // for get_AQI(), 330 secs timer
digitalWrite(ledPin,LOW);
delay(1000);
}
/* =====================*/
// ===== loop ====
/* =====================*/
void loop()
{
Blynk.run();
timer.run();
delay(20);
}
/* =====================*/
// ===== get_Weather ====
/* =====================*/
bool get_Weather()
{
digitalWrite(ledPin, HIGH);
Serial.println("\n============================================= ");
Serial.print("===== Connecting to ");
Serial.print(host);
Serial.println(" =====");
Serial.println("============================================= ");
WiFiClient client;
const int Port = 80;
if (!client.connect(host, Port)) {
Serial.println("Connection failed");
return false;
}
else Serial.println("Connection success");
// Create a URI for the request
String url = "/api/v1/rest/datastore/";
url += dataid;
url += "?Authorization=";
url += privateKey;
url += "&locationName=";
url += locationName;
url += "&elementName=";
url += elementName;
Serial.print("Requesting URL: ");
Serial.println(url);
// This will send the request to the server
client.print(String("GET ") + url + " HTTP/1.1\r\n" +
"Host: " + host + "\r\n" +
"Connection: close\r\n\r\n");
client.flush();
int respLen = 0;
bool skip_headers = true;
while (client.connected() || client.available()) {
if (skip_headers) {
String aLine = client.readStringUntil('\n');
if (aLine.length() <= 1) {
skip_headers = false;
}
}
else {
int bytesIn;
bytesIn = client.read((uint8_t *)&respBuf[respLen], sizeof(respBuf) - respLen);
Serial.print(F("bytesIn ")); Serial.println(bytesIn);
if (bytesIn > 0) {
respLen += bytesIn;
if (respLen > sizeof(respBuf)) respLen = sizeof(respBuf);
}
else if (bytesIn < 0) {
Serial.print(F("read error "));
Serial.println(bytesIn);
}
}
delay(1);
}
Serial.println();
Serial.println("===== Closing connection =====");
Serial.println();
client.stop();
if (respLen >= sizeof(respBuf)) {
Serial.print(F("respBuf overflow "));
Serial.println(respLen);
return false;
}
if(showWeather(respBuf)) {
Serial.println("Get Temp. data completed");
digitalWrite(ledPin, LOW);
return true;
}
else{
Serial.println("Get Temp. data FAILD(JSON)");
digitalWrite(ledPin, LOW);
return false;
}
}
/* =====================*/
// ===== showWeather ====
/* =====================*/
bool showWeather(char *json){
StaticJsonBuffer<2048> jsonBuffer;
// 前處理
char *jsonstart = strchr(json, '{');
Serial.println("jsonstart");
Serial.println(jsonstart);
Serial.println("");
if (jsonstart == NULL) {
Serial.println(F("JSON data missing"));
return false;
}
json = jsonstart;
// Parse(解析) JSON
JsonObject& root = jsonBuffer.parseObject(json);
if (!root.success()) {
Serial.println(F("jsonBuffer.parseObject() failed"));
return false;
}
// Extract weather info from parsed JSON
float wdsd = root["records"]["location"][0]["weatherElement"][0]["elementValue"];
float _temp = root["records"]["location"][0]["weatherElement"][1]["elementValue"];
float humd = root["records"]["location"][0]["weatherElement"][2]["elementValue"];
float _perceived_temp = 1.04*_temp + 0.2*humd*6.1*exp( (17.27*_temp)/(237.7+_temp) ) - 0.65*wdsd -2.7;
Serial.print("Temp. = ");
Serial.println(_temp);
Serial.print("Wdsd. = ");
Serial.println(wdsd);
Serial.print("Humd. = ");
Serial.println(humd);
Serial.print("Perceived Temp. = ");
Serial.println(_perceived_temp);
temp = _temp; // 用一個全域變數供 get_Weather 調用
perceived_temp = _perceived_temp;
return true;
}
/* =====================*/
// ===== get_AQI ====
/* =====================*/
bool get_AQI()
{
digitalWrite(ledPin, HIGH);
Serial.println("\n============================================= ");
Serial.print("===== Connecting to ");
Serial.print(AQIhost);
Serial.println(" =====");
Serial.println("============================================= ");
WiFiClient client;
const int Port = 80;
if (!client.connect(AQIhost, Port)) {
Serial.println("Connection failed");
return false ; //若連線失敗直接離開 get_AQI()
}
else Serial.println("Connection success");
// ====== Create a URI for the request =====
String url = "/webapi/api/rest/datastore/";
url += sourceid;
url += "/?format=json&limit=";
url += _limit;
url += "&offset=";
url += _offset;
url += "&sort=County&token=";
url += token;
Serial.print("Requesting URL: ");
Serial.println(url);
// This will send the request to the server
client.print(String("GET ") + url + " HTTP/1.1\r\n" +
"Host: " + AQIhost + "\r\n" +
"Connection: close\r\n\r\n");
client.flush();
int AQIrespLen = 0;
bool skip_headers = true;
unsigned long last_time = millis();
flag = 1;
Serial.println("Waiting for response");
while (client.connected() || client.available()) {
if (skip_headers) {
String aLine = client.readStringUntil('\n');
if (aLine.length() <= 1) skip_headers = false;
}
else {
int bytesIn;
bytesIn = client.read((uint8_t *)&AQIrespBuf[AQIrespLen], sizeof(AQIrespBuf) - AQIrespLen);
if (bytesIn > 0) {
AQIrespLen += bytesIn;
if (AQIrespLen > sizeof(AQIrespBuf)) AQIrespLen = sizeof(AQIrespBuf);
}
else if (bytesIn < 0){
Serial.print(F("read error "));
Serial.println(bytesIn);
}
}//end else
delay(1);
if( ( millis() - last_time ) > 300000 ) {
Serial.println("[ERROR] TIME OUT (300sec)");
client.stop();
flag = 0;
return false;
}
} // end while
Serial.println("===== Closing connection =====");
client.stop();
if (AQIrespLen >= sizeof(AQIrespBuf)) {
Serial.print(F("AQIrespBuf overflow "));
Serial.println(AQIrespLen);
flag = 0;
return false;
}
AQIrespBuf[AQIrespLen++] = '\0';
Serial.print(F("AQIrespLen "));
Serial.println(AQIrespLen);
Serial.print("Use ");
Serial.print((millis() - last_time)/1000);
Serial.println(" sec");
if(showAQI(AQIrespBuf)) {
Serial.println("Get AQI. data SUCCESS");
digitalWrite(ledPin, LOW);
flag = 1; //成功擷取到 AQI 資料
Serial.print("flag = ");
Serial.println(flag);
return true;
}
else{
Serial.println("Get AQI. data FAILED ");
digitalWrite(ledPin, LOW);
flag = 0;
return false;
}
}
/* =====================*/
// ===== showAQI ====
/* =====================*/
bool showAQI(char *json){
StaticJsonBuffer<2048> jsonBuffer;
Serial.println("Show AQI. data");
char *jsonstart = strchr(json, '{');
Serial.println("jsonstart");
Serial.print(jsonstart);
if (jsonstart == NULL) {
Serial.println(F("JSON data missing"));
return false;
}
json = jsonstart;
JsonObject& root = jsonBuffer.parseObject(json);
if (!root.success()) {
Serial.println(F("jsonBuffer.parseObject() failed"));
return false;
}
int _AQI = root["result"]["records"][0]["AQI"];
int _PM2_5 = root["result"]["records"][0]["PM2.5"];
int _PM10 = root["result"]["records"][0]["PM10"];
Serial.print("AQI = ");
Serial.println(_AQI);
Serial.print("APM2.5 = ");
Serial.println(_PM2_5);
Serial.print("PM10 = ");
Serial.println(_PM10);
if ( ( _AQI > 0 ) && ( _PM2_5 > 0 ) && ( _PM10 > 0 ) ) {
AQI = _AQI;
PM2_5 = _PM2_5;
PM10 = _PM10;
Serial.println("AQI Update");
return true;
}
else{
AQI = AQI;
PM2_5 = PM2_5;
PM10 = PM10;
Serial.println("AQI Maintain");
return false;
}
}
/* =====================*/
// ===== send_event ====
/* =====================*/
void send_event(const char *event)
{
digitalWrite(ledPin, HIGH);
Serial.print("Connecting to ");
Serial.println(IFTTThost);
WiFiClient client;
const int httpPort = 80;
if (!client.connect(IFTTThost, httpPort)) {
Serial.println("Connection failed");
return;
}
else Serial.println("Connection successful");
// Create a URI for the request
String url = "/trigger/";
url += event;
url += "/with/key/";
url += IFTTTprivateKey;
url += "?";
url += "value1=";
url += temp;
url += "&";
url += "value2=";
url += perceived_temp;
url += "&";
url += "value3=";
url += AQI;
Serial.print("\nSend Temperature = ");
Serial.println(temp);
Serial.print("Send Perceived Temperature = ");
Serial.println(perceived_temp);
Serial.print("AQI = ");
Serial.println(AQI);
Serial.print("Requesting URL: ");
Serial.println(url);
// This will send the request to the server
client.print(String("GET ") + url + " HTTP/1.1\r\n" +
"Host: " + IFTTThost + "\r\n" +
"Connection: close\r\n\r\n");
// Read all the lines of the reply from server and print them to Serial,
// the connection will close when the server has sent all the data.
while( client.connected() )
{
if(client.available()){
String line = client.readStringUntil('\r');
Serial.print(line);
}
else {
delay(50); // No data yet, wait a bit
}
}
Serial.println();
Serial.println("===== Closing connection =====");
client.stop();
digitalWrite(ledPin, LOW);
}
/* =====================*/
// ===== showTime ====
/* =====================*/
void showTime() {
String t="";
byte h = hour();
if (h < 10) {t.concat('0');}
t.concat(h);
t.concat(':');
byte m = minute();
if (m < 10) {t.concat('0');}
t.concat(m);
t.concat(':');
byte s = second();
if (s < 10) {t.concat('0');}
t.concat(s);
Serial.print("Current Time -> ");
Serial.println(t);
}
2048>2048>
其中 goodMorning 是我在 IFTTT 上面設定的事件名稱,將 value1, value2, value3 都用上了
欲更改成你要的地點,請看下方兩篇
NodeMCU 教學 - 06:使用 WeMos D1 mini NodeMCU 網路爬蟲 抓取 JSON 天氣資訊
NodeMCU 教學 - 08:使用 WeMos D1 mini NodeMCU 網路爬蟲 抓取 JSON 空氣品質 並結合手機 BLYNK
BLYNK 方面的設置,請看
《進階※應用篇》寫程式Arduino教學 - 03:手機與 Arduino 的連結 BLYNK 應用簡述
NodeMCU 教學 - 02:WeMos D1 mini NodeMCU 與 BLYNK
IFTTT Line 通知方面,請看
NodeMCU 教學 - 03:WeMos D1 mini (NodeMCU) 與 Line 的連結