2016年7月31日 星期日

★ 利用網頁控制 Arduino (二)

又到周末了, 昨天早上岳母突然打電話問我幾點回鄉下, 想要搭我便車回去, 我說是傍晚, 因為姊姊下午要去畫室, 傍晚要上數學, 時間比較趕, 我騎摩托車接她回來比較快. 姐姐聽我這樣說, 就說她可以搭捷運也很快, 所以早上便回鄉下了. 吃過中飯後在冰箱找到上週買的冰棒, 實在太棒了! 這夏日的午後悶熱難耐, 還是躲在書房吹電扇寫程式做實驗快活些.

今天繼續用 Arduino + ESP8266 來做  "Arduino Cookbook 錦囊妙計這本書 15.8 節的實驗, 它是在 15.7 節的基礎上擴展為依據要求不同的網址, 可顯示多個不同的網頁, 具體來說, 例如要求 192.168.2.114/digital/ 就顯示所有數位腳位的狀態; 而要求 192.168.2.114/analog/ 就顯示所有類比腳位的狀態. 事實上只要參考之前製作 IOT 模組時寫的 wifi 設定功能程式碼, 修改一下 15.7 節程式中的路徑處理部分就可以了, 參考 :

# 利用網頁控制 Arduino (一)
# 製作 Arduino Nano + ESP8266 物聯網模組 (三)

測試 1 :

#include <SoftwareSerial.h>
#define DEBUG true

SoftwareSerial esp8266(7,8); //(RX,TX)
String ssid="EDIMAX-tony";
String pwd="1234567890";
const int MAX_PAGE_NAME_LEN=8;  //buffer size
char buffer[MAX_PAGE_NAME_LEN + 1]; //store page_name

void setup() {
  Serial.begin(9600);
  esp8266.begin(9600);
  sendData("AT+RST\r\n",2000,DEBUG); // reset ESP8266
  sendData("AT+CWMODE=1\r\n",1000,DEBUG); //configure as station
  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  
  while (!connectWifi(ssid, pwd)) {
    Serial.println("Connecting WiFi ... failed");
    delay(2000);
    }
  sendData("AT+GMR\r\n",1000,DEBUG);
  delay(3000); //wait for wifi connection to get local ip
  sendData("AT+CIFSR\r\n",1000,DEBUG); //get ip address
  }

void loop() {
  if (esp8266.available()) { // check if esp8266 is sending message
    if (esp8266.find("+IPD,")) {
      delay(1000); //waiting for response: ?pinD2=1&pinA2=1023
      int connectionId=esp8266.read()-48;  //turn ASCII to number
      if (esp8266.find("GET /")) { //retrieve page name (router)
        memset(buffer, 0, sizeof(buffer));  //clear buffer (all set to 0)
        String webpage="<html><body>";
        if (esp8266.readBytesUntil('/', buffer, sizeof(buffer))) {
           if (strcmp(buffer, "analog") == 0) { //show analog pins
            Serial.println("analog");
            webpage += "<h1>Analog Pins</h1>";
            for (byte i=0; i<6; i++) {
              webpage += "analog pin ";
              webpage += i;
              webpage += " is ";
              webpage += analogRead(i);
              webpage += "<br>";
              }
            }
          else if (strcmp(buffer, "digital") == 0) {
            Serial.println("digital");
            webpage += "<h1>Digital Pins</h1>";          
            for (byte i=2; i<14; i++) {
              webpage += "digital pin ";
              webpage += i;
              webpage += " is ";
              if (digitalRead(i) == 1) {webpage += "HIGH";}
              else {webpage += "LOW";}
              webpage += "<br>";
              }    
            }
          else {
            webpage += "<h1>Unknown Page</h1>";
            webpage += "Recognized Pages are : <br>";
            webpage += "192.168.x.x/analog/<br>";
            webpage += "192.168.x.x/digital/<br>";
            }
          }
        webpage += "</body></html>";
        String cipSend="AT+CIPSEND=";
        cipSend += connectionId;
        cipSend += ",";
        cipSend += webpage.length();
        cipSend += "\r\n";
        sendData(cipSend,1000,DEBUG);
        sendData(webpage,2000,DEBUG);
        sendData("AT+CIPCLOSE=" + (String)connectionId + "\r\n",3000,DEBUG);        
        }
      }
    }
  }

