⚡ دادهی زنده با WebSocket روی ESP32
در صفحهی دماسنج، مرورگر هر چند ثانیه یک بار صفحه را دوباره میخواست. این روش کار میکند، ولی کند است و کلی دادهی تکراری جابهجا میکند. WebSocket یک خط ارتباطی همیشه باز بین مرورگر و ESP32 است: هر وقت عدد عوض شد، برد خودش آن را میفرستد و هر وقت دکمهای زدی، فرمان در یک چشم به هم زدن به برد میرسد. در این آموزش با کتابخانهی ESPAsyncWebServer این کار را میکنیم و در شبیهساز ESP32 فارسی، مثل ووکوی (Wokwi)، نتیجه را میبینیم.
چه میسازیم؟
صفحهای که درصد چرخش یک ولوم را با عدد بزرگ و زنده نشان میدهد (دو بار در ثانیه) و یک دکمه دارد که LED را خاموش و روشن میکند. اگر صفحه را در دو مرورگر باز کنی، هر دو همزمان بهروز میشوند.
قطعهها و سیمکشی
| قطعه | اتصال |
|---|---|
| پتانسیومتر | کناریها به 3V3 و GND، وسط به GPIO34 |
| LED + مقاومت ۲۲۰ اهم | GPIO4 و GND |
کد کامل
#include <WiFi.h>
#include <AsyncTCP.h>
#include <ESPAsyncWebServer.h>
const int LED = 4;
const int KNOB = 34;
bool ledOn = false;
unsigned long lastSend = 0;
AsyncWebServer server(80);
AsyncWebSocket ws("/ws"); // the socket lives at /ws
const char page[] PROGMEM = R"rawliteral(<!DOCTYPE html><html>
<head><meta name="viewport" content="width=device-width, initial-scale=1"></head>
<body style="font-family:sans-serif;text-align:center">
<h2>Live knob</h2>
<p style="font-size:48px" id="v">--</p>
<button onclick="ws.send('toggle')">Toggle LED</button>
<p id="led">LED: ?</p>
<script>
var ws = new WebSocket('ws://' + location.host + '/ws');
ws.onmessage = function (e) {
if (e.data.indexOf('led:') === 0) {
document.getElementById('led').innerText = 'LED: ' + e.data.substring(4);
} else {
document.getElementById('v').innerText = e.data + ' %';
}
};
</script></body></html>
)rawliteral";
void onEvent(AsyncWebSocket *server, AsyncWebSocketClient *client, AwsEventType type,
void *arg, uint8_t *data, size_t len) {
if (type == WS_EVT_CONNECT) {
Serial.print("client connected: ");
Serial.println(client->id());
client->text(ledOn ? "led:ON" : "led:OFF");
} else if (type == WS_EVT_DATA) {
AwsFrameInfo *info = (AwsFrameInfo *)arg;
if (info->final && info->index == 0 && info->len == len && info->opcode == WS_TEXT) {
String msg = "";
for (size_t i = 0; i < len; i++) msg += (char)data[i];
if (msg == "toggle") {
ledOn = !ledOn;
digitalWrite(LED, ledOn ? HIGH : LOW);
ws.textAll(ledOn ? "led:ON" : "led:OFF"); // tell every open page
}
}
}
}
void setup() {
Serial.begin(115200);
pinMode(LED, OUTPUT);
WiFi.begin("Wokwi-GUEST", ""); // simulator network, no password
while (WiFi.status() != WL_CONNECTED) delay(200);
Serial.println(WiFi.localIP());
ws.onEvent(onEvent);
server.addHandler(&ws);
server.on("/", HTTP_GET, [](AsyncWebServerRequest *request) {
request->send(200, "text/html", page);
});
server.begin();
}
void loop() {
ws.cleanupClients(); // forget closed pages
if (millis() - lastSend > 500) {
lastSend = millis();
int percent = map(analogRead(KNOB), 0, 4095, 0, 100);
ws.textAll(String(percent));
}
}
توضیح بخشهای مهم
AsyncWebServerبرخلافWebServer«ناهمگام» است: لازم نیست درloopمدامhandleClientصدا بزنی؛ کتابخانه خودش در پسزمینه جواب میدهد.AsyncWebSocket ws("/ws");یک نقطهی WebSocket روی مسیر/wsمیسازد وserver.addHandler(&ws)آن را به سرور میچسباند.- تابع
onEventبرای هر اتفاق صدا زده میشود: وصل شدن یک صفحه (WS_EVT_CONNECT) یا رسیدن پیام (WS_EVT_DATA). شرط طولانیinfo->final ...مطمئن میشود پیام کامل و متنی است. ws.textAll(...)یک پیام را به همهی صفحههای باز میفرستد؛client->text(...)فقط به همان یکی.- در جاوااسکریپت صفحه،
new WebSocket('ws://' + location.host + '/ws')به همان برد وصل میشود وws.onmessageهر پیام رسیده را نشان میدهد. پیامهایی که باled:شروع شوند وضعیت چراغاند و بقیه عدد ولوم. ws.cleanupClients()صفحههایی را که بسته شدهاند از حافظه پاک میکند تا حافظه پر نشود.
WebSocket کجا به کار میآید؟
هر جا که سرعت مهم است، WebSocket برنده است: کنترل یک ماشین رباتیک با دکمههای جهت، بازیهای دونفره که روی دو گوشی اجرا میشوند، یا نمایش لحظهای فاصلهسنج. چون اتصال همیشه باز است، لازم نیست برای هر پیام یک درخواست تازه ساخته شود و تأخیر خیلی کم میماند. یک تمرین خوب: یک اسلایدر به صفحه اضافه کن که عددش را با ws.send بفرستد و در C++ با ledcWrite نور LED را تنظیم کند.
امتحانش کن
در تینکرکد فارسی دو مثال آماده هست: LED زنده با WebSocket روی ESP32 و عدد زنده با WebSocket. اگر میخواهی اول خود ESPAsyncWebServer را بدون WebSocket ببینی، وبسرور سریع ESP32 را باز کن. خواندن مثالها رایگان است و اجرای ESP32 با اشتراک Plus؛ شبیهساز WebSocket را کامل شبیهسازی میکند. توضیح درسی در درس «بهروزرسانی زنده با WebSocket».
اشکالهای رایج
- خطای «AsyncTCP.h: No such file»: ESPAsyncWebServer به کتابخانهی AsyncTCP نیاز دارد. هر دو را نصب کن (در Wokwi.com هر دو باید در فهرست کتابخانهها باشند).
- عدد در صفحه عوض نمیشود: در جاوااسکریپت
ws://راhttp://نوشتهای، یا مسیر با/wsدر کد C++ یکی نیست. - با وصل بودن وایفای، ولوم همیشه ۰ یا ۴۰۹۵ است: پتانسیومتر را به یکی از پینهای ADC2 وصل کردهای. GPIO32 تا ۳۹ را انتخاب کن.
- برد بعد از مدتی ریست میشود: داخل
onEventازdelayاستفاده کردهای. کدهای ناهمگام نباید منتظر بمانند.
اگر فقط داده از برد به مرورگر میرود و برعکسش لازم نیست، راه سادهتری هم هست: نمودار زنده با EventSource (SSE). قبل از این، وبسرور سادهی ESP32 را هم ببین. پروژههای بیشتر در وایفای و اینترنت اشیا.
LED زنده با WebSocket را باز کن ⚡