2016年7月8日 星期五

以 Arduino+ESP8266 物聯網模組重作 DHT11 溫溼度實驗

之前有網友留言問 DHT11 實驗出現奇怪數字問題, 我推測是 DHT11 燒掉了 (正負接反), 如果零件正常, 按照我之前的測試紀錄來做應該不會有問題才對. 參考 :

# Arduino 溫濕度感測器 DHT11 測試

那時我正在忙著焊我的 IOT 模組, 現在萬事俱備, 於是找出 DHT11 來重新做一次溫溼度實驗, 當然套用為這塊 IOT 模組寫的程式架構. 首先到 Github 下載 Adafruit 的 DHT11/22 函式庫到 Arduino 安裝目錄下的 library 資料夾下 (然後 Arduino IDE 必須重開才抓得到) :

adafruit/DHT-sensor-library

然後把 DHT11 插到麵包板上, VCC 與 DATA 腳之間插上 10K 電阻, VCC 與 GND 插個 0.1uF 電容 (104), 再將 DATA 腳接到 Arduino 的 D2 腳, 然後將 IOT 模組設定好要連線的 AP, 切到工作模式重開機, 將下列程式上傳即可 :

測試 1 :

#include <SoftwareSerial.h>
#define DEBUG true
//-----application include & def listed HERE-----
#include "DHT.h"
#define DHTPIN 2  
#define DHTTYPE DHT11

//system use please do not edit
SoftwareSerial esp8266(7,8); //(RX,TX)
const int SW_PIN=4; //Pin to switch configuration or working mode
const int MAX_PAGE_NAME_LEN=48;  //buffer size
char buffer[MAX_PAGE_NAME_LEN + 1]; //store page_name/ssid/pwd
int mode; //store current mode(LOW=wifi setup, HIGH=working)

//-----application global variables listed HERE-----
DHT dht(DHTPIN, DHTTYPE); // Initialize DHT sensor

void setup() {
  //system use please do not edit
  Serial.begin(9600);
  esp8266.begin(9600);
  sendData("AT+RST\r\n",2000,DEBUG); // reset ESP8266
  pinMode(SW_PIN, INPUT);
  mode=digitalRead(SW_PIN);
  sendData("AT+GMR\r\n",1000,DEBUG);
  if (mode==LOW) { //setup mode : for wifi configuration
    sendData("AT+CWMODE=3\r\n",1000,DEBUG); //configure as access point
    sendData("AT+CIPMUX=1\r\n",1000,DEBUG); //enable multiple connections
    sendData("AT+CIPSERVER=1,80\r\n",2000,DEBUG); //turn on server 80 port    
    }
  else {  //working mode : for running application
    sendData("AT+CWMODE=1\r\n",1000,DEBUG); //configure as a station
    delay(3000); //wait for wifi connection to get local ip
    }
  sendData("AT+CIFSR\r\n",1000,DEBUG); //get ip address
  //-----application setup codes listed HERE-----
  Serial.println("DHT11 test");
  dht.begin();
  }

void loop() {
  if (mode==LOW) {setupWifi();} //system use please do not edit
  else { //-----application loop codes listed HERE-----  
    float h=dht.readHumidity();
    float t=dht.readTemperature();
    float f=dht.readTemperature(true);
    if (isnan(h) || isnan(t) || isnan(f)) {
      Serial.println("Failed to read from DHT sensor!");
      return;
      }
    Serial.print("Humidity: ");
    Serial.print(h);
    Serial.print("%\t");
    Serial.print("Temperature: ");
    Serial.print(t);
    Serial.print("*C\t");
    Serial.print(f);
    Serial.print("*F\n");
    delay(2000);    
    }
  }