boolean connectWifi(String ssid, String pwd) {
  String res=sendData("AT+CWJAP=\"" + ssid + "\",\"" + pwd + "\"\r\n",8000,DEBUG);
  res.replace("\r\n",""); //remove all line terminator
  if (res.indexOf("OK") != -1) {return true;}
  else {return false;}
  }

String sendData(String command, const int timeout, boolean debug) {
  String res="";
  esp8266.print(command);
  long int time=millis();
  while ((time + timeout) > millis()) {
    while(esp8266.available()) {res.concat((char)esp8266.read());}
    }
  if (debug) {Serial.print(res);}
  return res;
  }

因為要擷取網址根目錄下的路徑並進行比對以決定輸出何種網頁, 需要一個 字元陣列 buffer 來儲存路徑, 因為 digital 有 7 個字元, 因此這裡宣告一個長度為 8 字元的緩衝區即足夠.

如果客戶端連線 192.168.x.x/digial/ 的話, ESP8266 會回應含有 GET /digital/ HTTP1.1 標頭的訊息到緩衝區裡, 因此可用 find() 函數找到連線要求的標記 "GET /", 然後以 readBytesUntil() 找尋下一個 "/", 將之間的路徑字元串 digital 存入 buffer 裡, 這樣便能用 strcmp() 函數來比對了.

此程式編譯後記憶體耗用情形如下 :

草稿碼使用了 9,666 bytes (31%) 的程式存儲空間。最大值為 30,720 bytes。
全域變數使用了 804 bytes (39%) 動態記憶體,剩餘 1,244 bytes 的局部變數。最大值為 2,048 bytes 。

程式上傳後從序列埠監控視窗得知獲得 192.168.2.106 的 IP :

AT+RST


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

no change
AT+CIPMUX=1


OK
AT+CIPSERVER=1,80


OK
AT+CWJAP="EDIMAX-tony1966","1234567890"


OK
AT+GMR

0018000902

OK
AT+CIFSR

192.168.2.106

OK

以手機瀏覽器連線 ESP8266 伺服器, 分別輸入 192.168.2.106/digital/, 192.168.2.106/analog/, 以及 192.168.2.106/hello/, 結果如下 :




我們也可以把前一篇實驗裡更改 Arduino 輸出腳位狀態的功能整合進來, 增加一個 update 路徑如下 :

192.168.x.x/update/?pinD3=1&pinA2=1023

程式如下 :

測試 2 :

#include <SoftwareSerial.h>
#define DEBUG true

SoftwareSerial esp8266(7,8); //(RX,TX)
String ssid="EDIMAX-tony";
String pwd="1234567890";
const int MAX_PAGE_NAME_LEN=8;  //buffer size
char buffer[MAX_PAGE_NAME_LEN + 1]; //store page_name

void setup() {
  Serial.begin(9600);
  esp8266.begin(9600);
  sendData("AT+RST\r\n",2000,DEBUG); // reset ESP8266
  sendData("AT+CWMODE=1\r\n",1000,DEBUG); //configure as station
  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  
  while (!connectWifi(ssid, pwd)) {
    Serial.println("Connecting WiFi ... failed");
    delay(2000);
    }
  sendData("AT+GMR\r\n",1000,DEBUG);
  delay(3000); //wait for wifi connection to get local ip
  sendData("AT+CIFSR\r\n",1000,DEBUG); //get ip address
  }

