如何在 Arduino 上運行網頁伺服器 在本教程中,我將向您展示如何在帶有 WiFi 的 Arduino 設備上啟動網頁伺服器,就像我的 Arduino MKR WiFi 1010 。
我們將連接到現有的 WiFi 網絡,並且能夠通過我們的瀏覽器通過 HTTP 與 Arduino 互動。
這對於各種應用非常有趣。從簡單的傳感器數據檢查,到根據執行的 HTTP 請求 執行操作。
我們將從在 使用 Arduino 連接到 WiFi 網絡 教程中定義的此程序開始:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 #include <SPI.h> #include <WiFiNINA.h> void setup () { char ssid[] = SECRET_SSID; char pass[] = SECRET_PASS; Serial.begin (9600 ); while (!Serial); int status = WL_IDLE_STATUS; while (status != WL_CONNECTED) { Serial.print ("Connecting to " ); Serial.println (ssid); status = WiFi.begin (ssid, pass); delay (5000 ); } Serial.print ("IP address: " ); Serial.println (WiFi.localIP ()); } void loop () {}
在 setup()
之前加上這一行:
來初始化在端口 80 上的 TCP 伺服器,並在 setup()
的末尾呼叫:
來啟動伺服器。
現在這是一個 TCP 伺服器,不是 HTTP 伺服器。但是由於 HTTP (一個 TCP/IP 應用層協議) 建立在 TCP (傳輸層) 之上,我們可以很容易地自己建立 HTTP 伺服器。
首先我們需要監聽客戶端的連接。我們在 loop()
中這麼做:
1 2 3 4 5 6 void loop () { WiFiClient client = server.available (); if (client) { } }
server
的 available()
方法會監聽傳入的客戶端。
在 if (client) {}
檢查內部,我們有一個已連接的 HTTP 客戶端。我們需要做的是:
調用 client.connected()
來檢查數據是否連接,並且有數據可讀
調用 client.available()
來獲取可供讀取的字節數(這可以確保有數據可讀)
調用 client.read()
來從傳入數據中讀取一個字節(客戶端發送的 HTTP 請求)
調用 client.println()
或 client.print()
來向客戶端發送數據,構建合適的 HTTP 響應
調用 client.stop()
來結束連接
我們開始通過串口接口輸出用戶端發送的每個字符,並在最後關閉連接:
1 2 3 4 5 6 7 8 9 10 11 12 13 void loop () { WiFiClient client = server.available (); if (client) { while (client.connected ()) { if (client.available ()) { char c = client.read (); Serial.write (c); } } client.stop (); } }
嘗試將此程序上傳到 Arduino 上。將您的瀏覽器指向該 IP 地址。您將在串口接口上看到類似下面的內容。這是瀏覽器發送的內容:
1 2 3 4 5 6 7 8 9 GET / HTTP/1.1 Host: 192.168.1.40 Upgrade-Insecure-Requests: 1 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8 User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_6) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/14.0.2 Safari/605.1.15 Accept-Language: en-us Accept-Encoding: gzip, deflate Connection: keep-alive
注意結尾的空行。這是 HTTP 請求的結束。
我們需要攔截這個空行。
HTTP 請求中的每一行以 CR 回車字符(\r
)和 LF 換行字符(\n
)結束。
因此,請求的結束可以通過這兩個序列來確定:\r\n\r\n
。
這個簡單的算法能夠工作,我們只需要記住當前字符之前的 2 個字符,並且檢測是否識別到 \n\r\n
序列(該序列的最後 3 個字符足以確定最後一行):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 void loop () { WiFiClient client = server.available (); if (client) { char prevprev; char prev; while (client.connected ()) { if (client.available ()) { char c = client.read (); Serial.write (c); if (prevprev == '\n' && prev == '\r' && c == '\n' ) { } prevprev = prev; prev = c; } } client.stop (); } }
所以現在我們可以在 if
內部發送響應,我們可以使用 client.println()
,並添加這樣一個簡單的響應:
1 2 3 4 5 6 7 8 HTTP/1.1 200 OK Content-Type: text/html Connection: close <!DOCTYPE HTML> <html> test </html>
像這樣:
1 2 3 4 5 6 7 8 9 client.println("HTTP/1.1 200 OK"); client.println("Content-Type: text/html"); client.println("Connection: close"); client.println(); client.println("<!DOCTYPE HTML>"); client.println("<html>"); client.println("test"); client.println("</html>"); break;
break;
陳述結束了 while (client.connected()) {}
區塊。
這就是完整的程序:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 #include <SPI.h> #include <WiFiNINA.h> WiFiServer server (80 ) ;void setup () { char ssid[] = SECRET_SSID; char pass[] = SECRET_PASS; Serial.begin (9600 ); while (!Serial); int status = WL_IDLE_STATUS; while (status != WL_CONNECTED) { Serial.print ("Connecting to " ); Serial.println (ssid); status = WiFi.begin (ssid, pass); delay (5000 ); } Serial.print ("IP address: " ); Serial.println (WiFi.localIP ()); server.begin (); } void loop () { WiFiClient client = server.available (); if (client) { char prevprev; char prev; while (client.connected ()) { if (client.available ()) { char c = client.read (); Serial.write (c); if (prevprev == '\n' && prev == '\r' && c == '\n' ) { client.println ("HTTP/1.1 200 OK" ); client.println ("Content-Type: text/html" ); client.println ("Connection: close" ); client.println (); client.println ("<!DOCTYPE HTML>" ); client.println ("<html>" ); client.println ("test" ); client.println ("</html>" ); break ; } prevprev = prev; prev = c; } } client.stop (); } }
嘗試它,您應該會在瀏覽器中看到 test
:
該方法的工作方式一直到您需要弄清楚客戶端要求我們的內容為止。在這種情況下,您想要讀取每一行,因此這種替代方法更好:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 void loop () { WiFiClient client = server.available (); if (client) { String line = "" ; while (client.connected ()) { if (client.available ()) { char c = client.read (); Serial.write (c); if (c != '\n' && c != '\r' ) { line += c; } if (c == '\n' ) { if (line.length () == 0 ) { client.println ("HTTP/1.1 200 OK" ); client.println ("Content-Type: text/html" ); client.println ("Connection: close" ); client.println (); client.println ("<!DOCTYPE HTML>" ); client.println ("<html>" ); client.println ("test" ); client.println ("</html>" ); break ; } else { line = "" ; } } } } client.stop (); } }
在最後的 else
中,我們可以檢查行,因為行已結束,並根據我們的需要進行相應的操作。