void setupWifi() { //system use please do not edit
  if (esp8266.available()) { // check if the esp is sending a message
    if (esp8266.find("+IPD,")) {
      delay(1000);
      //esp8266 link response : +IPD,0,498:GET / HTTP/1.1
      //retrieve connection ID from response (0~4, after "+IPD,")
      int connectionId=esp8266.read()-48;  //from ASCII to number
      //subtract 48 because read() returns ASCII decimal value
      //and in ASCII, "0" (the first decimal number) starts at 48
      if (esp8266.find("GET /")) { //retrieve page name (router)
        memset(buffer, 0, sizeof(buffer));  //clear buffer (all set to 0)
        if (esp8266.readBytesUntil('/', buffer, sizeof(buffer))) {
          if (strcmp(buffer, "update") == 0) { //update wifi
            //"?ssid=aaa&pwd=bbb HTTP/1.1"        
            esp8266.find("?ssid="); //skip ssid token
            memset(buffer, 0, sizeof(buffer));  //clear buffer (all set to 0)
            esp8266.readBytesUntil('&', buffer, sizeof(buffer)); //retrieve ssid
            String ssid=buffer;
            esp8266.find("pwd="); //skip pwd token
            memset(buffer, 0, sizeof(buffer));  //clear buffer (all set to 0)
            esp8266.readBytesUntil(' ', buffer, sizeof(buffer)); //retrieve pwd
            String pwd=buffer;
            //configure as a station
            String res=sendData("AT+CWJAP=\"" + ssid + "\",\"" + pwd + "\"\r\n",6000,DEBUG);
                 
            //show setup result
            String webpage="<html>Wifi setup ";
            if (res.indexOf("OK") != -1) {webpage += "OK!</html>";}
            else {webpage += "Failed!</html>";}
            String cipSend="AT+CIPSEND=";
            cipSend += connectionId;
            cipSend += ",";
            cipSend +=webpage.length();
            cipSend +="\r\n";
            sendData(cipSend,1000,DEBUG);
            sendData(webpage,2000,DEBUG);
       
            String closeCommand = "AT+CIPCLOSE=";
            closeCommand+=connectionId; // append connection id
            closeCommand+="\r\n";
            sendData(closeCommand,3000,DEBUG);            
            }
          else { //show setup page
            String webpage="<html><form method=get action='/update/'>SSID ";
            webpage += "<input name=ssid type=text><br>";
            String cipSend = "AT+CIPSEND=";
            cipSend += connectionId;
            cipSend += ",";
            cipSend +=webpage.length();
            cipSend +="\r\n";
            sendData(cipSend,1000,DEBUG);
            sendData(webpage,2000,DEBUG);

            webpage="PWD <input name=pwd type=text> ";
            cipSend = "AT+CIPSEND=";
            cipSend += connectionId;
            cipSend += ",";
            cipSend +=webpage.length();
            cipSend +="\r\n";
            sendData(cipSend,1000,DEBUG);
            sendData(webpage,2000,DEBUG);

            webpage="<input type=submit value=Connect></form></html>";
            cipSend = "AT+CIPSEND=";
            cipSend += connectionId;
            cipSend += ",";
            cipSend +=webpage.length();
            cipSend +="\r\n";
            sendData(cipSend,1000,DEBUG);
            sendData(webpage,2000,DEBUG);

            String closeCommand = "AT+CIPCLOSE=";
            closeCommand+=connectionId; // append connection id
            closeCommand+="\r\n";
            sendData(closeCommand,3000,DEBUG);
            }
          }
        }
      }
    }
  }

//system use please do not edit
String sendData(String command, const int timeout, boolean debug) {
  String response="";
  esp8266.print(command); // send the read character to the esp8266
  long int time=millis();
  while ((time+timeout) > millis()) {
    while(esp8266.available()) {
      // The esp has data so display its output to the serial window
      char c=esp8266.read(); // read the next character.
      response += c;
      }
    }
  if (debug) {Serial.print(response);}
  return response;
  }

上面測試 1 只是將自 DHT11 讀取的溫溼度數值輸出到序列埠而已, 並未使用到 ESP8266 的連網功能. 序列埠監控視窗擷取訊息如下 :

AT+RST


OK
bB�鑭b禔S��"軫B�侒��餾�
[System Ready, Vendor:www.ai-thinker.com]
AT+GMR

0018000902

OK
AT+CWMODE=1

no change
AT+CIFSR

192.168.43.111

