2017/5/31

以ESP8266的AT命令建立HTTP服務


筆者也曾在更早前於「實作App遠程遙控智慧家電控制中心」談論到如何透過手機的「藍芽系統」來控制家電, Maker的天性終究是要連結「全球心智」的, 因此接下來我們也嘗試實作以WiFi為基礎的家電控制中心, 概念如下圖.




延續前一個案例「便宜的WiFi Solution-ESP8266的第一次接觸」中, 我們知道可透過序列埠傳送AT命令與ESP-01模組溝通. 本案例在所有電路都不變的前提下, 我們僅透過ESP-01模組的AT命令來建立HTTP伺服器, 或許對將來我們直接使用別人已經寫好的工具庫時比較有感覺(知道背後運作的原理).

設置流程如下: setup過程透過AT命令重置、將ESP-01設為AP(Access Point)模式、允許多重連線、啟動本地TCP伺服器, 之後在loop迴圈內持續等待用戶端請求並將客戶端訊息(web browser傳來)打包成HTML送回客戶端.



取得ESP-01模組的IP
一般ESP-01的預設IP192.168.4.1, 實際內容可透過前一個案例「便宜的WiFi Solution-ESP8266的第一次接觸」中的AT命令訪談於序列埠監視窗取得, 如下圖. 



在開啟web browser之前我們需先打開無線網路的SSID表列, 當我們將ESP-01開啟為本地TCP伺服器時, 我們可於SSID表列中看到它. 例如, 筆者的ESP-01模組預設的SSID名稱AI-THINKER_25D56D, 如圖所示.



連接ESP-01本地TCP伺服器
上傳燒錄程式碼並於電腦或手機(選取關於ESP-01模組的SSID表列)完成WiFi連線之後, 我們就能透過web browserESP-01提出連線請求, 如下圖直接於URL欄位中填入ESP-01的本地IP. 



Arduino程式
筆者的Arduino IDE開發環境1.8.2 ZIP免安裝版. 程式部份我們修改前一個案例「便宜的WiFi Solution-ESP8266的第一次接觸, 將傳送AT命令的部份寫成一個稱為sendATcmdprocedure, 並接受一個等待指定ms的時間延遲, 每道AT命令之間需等待約3~2秒否則ESP-01可能不會正常被設置成功.


#include <SoftwareSerial.h>

SoftwareSerial ESP(3, 2); // ESP8266 ESP-01: Tx, Rx
 
void setup() {
    Serial.begin(115200);
    ESP.begin(115200);
   
    Serial.println("\nInitialize ESP-01 ...");
    sendATcmd("AT+RST\r\n",3000);       // reset ESP-01
    sendATcmd("AT+CWMODE=2\r\n",2000);  // config ESP-01 as AP mode
    //sendATcmd("AT+CWMODE?\r\n",1000); // check AP mode
    //sendATcmd("AT+GMR\r\n",1000);     // check firmware version
    sendATcmd("AT+CIFSR\r\n",2000);     // check IP
    sendATcmd("AT+CIPMUX=1\r\n",2000);  // allow multiple access
    sendATcmd("AT+CIPSERVER=1,80\r\n",2000); // start server at port:80
    Serial.println("\nServer started at port 80 ...");
}

void setDelay(unsigned int delay) {
    unsigned long timeout = delay+millis();
    while( millis()<timeout ) {} // NOP
}

void sendATcmd(char* cmd, unsigned int delay) {
    ESP.print( cmd ); // send AT command to ESP-01
    setDelay(delay);
}

void sendHTML(byte connID,char* msg) {
    char html[256];
    char cipSend[256];
    char cipClose[256];
    sprintf(html,"<html><head><title>From \
ESP-01</title></head><body>+IPD,%d%s</body></html>",connID,msg);
    sprintf(cipSend,"AT+CIPSEND=%d,%d\r\n",connID,strlen(html));
    sprintf(cipClose,"AT+CIPCLOSE=%d\r\n",connID);
    sendATcmd(cipSend,1000);
    sendATcmd(html,1000);
    sendATcmd(cipClose,1000);
}

void loop() {
    // send AT command to ESP-01 form console (serial)
    if ( Serial.available() ) {
        ESP.write( Serial.read() );
    }
    if ( ESP.available() ) { // receive message from ESP-01
        if ( ESP.find("+IPD,") ) { // detect the client's request
            String msg="";
            byte connID = ESP.read()-48; // client's connection ID
            while( ESP.available() ) { // client's request from the web browser
                msg += (char)ESP.read();
            }
            sendHTML(connID,msg.c_str()); // send HTML message to client
            delay(100);
        }
    }
}



迴圈內主要是持續監看ESP-01是否有來自用戶端的連線請求(web browser提出要求), 若有則將於ESP-01序列埠接收到「+IPD,」開頭的訊息, 緊接著第一個位元組為連線編號(ASCII轉為數字需減去48)與後面訊息的資料長度, 剩餘部份則是來自客戶端的資料內容(如下圖, 我們可以看到web browser會用GET方法對ESP-01提出請求). 筆者的ESP-01模組最多只能同時連接兩個客戶請求, 第三個會失敗(不曉得是否因電流不足? 因為筆者直接用Arduino提供3.3VESP-01). 

為了知道客戶端連線進來之後發生什麼事(資料的交換), 我們將來自客戶端的資料內容打包成HTML格式再透過sendHTML傳回給用戶, 其底層則是透過「AT+CIPSEND」與「AT+CIPCLOSE」來完成. 上傳燒錄程式碼之後, 我們可以直接開啟web browser來觀察客戶端與ESP-01模組(當本地TCP伺服器)之間的互動.





我們也可以在URL表列中填入我們將來想用到的指令, 例如我們計畫將來為伺服器增加一個路徑名ctrl與一個能由客戶端控制的參數arg. 透過這樣的互動, 我們也可以先觀察底層事實上發生了甚麼事. 如下圖, 客戶端透過GET方法對ESP-01提出請求同時也將上述的路徑名稱與參數傳遞給ESP-01, 日後我們可以對其作處理.





相信, 接下來聰明的讀者已經開始天馬行空的想像許多關於家電之間連網與控制的應用了! 自己玩玩看吧!