為了以更有系統及益於維護為前提,在EPS32上實現Websocket該範例,會用到以下幾篇文章,建議先參考以下幾篇文章

SPIFFS:

https://karta146831.pixnet.net/blog/post/334863831-esp32--%e5%a4%96%e6%8e%9bspiffs-%e5%ae%89%e8%a3%9d%e6%95%99%e5%ad%b8

WebServer顯示數值(局部刷新):

https://karta146831.pixnet.net/blog/post/334959285-esp8266-esp32-asyncwebserver-%e9%a1%af%e7%a4%ba%e6%95%b8%e5%80%bc%28%e5%b1%80%e9%83%a8%e5%88%b7%e6%96%b0%29%e5%90%ab

WebServer(非同步伺服器) 控制IO :

https://karta146831.pixnet.net/blog/post/334863888-esp32-asyncwebserver%28%e9%9d%9e%e5%90%8c%e6%ad%a5%e4%bc%ba%e6%9c%8d%e5%99%a8%29-%e5%9f%ba%e7%a4%8e%e6%95%99%e5%ad%b8%e5%90%ab%e7%af%84

 

mDNS:

https://karta146831.pixnet.net/blog/post/335390963-esp32--mdns-server%e4%bd%bf%e7%94%a8%e6%95%99%e5%ad%b8%28%e5%a6%82%e4%bd%95%e5%84%aa%e5%8c%96esp32-webserver%29

 

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
}

重點說明:

image

上圖的功能為發送給全客戶端,透過JSON套件讓我們可以更輕鬆地完成JSON格式,

ws.textAll()這個地方要注意,他原本只能發送String的資料,透過改變定義來實現發送JSON的功能

 

image

上面這一塊原本他所要帶入的資料型態如下

request->send( 狀態碼 , 內容類型 , 內容 )

但因為我們的前端是存在SPIFFS的,所以作者直接更改了參數類型,如下圖

image

詳細得可以到下面連結的Github

https://github.com/me-no-dev/ESPAsyncWebServer?fbclid=IwAR3Edql3kCTumYLFvXD2RqJwMK6BLT8jB0Bunc2gvcXI00_dn2COxyYryls#respond-with-content-coming-from-a-file

 

 

image

上圖的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>

 

重點說明:

image

這裡的功能也很好理解就是當BUTTON的點擊事件觸發時,會發送Atoggle或Btoggle給ESP32

當你點擊時你會在監控視窗上看到Atoggle或Btoggle跳出來,這也意味著你可以透過這個方式來控制硬體

 

下面兩張圖是JSON格式收到收到之後透過元件ID的方式在指定的地方顯示出來:

image

image

 

如果你直接將下載的專案檔都正確的燒錄進去你可以看到以下畫面

image

按按鈕可以看到成功收到字串

image

 

 

 

 

 

 

 

 

 

 

arrow
arrow
    創作者介紹
    創作者 凶王 的頭像
    凶王

    凶王的部落

    凶王 發表在 痞客邦 留言(0) 人氣()