OK
DHT11 test
Humidity: 47.00% Temperature: 27.00*C 80.60*F
Humidity: 48.00% Temperature: 27.00*C 80.60*F
Humidity: 49.00% Temperature: 26.00*C 78.80*F
Humidity: 51.00% Temperature: 27.00*C 80.60*F
Humidity: 47.00% Temperature: 27.00*C 80.60*F
Humidity: 47.00% Temperature: 27.00*C 80.60*F
Humidity: 47.00% Temperature: 27.00*C 80.60*F
...

可見使用 Adafruit 的函式庫沒問題呀! 接下來參考之前寫的 Thingspeak 物聯網伺服器實驗, 將所量取的溫溼度數據上傳到 Thingspeak 伺服器上. 在這之前必須去申請一個 Thingspeak 帳號, 取得一個寫入的 API KEY, 參考下面這篇的說明 :

ESP8266 WiFi 模組與 Arduino 連線測試 (Thingspeak)

完整程式如下 :

測試 2 :

#include <SoftwareSerial.h>
#define DEBUG true
//-----application include & def listed HERE-----
#include "DHT.h"
#define DHTPIN 2  
#define DHTTYPE DHT11

//system use please do not edit
SoftwareSerial esp8266(7,8); //(RX,TX)
const int SW_PIN=4; //Pin to switch configuration or working mode
const int MAX_PAGE_NAME_LEN=48;  //buffer size
char buffer[MAX_PAGE_NAME_LEN + 1]; //store page_name/ssid/pwd
int mode; //store current mode(LOW=wifi setup, HIGH=working)

//-----application global variables listed HERE-----
DHT dht(DHTPIN, DHTTYPE); // Initialize DHT sensor
String api_key="NO5N8C7T2KINFCQE";  //Thingspeak API Write Key

void setup() {
  //system use please do not edit
  Serial.begin(9600);
  esp8266.begin(9600);
  sendData("AT+RST\r\n",2000,DEBUG); // reset ESP8266
  pinMode(SW_PIN, INPUT);
  mode=digitalRead(SW_PIN);
  sendData("AT+GMR\r\n",1000,DEBUG);
  if (mode==LOW) { //setup mode : for wifi configuration
    sendData("AT+CWMODE=3\r\n",1000,DEBUG); //configure as access point
    sendData("AT+CIPMUX=1\r\n",1000,DEBUG); //enable multiple connections
    sendData("AT+CIPSERVER=1,80\r\n",2000,DEBUG); //turn on server 80 port    
    }
  else {  //working mode : for running application
    sendData("AT+CWMODE=1\r\n",1000,DEBUG); //configure as a station
    delay(3000); //wait for wifi connection to get local ip
    }
  sendData("AT+CIFSR\r\n",1000,DEBUG); //get ip address
  //-----application setup codes listed HERE-----
  Serial.println("DHT11 test");
  dht.begin();
  }

void loop() {
  if (mode==LOW) {setupWifi();} //system use please do not edit
  else { //-----application loop codes listed HERE-----  
    float h=dht.readHumidity();        //get humidity from DHT11
    float c=dht.readTemperature();     //get temperature (C) from DHT11
    float f=dht.readTemperature(true); //get temperature (F) from DHT11
    //create data string to Thingspeak
    String data="GET /update?api_key=" + api_key + "&field1=" + (String)c +
                "&field2=" + (String)f + "&field3=" + (String)h + "\r\n";
    String cmd="AT+CIPSTART=\"TCP\",\"184.106.153.149\",80\r\n";
    sendData(cmd, 1000, DEBUG);
    cmd="AT+CIPSEND=";
    cmd += data.length();
    cmd += "\r\n";
    sendData(cmd,1000,DEBUG);
    String res=sendData(data, 2000, DEBUG);
    res.replace("\r\n",""); //remove all line terminator
    if (res.indexOf("Unlink") == -1) { //if no auto unlink
      sendData("AT+CIPCLOSE\r\n",2000,DEBUG); //close session
      }
    delay(5000);
    }
  }

