2020年8月25日 星期二

ESP32 作為 Web Server

ESP32本身具有WiFi連線的功能,而 ESP32 當作Web Server就是可以將監測的信號傳送到網路上的用戶端或是雲端伺服器。
要將ESP32設定成 Web Server,要有幾個步驟:

一、載入WiFi的函數庫
     
     #include <WiFi.h>

二、設定要連上無線基地台的ID及密碼

    const char* ssid     = "無線基地台的名字";                     
    const char* password = "連上無線基地台的密碼";
    
    ESP32 Web Server成功連上無線基地台後,會得到基地台給虛擬的 IP。因此這個時候在同網段的電腦或是手機,就可以用網頁瀏覽器連上這個 IP,與 ESP32 Web Server連線傳輸。     

三、宣告一個伺服器物件,名稱可以自訂,一般為了容易識別都設成 server;通訊埠也是設定網頁的80。若是要使用其他埠號,則用瀏覽器連上Web Server時,就要加上埠號。
 
    WiFiServer server(80);

     (似乎可以宣告兩個以上的伺服器物件,使用不同的埠號,這以後來測試看看)

四、啟動無線物件開始連結基地台,並不斷偵測無線是否已經連上了。如果沒有連上,每隔0.5秒不斷測試,並在監視視窗顯示狀態。

    WiFi.begin(ssid,password);
    while (WiFi.status() != WL_CONNECTED) {          //利用迴圈偵測無線物件連線的狀態
       delay(500);
       Serial.print(".");
    }

五、啟動 Web Server
    
    server.begin();

六、顯示連上基地台後,分配給 ESP32 Web Server的IP位址。可供同網段的電腦或手機用瀏覽器連線。
    1. 顯示在監視視窗上
        Serial.println("");
        Serial.println("WiFi connected.");
        Serial.println("IP address: ");
        Serial.println(WiFi.localIP());
   
      顯示到連線到無線基地台所分配到的 IP是 192.168.1.116 


    2. 之前使用過 OLED Display,正好也可以將 IP顯示在OLED。這比用USB連線上到ESP32電路板上,才能知道IP位址。
        display.setFont(ArialMT_Plain_16);
        display.drawString(00,00,"WiFi connected.");
        display.drawString(00,20,"IP address: ");
        display.drawString(00,40,WiFi.localIP());
        display.display(); 

    
     在編譯後,OLED顯示器的 drawString()內的參數第3個是字串,跟 localIP()是 IPAddress是不匹配的,所以要把 localIP() 改成 String 資料型態。因此使用String()轉換型態。
     
     String IPAddressofServer = String(Wifi.localIP());
     display.drawString(00,40,IPAddressofServer); 
     

   結果在 OLED 上顯示的 IP 卻不是 192.168.1.116。所以 IPAddress()是整數矩陣型態,無法使用 String()型態轉換。在 Google搜尋了 "IPAddress() to string"的方法,找到了:
      (1).建立一個轉換函數:
           String IpAddress2String(const IPAddress& ipAddress)
            {
                return String(ipAddress[0]) + String(".") +\
                String(ipAddress[1]) + String(".") +\
                String(ipAddress[2]) + String(".") +\
                String(ipAddress[3])  ; 
             }
        
            String IPAddressofServer = IpAddress2String(Wifi.localIP());
            display.drawString(00,40,IPAddressofServer);
     
      (2)後來查到一個Wifi.localIP()內建的方法:WiFi.localIP().toString()
            
            display.drawString(00,40,WiFi.localIP().toString());



















六、現在若是用手機(要在同一個網域,也就是手機也是連上同一台無線基地台)的瀏覽器上,輸入192.168.1.116,是一片空白。接下要設定當有使用端(client)連線上Web Server(ESP32)時,會適當給予回應。
    先宣告一個client物件,是用來 Server有接受到連線時的使用端資訊(client)
     WiFiClient client = server.available();
     if (client) {     
         當有連線到 Web Server,client會接收到連線的資料。
         所以可以使用 client.print(""); 將要顯示在資料顯示在網頁上
     }

   例如:
    void loop() {
        WiFiClient client = server.available(); 
        if (client) {                         
           client.println("HTTP/1.1 200 OK");      //因此顯示第一個顯示網頁的畫面  
           client.println("Content-type:text/html");
           client.println();
           client.print("<h1>");
           client.print(" Web Server");
           client.println("</h1>");     
       }
   }


