文章程式碼顯示

2018年1月30日 星期二

NodeMCU 教學 - 08:使用 WeMos D1 mini NodeMCU 網路爬蟲 抓取 JSON 空氣品質 並結合手機 BLYNK

本篇功能概述

  1. 實現 D1 mini 抓取環保署空氣品質資料
  2. 實現 D1 mini 與 BLYNK 連結
我們在

NodeMCU 教學 - 06:使用 WeMos D1 mini NodeMCU 網路爬蟲 抓取 JSON 天氣資訊

有抓過中央氣象局的天氣資訊(如氣溫),而此篇是要從環保署去抓現今最夯的空氣品質資料


連結在此 行政院環境保護署。環境資源資料開放平臺




我們可以抓到空汙品質的資訊,這是在前面的氣象局開放資料裡面沒有的


空氣品質指標(AQI)



取得資料的方式以及範例連結在此



在"權限登記"可以申請到專屬自己的 KEY

https://opendata.epa.gov.tw/webapi/api/rest/datastore/355000000I-000260?sort=MonitorDate&offset=0&limit=1000

我們用空氣品質指標(AQI)的範例來得到 JSON 格式的空氣品質資料。可以發現得到的資料還是很多,這個部分我目前還不曉得要怎麼修改 URL 以取得某一個地區的資料(例如前一章可以用 locatioName 針對新竹地區去抓)

使用範例的地方是教我們用 "skip"來忽略某些資料,後來我發現好像沒有用處,我們把 offset 替代 skip 並且加上 limit 後就可以成功進行資料篩選(得到新竹地區的空氣品質資訊)


因為我們只想針對 "新竹" 地區的空氣品質來進行資料擷取,並不想要一次得到這麼多筆資料,所以我們必須想辦法減少無效的資料量


東搞西弄後就抓出新竹部分的空氣品質了

https://opendata.epa.gov.tw/webapi/api/rest/datastore/355000000I-000259/?format=json&limit=3&offset=49&sort=County&token=你的key


接下來就是用程式碼來將資料傳上去 BLYNK 了

#define BLYNK_PRINT Serial
#include "ESP8266WiFi.h"
#include "ArduinoJson.h"
#include "BlynkSimpleEsp8266.h"
#include "SimpleTimer.h"

void get_AQI(); 
bool showAQI(char *json);

// ===== Wifi setup =====
const char *ssid = "WIFI名稱";
const char *pass = "WIFI密碼";

// =====  AQI. setup =====
const char *AQIhost = "opendata.epa.gov.tw";
const char *sourceid = "355000000I-000259";
const char *_limit = "3";
const char *_offset = "49";
const char *token = "環保署得到的KEY";
static char AQIrespBuf[4096];

// ==== BLYNK ====
char auth[] = "BLYNK的KEY";

// ===== Hardware setup =====
const int ledPin = D4;        // the number of the LED pin

SimpleTimer timer; 
volatile float AQI = 0;
volatile float PM2_5 = 0;
volatile float PM10 = 0;
volatile int flag = 1;

void timer_1260000(){
  flag = 0;
  Serial.println("timerInterrupt START!");
  get_AQI();
  
  Serial.println("Send API. data to BLYNK !!");
  Blynk.virtualWrite(V2, AQI); 
  Blynk.virtualWrite(V3, PM2_5); 
  Blynk.virtualWrite(V4, PM10); 
  
  Serial.println("timerInterrupt END!\n");
  flag = 1;
}

void setup() 
{
  pinMode(ledPin, OUTPUT);
  Serial.begin(115200);
  delay(10);
  digitalWrite(ledPin,LOW);
  Blynk.begin(auth, ssid, pass);
  timer.setInterval(1260000L, timer_1260000);
  digitalWrite(ledPin,HIGH);
}

void loop() 
{
  if(flag) {
    Blynk.run();
    timer.run();  
  }
  delay(20);
}

/* =====================*/
// ===== get_AQI ====
/* =====================*/
void get_AQI()
{
  digitalWrite(ledPin, HIGH);
  Serial.print("Connecting to ");
  Serial.println(AQIhost);
  
  WiFiClient client;
  
  const int Port = 80;
  if (!client.connect(AQIhost, Port)) {
    Serial.println("Connection failed");
    return;
  }
  
  // ====== 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;
  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);
      Serial.print(F("bytesIn ")); Serial.println(bytesIn);
      if (bytesIn > 0) {
        AQIrespLen += bytesIn;
        if (AQIrespLen > sizeof(AQIrespBuf)) AQIrespLen = sizeof(AQIrespBuf);
      }
      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 (AQIrespLen >= sizeof(AQIrespBuf)) {
    Serial.print(F("AQIrespBuf overflow "));
    Serial.println(AQIrespLen);
    return;
  }
  
  AQIrespBuf[AQIrespLen++] = '\0';
  Serial.print(F("AQIrespLen "));
  Serial.println(AQIrespLen);
  Serial.println("");

  if(showAQI(AQIrespBuf)) { 
    Serial.println("Get AQI. data");     
   }  
   
  digitalWrite(ledPin, LOW);  
}

/* =====================*/
// ===== showAQI ====
/* =====================*/
bool showAQI(char *json){
  StaticJsonBuffer<3072> 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;

  JsonObject& root = jsonBuffer.parseObject(json);  
  if (!root.success()) {
    Serial.println(F("jsonBuffer.parseObject() failed"));
    return false;
  }

  float _AQI = root["records"][0]["AQI"];
  float _PM2_5 = root["records"][0]["PM2.5"];
  float _PM10 = root["records"][0]["PM10"];
  Serial.print("AQI = ");
  Serial.println(_AQI);
  Serial.print("APM2.5 = ");
  Serial.println(_PM2_5);    
  Serial.print("PM10 = ");
  Serial.println(_PM10);    

  AQI = _AQI;
  PM2_5 = _PM2_5;
  PM10 = _PM10;
  
  return true;
}


收了好幾天之後的結果如下圖


補充:

上述那個空氣品質來源十分不穩定,常常會有連不上的問題,所以這裡提供另外一個資料來源,無須申請識別碼

Opendata2

這來源穩定很多且回應很迅速,但缺點是沒辦法在 URL 進行資料的篩選,只能把所有資料都抓了,目前我還沒有成功完成這個來源的實作,因為一次要收取全台灣所有測站資料的數據,過於龐大的 data 造成解析的困難以及版子的不穩定。

↓↓↓ 連結到部落格方針與索引 ↓↓↓

Blog 使用方針與索引