void setupWifi() { //system use please do not edit
  if (esp8266.available()) { // check if the esp is sending a message
    if (esp8266.find("+IPD,")) {
      delay(1000);
      //esp8266 link response : +IPD,0,498:GET / HTTP/1.1
      //retrieve connection ID from response (0~4, after "+IPD,")
      int connectionId=esp8266.read()-48;  //from ASCII to number
      //subtract 48 because read() returns ASCII decimal value
      //and in ASCII, "0" (the first decimal number) starts at 48
      if (esp8266.find("GET /")) { //retrieve page name (router)
        memset(buffer, 0, sizeof(buffer));  //clear buffer (all set to 0)
        if (esp8266.readBytesUntil('/', buffer, sizeof(buffer))) {
          if (strcmp(buffer, "update") == 0) { //update wifi
            //"?ssid=aaa&pwd=bbb HTTP/1.1"        
            esp8266.find("?ssid="); //skip ssid token
            memset(buffer, 0, sizeof(buffer));  //clear buffer (all set to 0)
            esp8266.readBytesUntil('&', buffer, sizeof(buffer)); //retrieve ssid
            String ssid=buffer;
            esp8266.find("pwd="); //skip pwd token
            memset(buffer, 0, sizeof(buffer));  //clear buffer (all set to 0)
            esp8266.readBytesUntil(' ', buffer, sizeof(buffer)); //retrieve pwd
            String pwd=buffer;
            //configure as a station
            String res=sendData("AT+CWJAP=\"" + ssid + "\",\"" + pwd + "\"\r\n",6000,DEBUG);
                 
            //show setup result
            String webpage="<html>Wifi setup ";
            if (res.indexOf("OK") != -1) {webpage += "OK!</html>";}
            else {webpage += "Failed!</html>";}
            String cipSend="AT+CIPSEND=";
            cipSend += connectionId;
            cipSend += ",";
            cipSend +=webpage.length();
            cipSend +="\r\n";
            sendData(cipSend,1000,DEBUG);
            sendData(webpage,2000,DEBUG);
       
            String closeCommand = "AT+CIPCLOSE=";
            closeCommand+=connectionId; // append connection id
            closeCommand+="\r\n";
            sendData(closeCommand,3000,DEBUG);            
            }
          else { //show setup page
            String webpage="<html><form method=get action='/update/'>SSID ";
            webpage += "<input name=ssid type=text><br>";
            String cipSend = "AT+CIPSEND=";
            cipSend += connectionId;
            cipSend += ",";
            cipSend +=webpage.length();
            cipSend +="\r\n";
            sendData(cipSend,1000,DEBUG);
            sendData(webpage,2000,DEBUG);

            webpage="PWD <input name=pwd type=text> ";
            cipSend = "AT+CIPSEND=";
            cipSend += connectionId;
            cipSend += ",";
            cipSend +=webpage.length();
            cipSend +="\r\n";
            sendData(cipSend,1000,DEBUG);
            sendData(webpage,2000,DEBUG);

            webpage="<input type=submit value=Connect></form></html>";
            cipSend = "AT+CIPSEND=";
            cipSend += connectionId;
            cipSend += ",";
            cipSend +=webpage.length();
            cipSend +="\r\n";
            sendData(cipSend,1000,DEBUG);
            sendData(webpage,2000,DEBUG);

            String closeCommand = "AT+CIPCLOSE=";
            closeCommand+=connectionId; // append connection id
            closeCommand+="\r\n";
            sendData(closeCommand,3000,DEBUG);
            }
          }
        }
      }
    }
  }

//system use please do not edit
String sendData(String command, const int timeout, boolean debug) {
  String response="";
  esp8266.print(command); // send the read character to the esp8266
  long int time=millis();
  while ((time+timeout) > millis()) {
    while(esp8266.available()) {
      // The esp has data so display its output to the serial window
      char c=esp8266.read(); // read the next character.
      response += c;
      }
    }
  if (debug) {Serial.print(response);}
  return response;
  }

這裡板子開機後進入工作模式, ESP8266 會自動連上 AP, 我們只要使用 AT+CIPSTART 指令與 Thingspeak 伺服器 (使用 IP=184.106.153.149 或者網址 api.thingspeak.com) 建立一個 TCP 連線 :

AT+CIPSTART="TCP","184.106.153.149",80

或者

