2016年7月29日 星期五

利用網頁控制 Arduino (一)

最近雜務較多, 加上決定菁菁不上補習班後, 晚上都要照進度教她國二數學課程, 所以比較沒時間玩 Arduino, 只能撿零星時間拼湊. 說實在的, 只有學生時代才能有大把連續時間去學甚麼, 進社會工作之後, 在學習時間上, 就只能 "夾縫裡求生存", 別再肖想有一大串連續時間可以研究學習. 但弔詭的是 (應該說, 人很賤的地方是), 當你有很多時間的時候, 反而不會把握, 只想玩. 現在的大學生把珍貴的時間花在玩 Game 上 FB 或 Line, 等到要找工作了, 才發現自己啥都不懂. 不景氣的時代, 加上不爭氣的學子, 台灣的前途真是堪憂啊!

好了, 言歸正傳, 昨晚翻閱 "Arduino Cookbook 錦囊妙計" 第 15 章乙太網路和網路連結 15.7 節 "處理傳進來的網路需求", 書中範例是用 Wiznet 乙太網路擴充板讓 Arduino 連上區網, 並將乙太網板設定為網頁伺服器, 由 Arduino 當 HTTP 協定處理器, 負責回應前端的網頁要求. 我是打算將乙太網改成 ESP8266 ESP01 板來進行同樣的實驗, 使用我自行焊接的 Nano+ESP8266 IOT 模組, 參考 :

# 製作 Arduino Nano + ESP8266 物聯網模組 (四) : 完結篇

本實驗的目的, 是要讓使用者以瀏覽器連線 Arduino 的網頁伺服器, 透過不同的網址來設定 Arduino 個針腳的值, 亦即, 可透過網路遠端控制 Arduino 的輸出入. 其網址格式為 :

http://192.168.2.104/?pinD2=1  (將 Arduino D2 針腳設為 HIGH 輸出)
http://192.168.2.104/?pinA5=1023  (將 Arduino A5 針腳設為 1023 輸出)

對 Arduino Nano 而言, 其可程式化輸出入腳如下 :

  1. 數位腳位 :
    D0~D13 共 14 個, 其中 D0 (Rx) 與 D1 (Tx) 與 USB 共接; D3, D5,D6,D9~D11 六隻腳具有 PWM 類比模擬功能. 
  2. 類比腳位 :
    A0~A6 共 7 個, 具有 A/D 轉換功能, 作為輸入時可將外部 0~5V 類比輸入轉為 0~1023 的數值; 反之作為輸出時可將 0~1023 的數值轉成 0~5V 的電壓輸出. 
參考 :

# Arduino Nano規格簡介

本實驗就是要透過網頁來遠端控制 Arduino 的 IO 腳的輸出. 下面程式的寫法參考了我為 IOT 模組所寫的 Wifi 設定程式碼, 參考 :

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

完整程式如下 :

測試 1 :

#include <SoftwareSerial.h>
#define DEBUG true