七、若要控制ESP32(例如傳給值給ESP32的Web Server,將偵測到的溫溼度顯示在OLED上)或是與Web Server互動(再將溫溼度顯示到手機上的網頁),這些首先都是要能把值傳到 ESP32上的Web Server,ESP32再根據接收的資料(控制字元),來判斷要回應甚麼動作。
       
      1.client.read() 是用來讀取網頁上傳送過來的資料
      2.client.connect() 用來確認client端是否還連線在 Web Server上,連線則傳回1,否則傳回0。
                                   我們必須先用這個函數來確認,client還連線在Server,以確保下面讀取到
                                   的資料是由client傳過來的。在測試的時候,曾拿掉這個判斷,會讀到還沒
                                   連線前的資料,造成誤判。
      3.client.available() 用來確認網頁是否有資料可以讀取,如果沒有資料就會傳回0。
      
      4.client傳遞資料的儲存

      String newLine="" ;             //宣告一個字串變數用來儲存clinet傳送過來的資料
      if (c=='\n') {                       //如果讀到的字元是 '\n',表示這個網頁是第一次連上 Web Server。
           if(newLine.length()==0){    //讀到字元\n,而且資料字串是空白,表示clint沒有傳遞資料,
                                                          所以是第一次連到 Server,所以顯示初始的網頁。
             client.println("HTTP/1.1 200 OK");      //初始的網頁,會有一個可以顯示溫溼度的連結  
             client.println("Content-Type:text/html");
             client.println();
             client.println("<!DOCTYPE HTML>");
             client.println("<html>");
             client.print("<h1>");
             client.print(" Web Server");
             client.println("</h1> <h3>");
             client.print("Click <a href=\"/T\">Here</a> to Dispaly Temperature and Humidity.");
             client.println("</h1></h3>");
             client.println("</html>");
             break;                                     //顯示首頁完,跳出while迴圈
           }
           else {          //讀到\n,而資料字串長度不是0,表示已經到client傳遞資料的尾端,清除資料
                                 字串
             newLine="";
           }  
        }
       else if (c!='\r') {  //新的client連線,但讀到資料不是 /n 跟 /r,把讀到的字元存入資料字串     
         newLine +=c;      
       }

    5.判斷資料字串: endsWith用來判斷資料字串(newLine)是否是 "GET /T"結尾的。
             
       if (newLine.endsWith("GET /T")){
           ht();                                                 //呼叫ht()函數,在 OLED上顯示溫濕度  
       }

    6.相關的程式:
   
    void ht(){
         h=dht.readHumidity();
         hh=h;
         t=dht.readTemperature();
         tt=t;
         if (isnan(h)||isnan(t)){
             display.drawString(00,5,"Failed to read from DHT sensor!");
             display.display();
             return;                      
         }
         display.clear();
         display.drawString(00,1,"Humi:");
         display.drawString(60,1,hh);
         display.drawString(90,1,"%");
         display.drawString(00,30,"Temp:");
         display.drawString(60,30,tt);
         display.drawString(90,30,"c");
         display.display();
     }

    void loop() {
        WiFiClient client = server.available(); 
         if (client){                                    //有網頁連線上到 Web Server
             display.clear();
             display.drawString(00,00,"client connected.");
             display.display();
             String newLine="";
             while(client.connected()){
             if (client.available()){                //available()是表示client有傳遞資料 
                 char c=client.read();
                 Serial.write(c);
                 if (c=='\n') {                   //如果讀到的字元是 '\n',表示這個網頁是第一次連上 Server
                    if (newLine.length()==0){
                       client.println("HTTP/1.1 200 OK");      //因此顯示首頁  
                       client.println("Content-Type:text/html");
                       client.println();
                       client.println("<!DOCTYPE HTML>");
                       client.println("<html>");
                       client.print("<h1>");
                       client.print(" Web Server");
                       client.println("</h1> <h3>");
                       client.print("Click <a href=\"/T\">Here</a> to Dispaly Temperature and Humidity.");
                       client.println("</h1></h3>");
                       client.println("</html>");
                       break;                                //顯示首頁完,跳出while迴圈
                   }
                   else {
                          newLine="";
                   }  
                }
                else if (c!='\r') {
                          newLine +=c;      
                }
       
               if (newLine.endsWith("GET /T")){    //呼叫ht()函數,在 OLED上顯示溫濕度  
                    ht();  
               }
            } //available
          } //while
        }
      } //loop


