為了以更有系統及益於維護為前提,在EPS32上實現Websocket該範例,會用到以下幾篇文章,建議先參考以下幾篇文章
SPIFFS:
WebServer顯示數值(局部刷新):
WebServer(非同步伺服器) 控制IO :
mDNS:
Websocket簡易說明:
WebSocket使得客戶端和伺服器之間的資料交換變得更加簡單,允許伺服器端主動向客戶端推播資料。
在WebSocket API中,瀏覽器和伺服器只需要完成一次交握,兩者之間就可以建立永續性的連接,並進行雙向資料傳輸。
WebSocket協定支援Web瀏覽器(或其他客戶端應用程式)與Web伺服器之間的互動,具有較低的開銷,便於實現客戶端與伺服器的即時資料傳輸。
伺服器可以通過標準化的方式來實現,而無需客戶端首先請求內容,並允許訊息在保持連接打開的同時來回傳遞。通過這種方式,可以在客戶端和伺服器之間進行雙向持續對話。
通訊通過TCP埠80或443完成,這在防火牆阻止非Web網路連接的環境下是有益的。
大多數瀏覽器都支援該協定,包括Google Chrome、Firefox、Safari、Microsoft Edge、Internet Explorer和Opera。
與HTTP不同,WebSocket提供全雙工通訊。此外,WebSocket還可以在TCP之上實現訊息流。TCP單獨處理位元組流,沒有原生的訊息概念。
在WebSocket之前,使用Comet可以實現全雙工通訊。但是Comet存在TCP交握和HTTP頭的開銷,因此對於小訊息來說效率很低。WebSocket協定旨在解決這些問題。
範例功能說明:
透過Websocket將ESP32上的字串及亂數透過JSON格式傳到客戶端網頁並顯示出來,當客戶端點擊按鈕時會傳送字串給ESP32(SERVER),在監控視窗中看到
以下為專案檔,如有需要請自行下載
https://drive.google.com/file/d/1o-qvGv1dZ2ltqD1GpjkoREoN-66OaeRL/view?usp=sharing
備註: 將專案解壓縮後先開啟Websocket_JSON.ino的專案檔,透過前面提到的SPIFFS將裡面的data資料夾燒錄至ESP32裡面,你只有燒
ino檔是不能正常工作的
程式碼 :
SERVER端:
#include <WiFi.h>
#include <ESPAsyncWebServer.h>
#include <SPIFFS.h>
#include <ArduinoJson.h>
StaticJsonDocument<200> jsondoc;
char json_output[100];
const char* ssid = "XXXXX"; //輸入你的熱點
const char* password = "XXXXX";//輸入你的熱點密碼
AsyncWebServer server(80); //PORT號
AsyncWebSocket ws("/ws");
//每當我們通過 WebSocket 協議從客戶端接收到新數據時,它就會運行
void handleWebSocketMessage(void *arg, uint8_t *data, size_t len) {
AwsFrameInfo *info = (AwsFrameInfo*)arg;
if (info->final && info->index == 0 && info->len == len && info->opcode == WS_TEXT) {
data[len] = 0;
String catchvalue = String((char*)data);
Serial.println(catchvalue);//顯示收到的數值
}
}
//配置一個事件監聽器來處理 WebSocket 協議的不同異步步驟。這個事件處理程序可以通過定義onEvent()
void BonEvent(AsyncWebSocket *server, AsyncWebSocketClient *client, AwsEventType type,
void *arg, uint8_t *data, size_t len) {
switch (type) {
case WS_EVT_CONNECT:
Serial.printf("WebSocket client #%u connected from %s\n", client->id(), client->remoteIP().toString().c_str());
break;
case WS_EVT_DISCONNECT:
Serial.printf("WebSocket client #%u disconnected\n", client->id());
break;
case WS_EVT_DATA:
handleWebSocketMessage(arg, data, len);
break;
case WS_EVT_PONG:
case WS_EVT_ERROR:
break;
}
}
void notifyClients() {
const uint8_t size = JSON_OBJECT_SIZE(3);
StaticJsonDocument<size> jsondoc;
jsondoc["room"] = "chosemaker";
jsondoc["rand"] = random(0,40);
char data[100];
size_t len = serializeJson(jsondoc, data);
ws.textAll(data, len);
}
void initWebSocket() {//初始化 WebSocket 協議
ws.onEvent(BonEvent);
server.addHandler(&ws);
}
void setup() {
Serial.begin(115200);
if (!SPIFFS.begin(true)) {
Serial.println("掛載SPIFFS分區出錯啦~");
return;
}
WiFi.mode(WIFI_STA);//SEVER用途選STA,雖然預設也是默認STA
WiFi.begin(ssid, password);
Serial.println("");
while (WiFi.status() != WL_CONNECTED) {
Serial.print(".");
delay(500);
}
Serial.print("\nIP位址:");
Serial.println(WiFi.localIP());
server.serveStatic("/", SPIFFS, "/www/").setDefaultFile("index.html");
initWebSocket();
// Web Server Root URL
server.on("/", HTTP_GET, [](AsyncWebServerRequest *request){
request->send(SPIFFS, "/index.html", "text/html");
});
server.begin();
Serial.println("HTTP伺服器開工了~");
}
void loop() {
delay(500);
ws.cleanupClients();
notifyClients();//發送給CLIENT
}
重點說明:
上圖的功能為發送給全客戶端,透過JSON套件讓我們可以更輕鬆地完成JSON格式,
ws.textAll()這個地方要注意,他原本只能發送String的資料,透過改變定義來實現發送JSON的功能
上面這一塊原本他所要帶入的資料型態如下
request->send( 狀態碼 , 內容類型 , 內容 )
但因為我們的前端是存在SPIFFS的,所以作者直接更改了參數類型,如下圖
詳細得可以到下面連結的Github
上圖的notifyClients之所以可以放在LOOP的地方並且可以一直刷新是因為set up()的地方已經建立好通道,之後
傳訊息都不用再做握手的動作了. 這也是websocket最大的特點
ws.cleanupClients()的用意在於有時候會遇到某個客戶端中斷連接但ESP32自己斷不掉,如果持續下去的話會消耗沒意義的資源
畢竟MCU的效能有限,所以才會設計出這個功能
網頁端:
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>ESP32物聯網</title>
<link href="https://code.jquery.com/ui/1.11.4/themes/smoothness/jquery-ui.css" rel="stylesheet" type="text/css">
<style type="text/css">
body {
font-family: "微軟正黑體", "黑體-繁", sans-serif;
}
#slider {
width: 300px;
margin: 15px;
}
</style>
</head>
<body>
<h1>ESP WebSocket Server-12/22</h1>
<div class="content">
<h2>Output - GPIO 2</h2>
<p id="state1">%STATE1%</p>
<p id="state2">%STATE2%</p>
<p><button id="Abutton" class="button">PRESS1</button></p>
<p><button id="Bbutton" class="button">PRESS2</button></p>
</div>
<script src="https://code.jquery.com/jquery-3.5.1.min.js"></script>
<script>
var gateway = `ws://${window.location.hostname}/ws`;
var websocket;
window.addEventListener('load', onLoad);
function onLoad(event) {
initWebSocket();
initButton();
}
function initWebSocket() {
console.log('Trying to open a WebSocket connection...');
websocket = new WebSocket(gateway);
websocket.onopen = onOpen;//连接建立时触发
websocket.onclose = onClose;//连接关闭时触发
websocket.onmessage = onMessage; // <-- add this line//客户端接收服务端数据时触发
}
function onOpen(event) {
console.log('Connection opened');
}
function onClose(event) {
console.log('Connection closed');
setTimeout(initWebSocket, 2000);
}
function onMessage(event) {
let data = JSON.parse(event.data);
document.getElementById('state1').innerHTML = data.room;
document.getElementById('state2').innerHTML = data.rand;
}
function initButton() {
document.getElementById('Abutton').addEventListener('click', toggle1);
document.getElementById('Bbutton').addEventListener('click', toggle2);
}
function toggle1(){
websocket.send('Atoggle');
}
function toggle2(){
websocket.send('Btoggle');
}
</script>
</body>
</html>
重點說明:
這裡的功能也很好理解就是當BUTTON的點擊事件觸發時,會發送Atoggle或Btoggle給ESP32
當你點擊時你會在監控視窗上看到Atoggle或Btoggle跳出來,這也意味著你可以透過這個方式來控制硬體
下面兩張圖是JSON格式收到收到之後透過元件ID的方式在指定的地方顯示出來:
如果你直接將下載的專案檔都正確的燒錄進去你可以看到以下畫面
按按鈕可以看到成功收到字串