AT+CIPSTART="TCP","api.thingspeak.com",80

然後製作 HTTP 的 GET 方法字串, 裡面帶上我們從 DHT11 取得的溫溼度, 其格式如下 :

GET update?api_key=寫入金鑰&欄位1=數值1&欄位2=數值2&欄位3=數值3
其中的欄位名稱我在 Thingspeak 分別為攝氏溫度, 華氏溫度, 以及濕度取名為 field1, field2, 與 field3, 因此傳送出去的字串是向下面這樣 :

GET /update?api_key=NO5N8C7T2KINFCQE&field1=29.00&field2=84.20&field3=73.00

然後用 AT+CIPSEND 指令告訴 ESP8266 要傳送的 GET 字串的長度 :

AT+CIPSEND=GET 字串長度

上面的程式中我是用字串的 length() 函式來取得字串長度, 注意, 因為 length() 傳回的是整數, 所以必須用 (String) 強制轉型 :

cmd="AT+CIPSEND=" + cmd + (String)data.length() + "\r\n";

不過上面程式中我是一個一個用 += 串接, 單獨時會自動轉型.

最後將 GET 字串當資料傳給伺服器, 其 API 就會將溫溼度數據寫入物聯網伺服器的資料庫了. 通常 Thingspeak 伺服器在收到我們傳送的資料後會自動拆除 TCP 連線 (ESP8266 會回應 Unlink), 但偶而不會, 因此我參考之前從 foxhollow 擷取網路時間的做法, 從 sendData() 函式的傳回字串中分析是否有收到 Unlink, 沒有的話才對 ESP8266 下 AT+CIPCLOSE 關閉 TCP 連線, 有的話就不需要. 其實一律都下 AT+CIPCLOSE 也沒關係, 只是在有自動 Unlink 情況時, 再下 AT+CIPCLOSE 會回應 Error 而已. 參考 :

從 HTTP 網頁伺服器獲取時間訊息的方法

序列埠監控視窗攫取之訊息如下 :

AT+RST


OK
bB�鑭b禔R��"墽B�侒��餾�
[System Ready, Vendor:www.ai-thinker.com]
AT+GMR

0018000902

OK
AT+CWMODE=1

no change
AT+CIFSR

192.168.2.111

OK
DHT11 test
AT+CIPSTART="TCP","184.106.153.149",80

AT+CIPSEND=77

busy p...

OK
Linked
GET /update?api_key=NO5N8C7T2KINFCQE&field1=30.00&field2=86.00&

ERROR
AT+CIPCLOSE


OK
Unlink
AT+CIPSTART="TCP","184.106.153.149",80


OK
Linked
AT+CIPSEND=77

> GET /update?api_key=NO5N8C7T2KINFCQE&field1=30.00&field2=86.00&
SEND OK

+IPD,5:19839
OK

OK
Unlink
AT+CIPSTART="TCP","184.106.153.149",80


OK
Linked
AT+CIPSEND=77

> GET /update?api_key=NO5N8C7T2KINFCQE&field1=30.00&field2=86.00&
SEND OK

+IPD,1:0
OK

OK
Unlink
AT+CIPSTART="TCP","184.106.153.149",80


OK
Linked
AT+CIPSEND=77

> GET /update?api_key=NO5N8C7T2KINFCQE&field1=30.00&field2=86.00&
SEND OK

+IPD,5:19840
OK

OK
Unlink

進入 Thingspeak 才發現距離我上次傳送數據到伺服器已經 11 個月, 快一年囉! 等監控鳥屋做好, 我就會放一個溫溼度, 氣壓, 與光度計進去, 無時無刻都在測候啦!


接下來測試 3 就要加入一個光敏電阻當光度計了, 參考去年 10 月寫的這篇 :

# Arduino 光敏電阻測試

我在麵包板上 DHT11 旁邊插上一個 10K 電阻與光敏電阻串聯, 光敏電阻接地, 10K 電阻接 VCC, 中間接到 Arduino 的 A0 腳 :



程式如下 :

測試 3 :

#include <SoftwareSerial.h>
#define DEBUG true
//-----application include & def listed HERE-----
#include "DHT.h"
#define DHTPIN 2  
#define DHTTYPE DHT11