參考資料:
  1.ESP32程式設計(基礎篇),曹永忠,2020年2月,渥瑪數位。

MicroPython在 ESP8266/ESP32(ㄧ) 使用 uPyCraft 開發工具

        除了使用C語言來撰寫控制微控器(MicroController)的程式,目前非常多人使用的Python程式語言也可以撰寫控制程式;只是一般微控器上的記憶體容量並不大,所以無法使用Python的所有功能。因此劍橋大學的 Damien P. George教授開發了 Python的精簡版,能使用在微控器上的 MicroPython。但必須要注意它並非能使用所有Python的函數庫及功能。(註1、2)

         ESP8266及ESP32模組的相片如下,左邊的是ESP8266,右邊的是ESP32。在 Arduino中要燒錄程式到微控器時,都要按右下角的按鈕(BOOT/FLASH)。

        市面上有可以一鍵燒錄的ESP32模組,如下圖。還提供可以外加電源輸入的插槽(左下角)。


        要在 ESP32 或是 ESP8266上使用 MicroPython 前,必須將相對應 MicroPython 的韌體(Firmware)燒錄到微控器上。在 Google上搜尋 MicroPython 就可以找到連接的網址:https://micropython.org/,上方的 DOWNLOAD 網頁內,就可以選擇要搭配相關微控器的MicroPython。

       ESP32:https://micropython.org/download/esp32/

        

        ESP8266:https://micropython.org/download/esp8266/

        網站是使用 esptool.py 來燒錄韌體。但如果對於文字模式的操作有困難,建議可以使用uPyCraft 這套由大陸 DFRoot 公司所推出免費的整合式開發軟體(IDE),它整合了終端機、Python 程式編輯器及軟體線上燒錄的功能。下載網址:https://github.com/DFRobot/uPyCraft。下載zip檔後,解壓縮後,以系統管理員身分執行 uPyCraft 應用程式。

        若是在執行後,出現找不到 MSVCR100.dll 的訊息,請到微軟的下載中心 Download Center
網站:https://www.microsoft.com/en-us/download/details.aspx?id=26999。

        

       Microsoft Visual C++ 2010 Service Pack 1 Redistributable Package MFC Security Update        選擇 Chinese(Traditional) 語系後,按下 Download 按鈕。


        在下載頁面,記得要下載的是 vcredist_86.exe,然後進行安裝。就可以解決問題。