void loop() {
  if (esp8266.available()) { // check if esp8266 is sending message
    if (esp8266.find("+IPD,")) {
      delay(1000); //waiting for response: ?pinD2=1&pinA2=1023
      int connectionId=esp8266.read()-48;  //turn ASCII to number
      if (esp8266.find("GET /")) { //retrieve page name (router)
        memset(buffer, 0, sizeof(buffer));  //clear buffer (all set to 0)
        String webpage="<html><body>";
        if (esp8266.readBytesUntil('/', buffer, sizeof(buffer))) {
           if (strcmp(buffer, "analog") == 0) { //show analog pins
            Serial.println("analog");
            webpage += "<h1>Analog Pins</h1>";
            for (byte i=0; i<6; i++) {
              webpage += "analog pin ";
              webpage += i;
              webpage += " is ";
              webpage += analogRead(i);
              webpage += "<br>";
              }
            }
          else if (strcmp(buffer, "digital") == 0) {
            Serial.println("digital");
            webpage += "<h1>Digital Pins</h1>";          
            for (byte i=2; i<14; i++) {
              webpage += "digital pin ";
              webpage += i;
              webpage += " is ";
              if (digitalRead(i) == 1) {webpage += "HIGH";}
              else {webpage += "LOW";}
              webpage += "<br>";
              }    
            }
          else if (strcmp(buffer, "update") == 0) {
            Serial.println("update");
            while (esp8266.findUntil("pin", "\r\n")) {
              char type=(char)esp8266.read();
              int pin=esp8266.parseInt();
              int val=esp8266.parseInt();
              if (type=='D') {
                Serial.print("Digital pin ");
                webpage += "Digital pin ";
                webpage += pin;
                webpage += " is updated to";
                webpage += val;
                pinMode(pin, OUTPUT);
                digitalWrite(pin, val);
                }
              else if (type=='A') {
                Serial.print("Analog pin ");
                webpage += "Analog pin ";
                webpage += pin;
                webpage += " is updated to ";
                webpage += val;                
                analogWrite(pin, val);        
                }
              else {
                Serial.println("Unexpected type:" + type);
                }
              Serial.print(pin);
              Serial.print("=");
              Serial.println(val);  
              webpage += "<br>";       
              }   
            }            
          else {
            webpage += "<h1>Unknown Page</h1>";
            webpage += "Recognized Pages are : <br>";
            webpage += "192.168.x.x/analog/<br>";
            webpage += "192.168.x.x/digital/<br>";
            }
          }
        webpage += "</body></html>";
        String cipSend="AT+CIPSEND=";
        cipSend += connectionId;
        cipSend += ",";
        cipSend += webpage.length();
        cipSend += "\r\n";
        sendData(cipSend,1000,DEBUG);
        sendData(webpage,2000,DEBUG);
        sendData("AT+CIPCLOSE=" + (String)connectionId + "\r\n",3000,DEBUG);        
        }
      }
    }
  }

boolean connectWifi(String ssid, String pwd) {
  String res=sendData("AT+CWJAP=\"" + ssid + "\",\"" + pwd + "\"\r\n",8000,DEBUG);
  res.replace("\r\n",""); //remove all line terminator
  if (res.indexOf("OK") != -1) {return true;}
  else {return false;}
  }

String sendData(String command, const int timeout, boolean debug) {
  String res="";
  esp8266.print(command);
  long int time=millis();
  while ((time + timeout) > millis()) {
    while(esp8266.available()) {res.concat((char)esp8266.read());}
    }
  if (debug) {Serial.print(res);}
  return res;
  }

此程式編譯後記憶體耗用情形如下 :

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

執行結果如下 :


在上面的實驗中, 路徑 digital 與 analog 的腳位狀態也可以用 HTML 的表格來呈現, 這樣會比較整齊美觀, 這也是  "Arduino Cookbook 錦囊妙計這本書 15.9 節的實驗, 其實也不過就是在輸出網頁格式上加上 TABLE 的相關元素而已, 如下列測試 3 所示 :

測試 3 :

#include <SoftwareSerial.h>
#define DEBUG true

SoftwareSerial esp8266(7,8); //(RX,TX)
String ssid="EDIMAX-tony";
String pwd="1234567890";
const int MAX_PAGE_NAME_LEN=8;  //buffer size
char buffer[MAX_PAGE_NAME_LEN + 1]; //store page_name

void setup() {
  Serial.begin(9600);
  esp8266.begin(9600);
  sendData("AT+RST\r\n",2000,DEBUG); // reset ESP8266
  sendData("AT+CWMODE=1\r\n",1000,DEBUG); //configure as station
  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  
  while (!connectWifi(ssid, pwd)) {
    Serial.println("Connecting WiFi ... failed");
    delay(2000);
    }
  sendData("AT+GMR\r\n",1000,DEBUG);
  delay(3000); //wait for wifi connection to get local ip
  sendData("AT+CIFSR\r\n",1000,DEBUG); //get ip address
  }