//system use please do not edit
SoftwareSerial esp8266(7,8); //(RX,TX)
const int SW_PIN=4; //Pin to switch configuration or working mode
const int MAX_PAGE_NAME_LEN=48;  //buffer size
char buffer[MAX_PAGE_NAME_LEN + 1]; //store page_name/ssid/pwd
int mode; //store current mode(LOW=wifi setup, HIGH=working)

//-----application global variables listed HERE-----
DHT dht(DHTPIN, DHTTYPE); // Initialize DHT sensor
String api_key="NO5N8C7T2KINFCQE";  //Thingspeak API Write Key

void setup() {
  //system use please do not edit
  Serial.begin(9600);
  esp8266.begin(9600);
  sendData("AT+RST\r\n",2000,DEBUG); // reset ESP8266
  pinMode(SW_PIN, INPUT);
  mode=digitalRead(SW_PIN);
  sendData("AT+GMR\r\n",1000,DEBUG);
  if (mode==LOW) { //setup mode : for wifi configuration
    sendData("AT+CWMODE=3\r\n",1000,DEBUG); //configure as access point
    sendData("AT+CIPMUX=1\r\n",1000,DEBUG); //enable multiple connections
    sendData("AT+CIPSERVER=1,80\r\n",2000,DEBUG); //turn on server 80 port    
    }
  else {  //working mode : for running application
    sendData("AT+CWMODE=1\r\n",1000,DEBUG); //configure as a station
    delay(3000); //wait for wifi connection to get local ip
    }
  sendData("AT+CIFSR\r\n",1000,DEBUG); //get ip address
  //-----application setup codes listed HERE-----
  Serial.println("DHT11 test");
  dht.begin();
  }

void loop() {
  if (mode==LOW) {setupWifi();} //system use please do not edit
  else { //-----application loop codes listed HERE-----  
    float h=dht.readHumidity();        //get humidity from DHT11
    float c=dht.readTemperature();     //get temperature (C) from DHT11
    float f=dht.readTemperature(true); //get temperature (F) from DHT11
    int cds=analogRead(A0);            //get cds voltage (0~5V to 0~1023 dark)
    int l=100-map(cds,0,1023,0,100);   //map 0~1023(dark) to 0(dark)~100%  
    //create data string to Thingspeak
    String data="GET /update?api_key=" + api_key + "&field1=" + (String)c + 
                "&field2=" + (String)f + "&field3=" + (String)h + "&field4=" + 
                (String)l + "\r\n";
    Serial.println(data);
    String cmd="AT+CIPSTART=\"TCP\",\"184.106.153.149\",80\r\n";
    sendData(cmd, 1000, DEBUG);
    cmd="AT+CIPSEND=" + (String)data.length() + "\r\n";
    sendData(cmd,1000,DEBUG);
    String res=sendData(data, 2000, DEBUG);
    res.replace("\r\n",""); //remove all line terminator
    if (res.indexOf("Unlink") == -1) { //if no auto unlink
      sendData("AT+CIPCLOSE\r\n",2000,DEBUG); //close session
      }
    delay(5000);
    }
  }