SoftwareSerial esp8266(7,8); //(RX,TX)
String ssid="H30-L02-webbot";
String pwd="1234567890";

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
    int digitalRequests=0;
    int analogRequests=0;
    if (esp8266.find("+IPD,")) { //get client request
      delay(1000); //waiting for response: ?pinD2=1 or ?pinA4=1023
      int connectionId=esp8266.read()-48;  //turn ASCII to number
      if (esp8266.find("GET /?pin")) { //retrieve page name (router)
        char type=(char)esp8266.read(); //read char after ?pin
        int pin=esp8266.parseInt(); //get first int number from serial
        int val=esp8266.parseInt(); //get first int number from serial
        if (type=='D') {
          Serial.print("Digital pin ");
          pinMode(pin, OUTPUT); //set pin as ouput
          digitalWrite(pin, val);
          digitalRequests++;        
          }
        else if (type=='A') {
          Serial.print("Analog pin ");
          analogWrite(pin, val);
          analogRequests++;            
          }
        else {
          Serial.println("Unexpected type:" + type);
          }
        Serial.print(pin);
        Serial.print("=");
        Serial.println(val);
        String webpage="<html>";
        webpage += (String)digitalRequests + " digital pin(s) written<br>";
        webpage += (String)analogRequests + " analog pin(s) written<br><br>";
        for (byte i=0; i<6; i++) {
          webpage += "A" + (String)i + "=" + (String)analogRead(i) + "<br>";
          }
        webpage += "</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;
  }

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

草稿碼使用了 10,602 bytes (34%) 的程式存儲空間。最大值為 30,720 bytes。
全域變數使用了 645 bytes (31%) 的動態記憶體,剩餘 1,403 bytes 供局部變數。最大值為 2,048 bytes 。

在此程式的 setup() 函數中, 我先將 ESP8266 設定為 mode 1 (station), 並且啟動可多重連線之網頁伺服器, 然後呼叫 connectWifi 連線無線基地台, ESP8266 將從 AP 取得一個區網 IP, 此 IP 即為網頁伺服器之連線網址. 接著程式就進入 loop() 函數等待用戶端連線.

在 loop() 函數中, 呼叫序列埠的 available() 函數可偵測 Arduino 的軟體序列埠緩衝器是否有資料從 ESP8266 送過來, 有的話就在緩衝器中尋找 "+IPD," 字串, 此乃客戶端正在連線的標記, 其格式例如 :

+IPD,0,298:GET /?pinD2=1 HTTP/1.1

在 "+IPD," 後面的數字即客戶端的連線通道 ID, 因為 ESP8266 的多重連線僅有 0~4 這五個 ID, 因此只要用 read() 讀取即可 (一個字元), 從序列埠讀到的其實是 ASCII 編碼的字元, 是個整數, 因此必須減掉 48 才是 0~9 的數字, 因為 0~9 的數字在 ASCII 編碼中為 48~57.

讀取通道 ID 後緩衝區就剩下 ",298:GET /?pin......" 了, 因為 read() 函數在讀取緩衝區時, 會將該字元從緩衝區刪除. 接下來我們直接呼叫序列埠的 find() 函數在緩衝區中尋找 "GET /?pin" 字串, 與 read() 一樣, 找到後此函數會將包含 "GET /?pin" 在內的之前字元串都從緩衝區內刪除, 因此緩衝區這時只剩下例如 "D2=1" 或 "A3=1023" 的字元串了. 這時再呼叫序列埠的 read() 便能擷取出是數位腳位 'D' 還是類比腳位 'A' 了. 這樣緩衝區就剩下 "2=1" 或 "3=1023" 了, 最後, 利用序列埠的 parseInt() 函數便能依序取得 pin 腳編號以及其值 val 了, 此函數會跳過所有非數字字元, 傳回第一個找到的數字 (整數)就停止, 因此它找到 "=" 時便停止, 傳回 pin 編號, 再呼叫 parseInt() 則跳過 "=" 傳會 val 值.

參考 :

https://www.arduino.cc/en/Serial/Read
https://www.arduino.cc/en/Serial/Find
https://www.arduino.cc/en/Reference/ParseInt

擷取出客戶端要求網址中的 type, pin 與 value 後, 便可用 digitalWrite() 或 analogWrite() 去控制 Arduino 的輸出了, 然後向客戶端輸出網頁, 顯示有幾個腳位被變更, 以及類比輸入所量測到數值. 注意, 串接輸出網頁內容字串時若使用 += 的話, 若一次串接一個非字串型態的 int, char 等類別時會自動轉型, 不須強制轉型; 若用 + 串接的話則必須用 (String) 強制轉型, 否則編譯失敗.

將程式上傳後, 從序列埠監控視窗可看到 ESP8266 伺服器已經啟動, 而且從所連線的無線基地台 DHCP 獲得一個 IP : 192.168.43.111, 等待用戶端連線進來 :

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="H30-L02-webbot","1234567890"


OK
AT+GMR

0018000902

OK
AT+CIFSR

192.168.2.114

OK

我使用筆電連線 ESP8266 網頁伺服器 IP=192.168.2.114 可獲得伺服器回應之網頁 :


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

Analog pin 2=1023
HTTP/1.1
Host: 192.168.2.114
ConnectiAT+CIPSEND=2,132

> <html>0 digital pin(s) written<br>1 analog pin(s) written<br><b
SEND OK
AT+CIPCLOSE=2


+IPD,1,348:GET /favicon.ico HTTP/1.1
Host: 192.168.2.114
Connection: keep-alive
User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.103 Safari/537.36
Accept: */*
Referer: http://192.168.43.111/?pinA2=1023
Accept-Encoding: gzip, deflate, sdch
Accept-Language: zh-TW,zh;q=0.8,en-US;q=0.6,en;q=0.4


OK

OK

好了, 這個測試到此應該算完成了.

不過這實驗也可以用 WeeESP8266 函式庫來做, 對於網址路徑就可以單純用字串來處理, 而不是像上面範例那樣直接處理序列埠送收. 事實上我起先是採用此函式庫來做書上的實驗的. 但因為有點不順利, 還是乖乖地去直接處理序列埠送收, 就是上面測試 1 的程式. 關於 WeeESP8266 函式庫用法, 參考 :

ITEAD WeeESP8266 函式庫測試
ITEAD WeeESP8266 函式庫測試 (二) : 網頁伺服器
使用 WeeESP8266 函式庫重作 NTP 對時實驗
使用 ITEAD WeeESP8266 函式庫改寫物聯網模組 wifi 設定程式
深入淺出 Wifi 晶片 ESP8266 with Arduino

完整程式如下 :

測試 2 :

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

SoftwareSerial esp8266(7,8); //(D7:RX, D8:TX)
ESP8266 wifi(esp8266); //create wifi object
String ssid="EDIMAX-tony";
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("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=""; //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(" HTTP/")) {path.concat((char)buffer[i]);}
      else {break;}
      }
    path=path.substring(path.indexOf("/") + 1, path.indexOf(" HTTP/"));
    Serial.println(path); //eg: ?pinD2=1 or ?pinA4=1023
    char type=path.charAt(4);
    int pin=path.substring(5, path.indexOf("=")).toInt();  
    int val=path.substring(path.indexOf("=") + 1).toInt();
    if (type=='D') {
      Serial.print("Digital pin ");
      pinMode(pin, OUTPUT);
      digitalWrite(pin, val);
      digitalRequests++;
      }
    else if  (type=='A') {
      Serial.print("Analog pin ");
      analogWrite(pin, val);
      analogRequests++;    
      }
    else {
      Serial.print("Unexpected type:");
      Serial.println(type);
      }
    Serial.print(pin);
    Serial.print("=");
    Serial.println(val);  
    String webpage="<html>" + (String)digitalRequests + " digital pin(s) written<br>" +
                   (String)analogRequests + " analog pin(s) written<br><br>";
    for (byte i=0; i<6; i++) {
      webpage += "A" + (String)i + "=" + (String)analogRead(i) + "<br>";
      }    
    webpage += "</html>";      
    wifi.send(mux_id, (const uint8_t*)webpage.c_str(), webpage.length());
    wifi.releaseTCP(mux_id);
    }
  }

使用 WeeESP8266 函式庫需先建立一個名為 ESP8266 物件, 並傳入與 ESP8266 通訊的序列埠物件, 這裡是使用 D7 與 D8 作為 Rx 與 Dx 的軟體序列埠. 同樣在 setup() 函數中呼叫 SetOprToStation() 以設定 ESP8266 工作在 mode 1, 並呼叫 enableMUX() 與 startTCPServer() 啟動多重連線之網頁伺服器, 然後呼叫 joinAP() 連上無線基地台.

進入 loop() 後就開始偵測 ESP8266 是否有收到送客戶端的連線要求. 使用 WeeESP8266 送收資料須準備一個 uint_8 型態的緩衝器 buffer 與連線通道紀錄器 mux_id 當參數, 傳入 recv() 接收函數中, 它會將回應字元放入緩衝器內, 將連線通道紀錄在 mux_id 中, 並傳回緩衝器收到的資料長度 len. 當 len 不為 0 表示有收到客戶端要求, 這時可用一個迴圈去讀取緩衝區, 直到 "HTTP/" 為止, 放入路徑字串變數 path 中, 其內容例如 :

+IPD,1,248:GET /?pinD2=1 HTTP/

這樣便可以利用 Arduino 的字串函數來擷取要設定的腳位與值. 關於字串處理請參考 :

Arduino 基本語法筆記

透過下列指令即可取出介於第一個斜線與空白之間的 URL 資訊 :

path=path.substring(path.indexOf("/") + 1, path.indexOf(" HTTP/"));

這樣 path 就得到 ?pinD2=1 或 ?pinA3=1023 的字串了. 利用 char() 可得到數位腳 (D) 或類比腳 (A), 而透過 substring() 與 indexOf() 就可以抓出腳位編號 pin 與其設定值 val. 注意, 因為擷取出來的 pin 與 val 是數字字串, 因此必須用 toInt() 轉成整數, 以便傳入 digitalWrite() 與 analogWrite() 之中.

編譯後記憶體占用情形如下 :

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

可見使用函式庫較占記憶體喔! 大約多了 10% 左右.

程式上傳後以手機連線同一無線基地台, 輸入 ESP8266 伺服器網址, 這次改為設定數位腳位 D2, 得到預期結果 :

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

Firmware version ... 0018000902
Enable web server ... OK
Firmware version ... 0018000902
IP ... 192.168.2.114
?pinD2=0
Digital pin 2=0
favicon.ico
Unexpected type:c
0=0

比較上面兩個測試, 當然測試 1 直接處理序列埠較低階麻煩, 但好處是省記憶體, 而且比較能理解底層運作的細節; 測試 2 使用函式庫, 字串處理較簡單, 程式看來解結易懂, 但較吃記憶體. 不管哪種做法, 都還是要在瀏覽器上輸入網址來操控, 還是麻煩. 雖然已有 App 介面可以讓遠端控制 Arduino 的輸出更容易, 但書上這個實驗能讓我們更熟悉序列埠與字串的操作. 

2016-07-30 補充 :

今天早上重新檢視程式碼時覺得為啥要設 digitalRequests 與 analogRequests 這兩個變數呢? 不是一次設定一個腳位嗎? 仔細看作者在 15.7 節末尾的說明才發現, 原來書中的範例程式可以一次設定好幾個腳位, 每一組參數都用 & 連起來即可, 例如 :

192.168.2.114/?pinD2=1&pinA2=1023

上面兩個測試程式只能一次處理一個參數而已. 所以我將其修改為可一次設定多個參數的程式如下 :

測試 3 :

#include <SoftwareSerial.h>
#define DEBUG true

SoftwareSerial esp8266(7,8); //(RX,TX)
String ssid="EDIMAX-tony";
String pwd="1234567890";

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
    int digitalRequests=0;
    int analogRequests=0;
    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)
        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 ");
            pinMode(pin, OUTPUT);
            digitalWrite(pin, val);
            digitalRequests++;        
            }
          else if (type=='A') {
            Serial.print("Analog pin ");
            analogWrite(pin, val);
            analogRequests++;            
            }
          else {
            Serial.println("Unexpected type:" + type);
            }
          Serial.print(pin);
          Serial.print("=");
          Serial.println(val);        
          }
        String webpage="<html>";
        webpage += (String)digitalRequests + " digital pin(s) written<br>";
        webpage += (String)analogRequests + " analog pin(s) written<br><br>";
        for (byte i=0; i<6; i++) {
          webpage += "A" + (String)i + "=" + (String)analogRead(i) + "<br>";
          }
        webpage += "</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;
  }

主要的改變是加入一個 while 迴圈用序列埠的 findUntil() 不斷去找 "pin". 編譯後記憶體耗用 :

草稿碼使用了 10,856 bytes (35%) 的程式存儲空間。最大值為 30,720 bytes。
全域變數使用了 645 bytes (31%) 的動態記憶體,剩餘 1,403 bytes 供局部變數。最大值為 2,048 bytes 。

瀏覽器連線結果正確 :


而使用 WeeESP8266 函式庫測試 2 則被改成下列測試 4 :

測試 4 :

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

SoftwareSerial esp8266(7,8); //(D7:RX, D8:TX)
ESP8266 wifi(esp8266); //create wifi object
String ssid="EDIMAX-tony";
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("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=""; //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(" HTTP/")) {path.concat((char)buffer[i]);}
      else {break;}
      } //path : "+IPD,1,248:GET /?pinD2=1&pinA4=1023 HTTP/"
    path=path.substring(path.indexOf("?") + 1, path.indexOf(" HTTP/"));
    Serial.println(path); //eg : pinD2=1 or pinD2=1&pinA4=1023
    while (path.indexOf("pin") != -1) { //parsing not finished
      char type=path.charAt(3);
      int pin=path.substring(4, path.indexOf("=")).toInt();  
      int val=path.substring(path.indexOf("=") + 1).toInt();
      if (type=='D') {
        Serial.print("Digital pin ");
        pinMode(pin, OUTPUT);
        digitalWrite(pin, val);
        digitalRequests++;
        }
      else if  (type=='A') {
        Serial.print("Analog pin ");
        analogWrite(pin, val);
        analogRequests++;    
        }
      else {
        Serial.print("Unexpected type:");
        Serial.println(type);
        }
      Serial.print(pin);
      Serial.print("=");
      Serial.println(val);
      if (path.indexOf("&") != -1) { //multiple parameters
        path=path.substring(path.indexOf("&") + 1); //remove parameter      
        }
      else {path="";} //terminate loop 
      }
    String webpage="<html>" + (String)digitalRequests + " digital pin(s) written<br>" +
                   (String)analogRequests + " analog pin(s) written<br><br>";
    for (byte i=0; i<6; i++) {
      webpage += "A" + (String)i + "=" + (String)analogRead(i) + "<br>";
      }    
    webpage += "</html>";      
    wifi.send(mux_id, (const uint8_t*)webpage.c_str(), webpage.length());
    wifi.releaseTCP(mux_id);
    }
  }

主要的變更也是多加了一個 while 迴圈來偵測是否還有 "pin" 字眼存在, 最重要的是迴圈末尾要自 path 字串中移除目前所處理的參數, 若已無 & 字元表示已處理完全部參數, 要把 path 設為空字串來終止迴圈. 編譯後記憶體耗用如下 :

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

以手機瀏覽器連線 192.168.2.114/?pinD2=1&pinD3=0&pinA2=1023 結果如下 :

可見迴圈會累計有幾個腳位被設定輸出值了.

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

Firmware version ... 0018000902
Enable web server ... OK
IP ... 192.168.2.114
pinD2=1&pinD3=0&pinA2=1023
Digital pin 2=1
Digital pin 3=0
Analog pin 2=1023
GET /favicon.ico


7 則留言 :

匿名 提到...

請問能改成AP server來控制arduino嗎?

小狐狸事務所 提到...

可以喔, 參考 :
http://yhhuang1966.blogspot.tw/2015/11/esp8266-at.html
http://yhhuang1966.blogspot.tw/2016/06/allaboutee-esp8266_2.html

Unknown 提到...
作者已經移除這則留言。
匿名 提到...

您好:
請問手機控制輸入輸出需要與ESP8266連同一個網路嗎?
還是可以在不同地區使用不同網路去遠端控制?
謝謝。

小狐狸事務所 提到...

遠端的話可以利用物聯網網站或 Blynk 喔! 參考 :

http://yhhuang1966.blogspot.com/2016/08/blynk.html

匿名 提到...

hi 您好:
想請問我用您的範例三可以成功用電腦瀏覽器讀取arduino數值,
但為何用手機瀏覽器讀取都無法讀到數值,手機瀏覽器會一直秀連線逾時,
但序列峊視窗卻有connectid出現呢?
感謝

小狐狸事務所 提到...

此實驗為 Arduino 透過 ESP8288 連接家中無線基地台, 獲得基地台配發如 192.168.2.114 網址, ESP8266 又開啟 AP 模式作為伺服器, 因此手機應該先連線無線基地台再用瀏覽器瀏覽 ESP8288 網址.