void loop() {
  if (esp8266.available()) { // check if esp8266 is sending message
    if (esp8266.find("+IPD,")) {
      delay(1000); //waiting for response: ?pinD2=1&pinA2=1023
      int connectionId=esp8266.read()-48;  //turn ASCII to number
      if (esp8266.find("GET /")) { //retrieve page name (router)
        memset(buffer, 0, sizeof(buffer));  //clear buffer (all set to 0)
        String webpage="<html><body>";
        if (esp8266.readBytesUntil('/', buffer, sizeof(buffer))) {
           if (strcmp(buffer, "analog") == 0) { //show analog pins
            Serial.println("analog");
            webpage += "<h1>Analog Pins</h1>";
            webpage += "<table border=1>";
            for (byte i=0; i<6; i++) {
              webpage += "<tr><td>analog pin ";
              webpage += i;
              webpage += "</td><td>";
              webpage += analogRead(i);
              webpage += "</td></tr>";
              }
            webpage += "</table>";
            }
          else if (strcmp(buffer, "digital") == 0) {
            Serial.println("digital");
            webpage += "<h1>Digital Pins</h1>";
            webpage += "<table border=1>";          
            for (byte i=2; i<=9; i++) {
              webpage += "<tr><td>digital pin ";
              webpage += i;
              webpage += "</td><td>";
              if (digitalRead(i) == 1) {webpage += "HIGH";}
              else {webpage += "LOW";}
              webpage += "</td></tr>";
              }
            webpage += "</table>";  
            }
          else if (strcmp(buffer, "update") == 0) {
            Serial.println("update");
            while (esp8266.findUntil("pin", "\r\n")) {
              char type=(char)esp8266.read();
              int pin=esp8266.parseInt();
              int val=esp8266.parseInt();
              if (type=='D') {
                Serial.print("Digital pin ");
                webpage += "Digital pin ";
                webpage += pin;
                webpage += " is updated to";
                webpage += val;
                pinMode(pin, OUTPUT);
                digitalWrite(pin, val);
                }
              else if (type=='A') {
                Serial.print("Analog pin ");
                webpage += "Analog pin ";
                webpage += pin;
                webpage += " is updated to ";
                webpage += val;              
                analogWrite(pin, val);      
                }
              else {
                Serial.println("Unexpected type:" + type);
                }
              Serial.print(pin);
              Serial.print("=");
              Serial.println(val);
              webpage += "<br>";    
              }
            }          
          else {
            webpage += "<h1>Unknown Page</h1>";
            webpage += "Recognized Pages are : <br>";
            webpage += "192.168.x.x/analog/<br>";
            webpage += "192.168.x.x/digital/<br>";
            }
          }
        webpage += "</body></html>";
        String cipSend="AT+CIPSEND=";
        cipSend += connectionId;
        cipSend += ",";
        cipSend += webpage.length();
        cipSend += "\r\n";
        sendData(cipSend,1000,DEBUG);
        sendData(webpage,5000,DEBUG);
        sendData("AT+CIPCLOSE=" + (String)connectionId + "\r\n",3000,DEBUG);        
        }
      }
    }
  }

boolean connectWifi(String ssid, String pwd) {
  String res=sendData("AT+CWJAP=\"" + ssid + "\",\"" + pwd + "\"\r\n",8000,DEBUG);
  res.replace("\r\n",""); //remove all line terminator
  if (res.indexOf("OK") != -1) {return true;}
  else {return false;}
  }

String sendData(String command, const int timeout, boolean debug) {
  String res="";
  esp8266.print(command);
  long int time=millis();
  while ((time + timeout) > millis()) {
    while(esp8266.available()) {res.concat((char)esp8266.read());}
    }
  if (debug) {Serial.print(res);}
  return res;
  }

此程式與上面兩個測試差別僅僅是在顯示數位與類比腳位部分添加 HTML 的表格元素而已 (藍色部分), 編譯後記憶體耗用情形如下 :

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

以手機連覽器連線 192.168.2.106/digital/  結果如下 :

連線 192.168.2.106/analog/ 結果如下 :


注意, 上面程式中只顯示了 D2~D9 這 8 個數位腳狀態, 而非如測試 1 中顯示 D2~D13, 原因是若顯示到 D10 以上, 瀏覽器會等很久都沒回應, 最後輸出 "無法顯示網頁", 這應該是 HTML 的表格元素佔用了太多 SRAM 記憶體所致.

因此我參考之前所用的 F() 函數, 將字串常數放在程式記憶體 (Flash) 以節省 SRAM 記憶體的技巧來解決此問題, 參考 :

# 使用 ITEAD WeeESP8266 函式庫改寫物聯網模組 wifi 設定程式
https://www.arduino.cc/en/Serial/Print

You can pass flash-memory based strings to Serial.print() by wrapping them with F(). For example :
Serial.print(F(“Hello World”))