void setupWifi() { //system use please do not edit
  if (esp8266.available()) { // check if the esp is sending a message
    if (esp8266.find("+IPD,")) {
      delay(1000);
      //esp8266 link response : +IPD,0,498:GET / HTTP/1.1
      //retrieve connection ID from response (0~4, after "+IPD,")
      int connectionId=esp8266.read()-48;  //from ASCII to number
      //subtract 48 because read() returns ASCII decimal value
      //and in ASCII, "0" (the first decimal number) starts at 48
      if (esp8266.find("GET /")) { //retrieve page name (router)
        memset(buffer, 0, sizeof(buffer));  //clear buffer (all set to 0)
        if (esp8266.readBytesUntil('/', buffer, sizeof(buffer))) {
          if (strcmp(buffer, "update") == 0) { //update wifi
            //"?ssid=aaa&pwd=bbb HTTP/1.1"        
            esp8266.find("?ssid="); //skip ssid token
            memset(buffer, 0, sizeof(buffer));  //clear buffer (all set to 0)
            esp8266.readBytesUntil('&', buffer, sizeof(buffer)); //retrieve ssid
            String ssid=buffer;
            esp8266.find("pwd="); //skip pwd token
            memset(buffer, 0, sizeof(buffer));  //clear buffer (all set to 0)
            esp8266.readBytesUntil(' ', buffer, sizeof(buffer)); //retrieve pwd
            String pwd=buffer;
            //configure as a station
            String res=sendData("AT+CWJAP=\"" + ssid + "\",\"" + pwd + "\"\r\n",6000,DEBUG);
                 
            //show setup result
            String webpage="<html>Wifi setup ";
            if (res.indexOf("OK") != -1) {webpage += "OK!</html>";}
            else {webpage += "Failed!</html>";}
            String cipSend="AT+CIPSEND=";
            cipSend += connectionId;
            cipSend += ",";
            cipSend +=webpage.length();
            cipSend +="\r\n";
            sendData(cipSend,1000,DEBUG);
            sendData(webpage,2000,DEBUG);
       
            String closeCommand = "AT+CIPCLOSE=";
            closeCommand+=connectionId; // append connection id
            closeCommand+="\r\n";
            sendData(closeCommand,3000,DEBUG);            
            }
          else { //show setup page
            String webpage="<html><form method=get action='/update/'>SSID ";
            webpage += "<input name=ssid type=text><br>";
            String cipSend = "AT+CIPSEND=";
            cipSend += connectionId;
            cipSend += ",";
            cipSend +=webpage.length();
            cipSend +="\r\n";
            sendData(cipSend,1000,DEBUG);
            sendData(webpage,2000,DEBUG);

            webpage="PWD <input name=pwd type=text> ";
            cipSend = "AT+CIPSEND=";
            cipSend += connectionId;
            cipSend += ",";
            cipSend +=webpage.length();
            cipSend +="\r\n";
            sendData(cipSend,1000,DEBUG);
            sendData(webpage,2000,DEBUG);

            webpage="<input type=submit value=Connect></form></html>";
            cipSend = "AT+CIPSEND=";
            cipSend += connectionId;
            cipSend += ",";
            cipSend +=webpage.length();
            cipSend +="\r\n";
            sendData(cipSend,1000,DEBUG);
            sendData(webpage,2000,DEBUG);

            String closeCommand = "AT+CIPCLOSE=";
            closeCommand+=connectionId; // append connection id
            closeCommand+="\r\n";
            sendData(closeCommand,3000,DEBUG);
            }
          }
        }
      }
    }
  }

//system use please do not edit
String sendData(String command, const int timeout, boolean debug) {
  String response="";
  esp8266.print(command); // send the read character to the esp8266
  long int time=millis();
  while ((time+timeout) > millis()) {
    while(esp8266.available()) {
      // The esp has data so display its output to the serial window
      char c=esp8266.read(); // read the next character.
      response += c;
      }
    }
  if (debug) {Serial.print(response);}
  return response;
  }

上面程式我特別將 data 變數輸出, 因為在測試 2 串列埠所擷取的訊息裡, 發現 send 出去的字串不全, 只顯示到 field2, 但實際上又有傳送到 Thingspeak, 所以用 println() 印出來看看原始字串是否有問題. 我把字串貼到 Word 去計算字元數, 剛好是 64 字元, 我想可能是軟體串列埠緩衝器設為 64 字元的關係.

在 Thingspeak 以前就有設定 field4 來顯示光度, 所以也不用修改, 程式上傳後馬上就可以看到第四欄位有資料了 :


此程式編譯後之記憶體損耗不到一半 :

草稿碼使用了 14,774 bytes (48%) 的程式存儲空間。最大值為 30,720 bytes。
全域變數使用了 920 bytes (44%) 的動態記憶體,剩餘 1,128 bytes 供局部變數。最大值為 2,048 bytes 。

好了, 此次於颱風天用 IOT 模組重做實驗到這裡就完成了. Adafruit 的函式庫沒問題的, 如果數據有問題, 可能是 DHT11 壞掉了.