如果選成 vcredist_x64.exe 是沒用有的。猜想  uPyCraft 是不是32位元版的原因。 

        開啟程式後,在 Tools 功能表下,從 board 中選取 esp32,再設定 Serial 中設定 esp32 模組連接的串列埠號(要按各自 esp32 連結到電腦的埠號)。


        若 ESP32模組未安裝 MicroPython韌體的話,uPyCraft會出現下面的視窗:


        此時,可以直接燒錄 uPyCraft所提供的 MicroPython韌體,但還是建議使用 MicroPython官網所提供韌體。burn_addr(燒錄韌體的起始位址)要設定成 0x1000,就可以開始燒錄。

        按下 ok按鈕後,先進行清除(Erase)的工作,如果無法開始清除,試著按住ESP32模組上右下角的BOOT按鍵,直到開始清除。


        清除後,接著就會進行燒錄(Burn)的工作。

        燒錄完,不會出現任何的提示訊息,此時可以按下uPyCraft視窗右邊功能鈕的連結(Connect)。


        連接上 ESP32 模組,在 uPyCraft下半部的 Shell 視窗就會出現 MicroPython 的提示 >>>,可以輸入指令進行測試,另外左邊的視窗,點選 device,會顯示 ESP32模組上的檔案。


        接下來自行輸入一個 python 程式,或是由 範例 (Examples)中選取一個程式,進行測試是否可以在 ESP32 模組上使用 Python,最簡單的測試範例就是可以讓 onBoard 的 LED閃爍。所以選擇 blink.py。


        撰寫好 MicroPython 程式,可以使用功能表 Edit下的 syntaxCheck 進行語法的檢查。
   

        如果有錯誤的地方,例如 while Ture 後面,忘了加上:經過 yntaxCheck 檢查,會在 Shell 視窗出現錯誤的訊息。如下圖:


       語法檢查後,沒有問題就可以在 Tools 功能表下的 DownloadAndRun 命令或是直接按下快捷鍵 F5,將 MicroPython 檔案 (blink.py)上傳到 ESP32 後,執行程式,檢查結果是否正確。


        在 uPyCraft 左邊的視窗,在 device 資料夾中,可以看到剛上傳到 ESP32的檔案 (blink.py), 還有 boot.py。


        如果要執行在 ESP32 內的某個 MicroPython 檔案,可以利用 uPyCraft 左邊的視窗,從 device 資料夾中點選檔案後,按下滑鼠右鍵,選擇 Run 的命令。如果選擇 Default Run,則是 ESP32一接上電源後就會馬上執行程式 blink.py。

 
        選擇 Default Run命令後,會產生一個 main.py檔案,裡面的指令就是 ESP32 接上電源後就會執行的檔案(blink.py)。再按下 ESP32模組上的 RST 或是 EN 按鈕就會看到執行的結果。 如果要停止執行,選擇 Tools功能表下的 Stop命令。


        要注意的是: 如果直接關掉 uPyCraft 程式或是拔起 ESP32模組,再開啟  uPyCraft 或是插回 ESP32模組,此時  uPyCraft 將無法連線上 ESP32模組,會出現重新燒錄韌體的視窗。

       MicroPython檔案系統,在根目錄(device)下常會有兩個 python 檔案:boot.py 與 main.py。在 ESP32開機後,會先去根目錄下找尋 boot.py 檔案,存在的話就先執行此開機檔; 然後再尋找 main.py,如果有的話就會再執行這個程式。 main.py 裡面主要就是一個執行特定應用邏輯無窮迴圈。(註3)

        一旦將 blink.py設定成  Default Run,會產生 main.py 無限迴圈執行檔案,以致關掉uPyCraft程式或是拔起 ESP32模組後 ,uPyCraft 無法連線上 ESP32模組。

       要解決這個問題,若是在設定成  Default Run 後,先從 Tools 功能表下的 Stop 命令停止程式執行,再刪除 main.py,將會出現 delete error。

       

        目前想到解決的方法是:

       1.開啟 main.py

       2.將 main.py內的 exec(open('./main.py').read(),globals()) 程式刪除掉。

       3.再從 Tools 功能表中,選擇 Download命令,將 main.py清除空白。


        如果未清除 main.py 內的 exec(open('./main.py').read(),globals()) ,就關掉 uPyCraft 程式或是拔起 ESP32模組,那除了重新燒錄韌體外,可以選擇用 Thonny 整合開發軟體來刪除 main.py。

        uPyCraft更多的使用方法,請參考註2的說明文件。

        另外要注意的是:使用 uPyCraft 燒錄 ESP8266 韌體時,burn_addr 要設定成 0x0 (預設值)。


註1:超圖解Python物聯網實作入門,趙英傑,旗標出版社(2018)。

註2:uPyCraft簡體中文說明文件:http://docs.dfrobot.com.cn/upycraft

註3:MicroPython on ESP8266 (六) : 檔案系統測試,小狐狸事務所,http://yhhuang1966.blogspot.com/2017/05/micropython-on-esp8266_18.html

註4:經典ESP學習網站(Random Nerd Tutorials): https://randomnerdtutorials.com/