修改後程式如下 :

測試 4 :

#include <SoftwareSerial.h>
#define DEBUG true

SoftwareSerial esp8266(7,8); //(RX,TX)
const String ssid="EDIMAX-tony";
const String pwd="1234567890";
const int MAX_PAGE_NAME_LEN=8;  //buffer size
char buffer[MAX_PAGE_NAME_LEN + 1]; //store page_name

void setup() {
  Serial.begin(9600);
  esp8266.begin(9600);
  sendData(F("AT+RST\r\n"),2000,DEBUG); // reset ESP8266
  sendData(F("AT+CWMODE=1\r\n"),1000,DEBUG); //configure as station
  sendData(F("AT+CIPMUX=1\r\n"),1000,DEBUG); //enable multiple connections
  sendData(F("AT+CIPSERVER=1,80\r\n"),2000,DEBUG); //turn on server 80 port  
  while (!connectWifi(ssid, pwd)) {
    Serial.println(F("Connecting WiFi ... failed"));
    delay(2000);
    }
  sendData(F("AT+GMR\r\n"),1000,DEBUG);
  delay(3000); //wait for wifi connection to get local ip
  sendData(F("AT+CIFSR\r\n"),1000,DEBUG); //get ip address
  }

void loop() {
  if (esp8266.available()) { // check if esp8266 is sending message
    if (esp8266.find("+IPD,")) {
      delay(1000); //waiting for response: ?pinD2=1&pinA2=1023
      int connectionId=esp8266.read()-48;  //turn ASCII to number
      if (esp8266.find("GET /")) { //retrieve page name (router)
        memset(buffer, 0, sizeof(buffer));  //clear buffer (all set to 0)
        String webpage=F("<html><body>");
        if (esp8266.readBytesUntil('/', buffer, sizeof(buffer))) {
           if (strcmp(buffer, "analog") == 0) { //show analog pins
            Serial.println(F("analog"));
            webpage += F("<h1>Analog Pins</h1>");
            webpage += F("<table border=1>");
            for (byte i=0; i<6; i++) {
              webpage += F("<tr><td>analog pin ");
              webpage += i;
              webpage += F("</td><td>");
              webpage += analogRead(i);
              webpage += F("</td></tr>");
              }
            webpage += F("</table>");
            }
          else if (strcmp(buffer, "digital") == 0) {
            Serial.println(F("digital"));
            webpage += F("<h1>Digital Pins</h1>");
            webpage += F("<table border=1>");          
            for (byte i=2; i<=13; i++) {
              webpage += F("<tr><td>digital pin ");
              webpage += i;
              webpage += F("</td><td>");
              if (digitalRead(i) == 1) {webpage += F("HIGH");}
              else {webpage += F("LOW");}
              webpage += F("</td></tr>");
              }
            webpage += F("</table>");  
            }
          else if (strcmp(buffer, "update") == 0) {
            Serial.println("update");
            while (esp8266.findUntil("pin", "\r\n")) {
              char type=(char)esp8266.read();
              int pin=esp8266.parseInt();
              int val=esp8266.parseInt();
              if (type=='D') {
                Serial.print(F("Digital pin "));
                webpage += F("Digital pin ");
                webpage += pin;
                webpage += F(" is updated to");
                webpage += val;
                pinMode(pin, OUTPUT);
                digitalWrite(pin, val);
                }
              else if (type=='A') {
                Serial.print(F("Analog pin "));
                webpage += F("Analog pin ");
                webpage += pin;
                webpage += F(" is updated to ");
                webpage += val;              
                analogWrite(pin, val);      
                }
              else {
                Serial.print(F("Unexpected type:"));
                Serial.println(type);
                }
              Serial.print(pin);
              Serial.print("=");
              Serial.println(val);
              webpage += F("<br>");    
              }
            }          
          else {
            webpage += F("<h1>Unknown Page</h1>");
            webpage += F("Recognized Pages are : <br>");
            webpage += F("192.168.x.x/analog/<br>");
            webpage += F("192.168.x.x/digital/<br>");
            }
          }
        webpage += F("</body></html>");
        String cipSend=F("AT+CIPSEND=");
        cipSend += connectionId;
        cipSend += F(",");
        cipSend += webpage.length();
        cipSend += F("\r\n");
        sendData(cipSend,1000,DEBUG);
        sendData(webpage,5000,DEBUG);
        sendData("AT+CIPCLOSE=" + (String)connectionId + F("\r\n"),3000,DEBUG);        
        }
      }
    }
  }