上面三個程式看起來很複雜, 其實是因為我使用自製的 Arduino+ESP8266 物聯網模組之故, 為了能隨時用手機瀏覽器更改要連線的無線基地台, 程式需要 setWifi() 與 sendData() 函式來完成此功能. 參考 :

# 製作 Arduino Nano + ESP8266 物聯網模組 (三)


如果只是固定連線某個 AP 的話, 只要以 USB-TTL 線將 ESP8266 要連接的 AP 用 AT+CWJAP 指令設定好, 以後開機就會自動連上該 AP, 這樣上面的測候程式就變得很簡短 :

#include <SoftwareSerial.h>
#include "DHT.h"
#define DEBUG true
#define DHTPIN 2  
#define DHTTYPE DHT11

SoftwareSerial esp8266(7,8); //(RX,TX)
DHT dht(DHTPIN, DHTTYPE); // Initialize DHT sensor
String api_key="NO5N8C7T2KINFCQE";  //Thingspeak API Write Key

void setup() {
  Serial.begin(9600);
  esp8266.begin(9600);
  sendData("AT+RST\r\n",2000,DEBUG); // reset ESP8266
  sendData("AT+GMR\r\n",1000,DEBUG);
  sendData("AT+CIFSR\r\n",1000,DEBUG); //get ip address
  Serial.println("DHT11 test");
  dht.begin();
  }

void loop() {
  float h=dht.readHumidity();        //get humidity from DHT11
  float c=dht.readTemperature();     //get temperature (C) from DHT11
  float f=dht.readTemperature(true); //get temperature (F) from DHT11
  int cds=analogRead(A0);            //get cds voltage (0~5V to 0~1023 dark)
  int l=100-map(cds,0,1023,0,100);   //map 0~1023(dark) to 0(dark)~100%  
  //create data string to Thingspeak
  String data="GET /update?api_key=" + api_key + "&field1=" + (String)c +
              "&field2=" + (String)f + "&field3=" + (String)h + "&field4=" +
              (String)l + "\r\n";
  Serial.println(data);
  String cmd="AT+CIPSTART=\"TCP\",\"184.106.153.149\",80\r\n";
  sendData(cmd, 1000, DEBUG);
  cmd="AT+CIPSEND=" + (String)data.length() + "\r\n";
  sendData(cmd,1000,DEBUG);
  String res=sendData(data, 2000, DEBUG);
  res.replace("\r\n",""); //remove all line terminator
  if (res.indexOf("Unlink") == -1) { //if no auto unlink
    sendData("AT+CIPCLOSE\r\n",2000,DEBUG); //close session
    }
  delay(5000);
  }

String sendData(String command, const int timeout, boolean debug) {
  String response="";
  esp8266.print(command); // send the read character to the esp8266
  long int time=millis();
  while ((time+timeout) > millis()) {
    while(esp8266.available()) {
      // The esp has data so display its output to the serial window
      char c=esp8266.read(); // read the next character.
      response += c;
      }
    }
  if (debug) {Serial.print(response);}
  return response;
  }

編譯後記憶體損耗減少 10% 以上 :

草稿碼使用了 11,872 bytes (38%) 的程式存儲空間。最大值為 30,720 bytes。
全域變數使用了 539 bytes (26%) 的動態記憶體,剩餘 1,509 bytes 供局部變數。最大值為 2,048 bytes 。

參考 :

# Arduino教學-使用ESP8266 wifi模組+DHT 溫溼度感測器上傳thingspeak


2 則留言 :

Unknown 提到...

您好,看了您的blog後,受益良多,不好意思想跟您請教一下,當網頁顯示溫濕度後,想進一步探討網頁撰寫(設定警報值,當溫度超出設定值,發出警報),因對網頁撰寫像一張白紙,請問您有相關的書籍或其他可介紹嗎?謝謝!!

小狐狸事務所 提到...

您好, 既然您對 Arduino 與網頁設計有興趣, 趙英傑的 "超圖解 Arduino 互動設計入門" 這本書裡面有對 HTML, HTTP 等網頁基本概念的圖解介紹以及與 Arduino 的搭配.