boolean connectWifi(String ssid, String pwd) {
  String res=sendData("AT+CWJAP=\"" + ssid + F("\",\"") + pwd + F("\"\r\n"),8000,DEBUG);
  res.replace("\r\n",""); //remove all line terminator
  if (res.indexOf("OK") != -1) {return true;}
  else {return false;}
  }

String sendData(String command, const int timeout, boolean debug) {
  String res=F("");
  esp8266.print(command);
  long int time=millis();
  while ((time + timeout) > millis()) {
    while(esp8266.available()) {res.concat((char)esp8266.read());}
    }
  if (debug) {Serial.print(res);}
  return res;
  }

此處我將所有可以使用 F() 函數的字串常數都用上了, 少部分沒有使用是因為編譯失敗的關係, 可能是資料型態轉換不合法, 就暫時不去研究了. 經過如此調整, 編譯後記憶體耗用如下 :

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

可見將字串常數放到程式記憶體去後, 動態記憶體耗用比已降至 21%, 以手機瀏覽器連線 192.168.2.106/digital/  結果已能正常輸出網頁矣 :


可見只有 2K SRAM 的 Arduino 真的必須對記憶體的使用錙銖必較啊! 

OK, 接下來要將上面測試 4 改用 WeeESP8266 函式褲來寫, 程式如下 :

測試 5 :

#include <SoftwareSerial.h>
#include "ESP8266.h"

SoftwareSerial esp8266(7,8); //(D7:RX, D8:TX)
ESP8266 wifi(esp8266); //create wifi object
const String ssid="EDIMAX-tony";
const String pwd="1234567890";

void setup() {
  Serial.begin(9600);
  wifi.restart();  //reset ESP8266
  Serial.print(F("Firmware version ... "));
  Serial.println(wifi.getVersion()); //
  boolean ok=true; //default result ok
  ok &= wifi.setOprToStation(); //set esp8266 : mode 1
  ok &= wifi.enableMUX(); //enable multi-connection
  ok &= wifi.startTCPServer(80); //enable sep8266 web server
  if (ok) {Serial.println(F("Enable web server ... OK"));}
  while (!wifi.joinAP(ssid, pwd)) {
    Serial.println(F("Connecting WiFi ... failed"));
    delay(2000);
    }
  delay(3000); //wait for wifi connection to get local ip
  Serial.print(F("IP ... "));
  Serial.println(wifi.getLocalIP()); //show local ip
  }

void loop() {
  uint8_t buffer[128]={0}; //init receiving buffer to store esp response
  uint8_t mux_id; //TCP connection id (0~4)
  uint32_t len=wifi.recv(&mux_id, buffer, sizeof(buffer), 2000); //get response
  if (len > 0) { //data received, fetch requested path from HTTP
    String path=F(""); //init path
    int digitalRequests=0; //digital request counts
    int analogRequests=0; //analog request counts
    for (uint32_t i=0; i<len; i++) { //concat path string
      if (!path.endsWith(F(" HTTP/"))) {path.concat((char)buffer[i]);}
      else {break;}
      } //path : "+IPD,1,248:GET /digital/ HTTP/"
    path=path.substring(path.indexOf(F("/")) + 1, path.indexOf(F(" HTTP/")));
    Serial.println(path); //eg : digital, analog, update/?pinD2=1
    String webpage=F("<html><body>");
    if (path.startsWith("analog")) {
      Serial.println(F("analog"));
      webpage += F("<h1>Analog Pins</h1>");
      webpage += F("<table border=1>");
      for (byte i=0; i<6; i++) {
        webpage += F("<tr><td>analog pin ");
        webpage += i;
        webpage += F("</td><td>");
        webpage += analogRead(i);
        webpage += F("</td></tr>");
        }
      webpage += F("</table>");    
      }
    else if (path.startsWith("digital")) {
      Serial.println(F("digital"));
      webpage += F("<h1>Digital Pins</h1>");
      webpage += F("<table border=1>");          
      for (byte i=2; i<=10; i++) {
        webpage += F("<tr><td>digital pin ");
        webpage += i;
        webpage += F("</td><td>");
        if (digitalRead(i) == 1) {webpage += F("HIGH");}
        else {webpage += F("LOW");}
        webpage += F("</td></tr>");
        }
      webpage += F("</table>");        
      }
    else if (path.startsWith("update")) {
      path=path.substring(path.indexOf(F("?")) + 1); //pinD2=1&pinA3=1023
      while (path.indexOf(F("pin")) != -1) { //parsing not finished
        char type=path.charAt(3);
        int pin=path.substring(4, path.indexOf(F("="))).toInt();
        int val=path.substring(path.indexOf(F("=")) + 1).toInt();
        if (type=='D') {
          Serial.print(F("Digital pin "));
          webpage += F("Digital pin ");
          webpage += pin;
          webpage += F(" is updated to");
          webpage += val;
          pinMode(pin, OUTPUT);
          digitalWrite(pin, val);
          }
        else if  (type=='A') {
          Serial.print(F("Analog pin "));
          webpage += F("Analog pin ");
          webpage += pin;
          webpage += F(" is updated to ");
          webpage += val;              
          analogWrite(pin, val);  
          }
        else {
          Serial.print(F("Unexpected type:"));
          Serial.println(type);
          webpage += F("Unexpected type:");
          webpage += type;
          }
        Serial.print(pin);
        Serial.print(F("="));
        Serial.println(val);
        webpage += F("<br>");
        if (path.indexOf(F("&")) != -1) { //multiple parameters
          path=path.substring(path.indexOf(F("&")) + 1); //remove parameter    
          }
        else {path=F("");} //terminate loop
        }    
      }
    else {
      webpage += F("<h1>Unknown Page</h1>");
      webpage += F("Recognized Pages are : <br>");
      webpage += F("192.168.x.x/analog/<br>");
      webpage += F("192.168.x.x/digital/<br>");
      webpage += F("192.168.x.x/update/?pinD2=1&pinA3=1023<br>");          
      }
    webpage += F("</body></html>");
    wifi.send(mux_id, (const uint8_t*)webpage.c_str(), webpage.length());
    wifi.releaseTCP(mux_id);
    }
  }

有了測試 4 在記憶體耗用上的教訓, 在測試 5 中我已完全將能用 F() 函數來節省的字串常數都優化了, 甚至 ssid 與 pwd 兩個也加上 const 來減少 SRAM 的使用. 編譯後記憶體耗用情形如下 :

稿碼使用了 16,030 bytes (52%) 的程式存儲空間。最大值為 30,720 bytes。
全域變數使用了 725 bytes (35%) 的動態記憶體,剩餘 1,323 bytes 供局部變數。最大值為 2,048 bytes 。

這數字看起來還 OK, 用手機瀏覽器測試也能得到與上面測試 4 一樣的結果, 除了 digital 的部分只能顯示到 D10 外. 請注意上面程式的 digital 迴圈部分, 上限只到 10, 若改為 11 將遇到上面測試 3 網頁無反應問題. 看來看去也找不到哪裡還可以優化, 只好這樣了. 這也是使用函式庫的代價.

另外, 由於字串處理使用 startsWith() 的關係, analog 與 digital 後面的 / 不加也沒關係 :

192.168.2.106/analog
192.168.2.106/digital

這是跟上面的測試不同的地方.

參考 :

# Arduino Uart 互傳字串 與 字串處理 依特定符號切割
Arduino輸入字串切割成陣列
# [C/C++] 切割字串函數:strtok, Network mac address 分割
# How to convert string to char array in C++?
c_str() 用法 (C/C++)

20 則留言 :

五餅二魚水族倉庫 提到...

老師,你文章中說,用ardunio當網頁伺服器,效能不好,那如果用socket的方式,arduino 寫socket server,手機寫socket client程式,socket 負責8266與arduino連繫,這樣是否可行

五餅二魚水族倉庫 提到...
作者已經移除這則留言。
小狐狸事務所 提到...

這我沒試過, 是可行的喔! 我有一本趙英傑寫的超圖解物聯網 IoT實作入門 5-2 節有談到這個方法, 利用 HTML5 的 Websocket 來建立伺服器與客戶端持續的雙向連線, 但沒試過不知效能如何. 您有空可以先試試, 跟 HTTP 的單向非持續性連線方式比較看看.

五餅二魚水族倉庫 提到...

這篇參考一下
https://evothings.com/doc/examples/arduino-led-onoff-tcp.html

五餅二魚水族倉庫 提到...

這篇參考一下
https://evothings.com/doc/examples/arduino-led-onoff-tcp.html

五餅二魚水族倉庫 提到...

這個有詳細實作

https://evothings.com/how-to-connect-your-phone-to-your-esp8266-module/

只要手機能夠連上8266,再傳資料給arduino處理,只是evothing 最後還要轉成App,這段還弄不出來

五餅二魚水族倉庫 提到...

這個有詳細實作

https://evothings.com/how-to-connect-your-phone-to-your-esp8266-module/

只要手機能夠連上8266,再傳資料給arduino處理,只是evothing 最後還要轉成App,這段還弄不出來

小狐狸事務所 提到...

感謝分享, 有空我也來試試!

五餅二魚水族倉庫 提到...

找到 socket 的寫法了...

http://swf.com.tw/?p=897

http://swf.com.tw/?p=901

建立Arduino的Socket即時通訊程式(一)
建立Arduino的Socket即時通訊程式(二)

給你參考..... 可惜是用 網路線,不是 Esp8266 ...

小狐狸事務所 提到...

感謝您, 我要找時間來試試看.

五餅二魚水族倉庫 提到...

期待你的好文章

Unknown 提到...

版主你好,感謝您發這麼多有用的文章讓我學習
另外我想請問如果想自寫一個asp.net來接arduino的資料
再放進資料庫,有相關的資訊嗎?
arduino get出去的資料是什麼型態?
asp.net這邊如何持續接值?
如果有相關資訊 麻煩提供 謝謝 感謝版主~~

小狐狸事務所 提到...

應該是可以的, 不過我還沒做過這樣的實驗, 我都是使用 Blynk 把資料記在雲端, 參考 :

http://yhhuang1966.blogspot.tw/2016/08/blynk-displays.html

不過有時間可以來做這樣的實驗.

小狐狸事務所 提到...

吳兄, 這問題我稍微想了一下, 應該可以這麼做 :
PC 先啟用 IIS 伺服器, 建立好資料表, 寫一個處理 GET 要求的 ASP 應用程式擺在伺服器下, 用擷取 GET 字串傳來的資料寫入資料表, 網址就是這台 PC 的 IP, URL 例如 http://192.168.2.111/get_data.asp?temp=28.5, 然後在 Arduino 程式週期性地對此 URL 發出 GET 要求就可以了. 但注意 ARDUINO+ESP8266 需與 PC 在同一個網段 (同一個基地台), 試試看應該可以的.

小狐狸事務所 提到...

參考這篇 :
http://yhhuang1966.blogspot.tw/2015/10/esp8266-wifi-arduino.html?showComment=1493885098889#c5714305515296492622"

Unknown 提到...

版主感謝您的回覆
我們也有查到要用request接get的值
可是我們怎麼傳,網址列後面都不會加上我們要上傳的資料,所以request好像都沒用
包括用request直接存到sql也沒有用...
查遍網路上好像都沒有人這樣做過
所以有點卡住了....
如果版主有更好的資訊 再麻煩提供 感謝~~感激不盡

小狐狸事務所 提到...

吳兄弟, 您說送不出參數, 可能遇到了 url 特殊字元問題, 參考這篇後面的問答 :

http://yhhuang1966.blogspot.tw/2016/07/esp8266-twitter.html

url 字串中 % 字元有特殊用途 (表示編碼), 要傳送 '%' 字元本身要改用 '%25', 而跳行則要改用 '%0d%0a'.
參考 : http://www.convertstring.com/zh_TW/EncodeDecode/UrlEncode 與 https://openhome.cc/Gossip/Encoding/URLEncoding.html :

"在URI的規範中定義了一些保留字元(Reserved character),像是「:」、「/」、「?」、「&」、「=」、「@」、「%」等字元,在URI中都有它的作用,如果你要在請求參數上表達URI中的保留字元,必須在%字元之後以十六進位數值表示方式,來表示該字元的八個位元數值。"

匿名 提到...

你可以直接用arduino IDE 直接开发控制esp8266 用串口指令多此一举

小狐狸事務所 提到...

您說的沒錯, 但我開始學 ESP 時, ESP 還沒這麼火紅, 只好用 AT 指令. 好比現代人看飛鴿傳書, 何不拿手機出來用 Line 就好了呀! 飛鴿傳書真的是多此一舉.

小狐狸事務所 提到...

現在可改用更厲害的 MicroPython 囉!