راه اندازی وب سرور غیر همزمان(Async) با ESP8266

سلام

در قسمت هفتم یک وب سرور ساده با کتابخانه اورجینال ESP8266 راه اندازی کردیم و توسط وب توانستیم یک LED رو کنترل کنیم. در این قسمت کمی بیشتر با وب سرور و طراحی صفحات آشنا می شویم و با کتابخانه غیرهمزمان (Async) کار خواهیم کرد.



نصب کتابخانه در آردوینو

  • در قسمت قبل مشکل همزمان بودن کتابخانه اورجینال رو بررسی کردیم و چون دستور هندلینگ کلاینت ها در لوپ برنامه بود امکان استفاده از دستورات زمان بر در لوپ وجود نداشت. برای حل این مشکل از دو کتابخانه زیر استفاده خواهیم کرد:
  • ESP Async TCP
  • ESP Async WebServer


برای نصب کتابخانه در آردوینو دو روش وجود دارد:

  1. مراجعه به مسیر Sketch --> Include Library --> Manage Libraries و جستوجو نام کتابخانه، در صورتی که کتابخانه در لیست موجود بود میتوانید آن را نصب کنید.
  2. در مسیر Sketch --> Include Library گزینه Add .ZIP Library را کلیک کنید و فایل ZIP کتابخانه را آدرس دهی کنید.

برای نصب کتابخانه های بالا با استفاده دو لینک زیر فایل ZIP آنها را دانلود می کنیم.

سپس مطابق با روش دوم آنها را به آردوینو اضافه می کنیم.



راه اندازی وب سرور با کتابخانه Async Web Server

در اول برنامه کد های زیر را داریم: (کد ها رو یکجا میتونید از لینک آخر همین صفحه دانلود کنید)

#include <Arduino.h>
#include <ESP8266WiFi.h>
#include <ESPAsyncTCP.h>
#include <ESPAsyncWebServer.h>

const char* ssid = &quotYOUR_SSID&quot 
const char* password = &quotYOUR_PASSWORD&quot

AsyncWebServer server(80);

کتابخانه های ESPAsyncTCP و ESPAsyncWebServer را علاوه بر موارد قبلی فراخوانی می کنیم. همچنین پورتی که سرور روی آن اجرا می شود را نیز تعیین می کنیم (80). کانفیگ شبکه وای فای هم همانند قبل است.


توابع led_on, led_off, handleNotFound که در جلسه قبل در مورد آنها صحبت کردیم نیازمند کمی تغییرات به شرح زیر هستند:

void handleNotFound(AsyncWebServerRequest *request) {
    String message = &quotPage Not Found\n\n&quot
    message += &quotURL: &quot
    message += request->url();
    request->send(404, &quottext/plain&quot, message);

}
void led_on(AsyncWebServerRequest *request){
    digitalWrite(LED_BUILTIN, LOW);
    request->send(200, &quottext/plain&quot, &quotLED is ON!&quot);
}

void led_off(AsyncWebServerRequest *request){
    digitalWrite(LED_BUILTIN, HIGH);
    request->send(200, &quottext/plain&quot, &quotLED is OFF!&quot);
}

در آرگومان تمام توابع باید AsyncWebServerRequest *request ارسال شود و دستور ارسال صفحه وب به request->send تغییر یافته است. همچنین برای دریافت آدرس صفحه (بدون Hostname) باید عبارت ()request->url فراخوانی شود.


کد Setup

void setup() {
    pinMode(LED_BUILTIN, OUTPUT);
    digitalWrite(LED_BUILTIN, HIGH);

    Serial.begin(115200);
    Serial.println(&quot&quot);
    WiFi.mode(WIFI_STA);
    WiFi.begin(ssid, password);
    while (WiFi.status() != WL_CONNECTED) {
        delay(500);
        Serial.print(&quot.&quot);
    }

    Serial.print(&quot\nIP Address: &quot);
    Serial.println(WiFi.localIP());

    server.on(&quot/&quot, HTTP_GET, [](AsyncWebServerRequest *request){
        request->send(200, &quottext/plain&quot, &quotHello, world&quot);
    });
    server.on(&quot/on&quot, HTTP_GET, led_on);
    server.on(&quot/off&quot, HTTP_GET, led_off);
    server.onNotFound(handleNotFound);

    server.begin();
}

بعد از تنظیم LED به عنوان خروجی و خاموش کردن آن، راه اندازی وای فای و چاپ آی پی به دستورات سرور می رسیم.

در اینجا هم تابع server.on کم متفاوت است. آرگومان اول که همان path است. اما آرگومان دوم روش (Method) تبادل دیتاست که فعلا HTTP_GET رو در نظر میگیریم و آرگومان سوم تابع مدیریت درخواست است. اگر تابع از پیش تعریف شده داشتیم که فقط اسم آن را parse می کنیم اگر نه به صورت (arg)[] تابع را در همان آرگومان تعریف می کنیم با این تفاوت که آرگومان تابعی که تعریف میکنیم AsyncWebServerRequest *request خواهد بود.

و نهایتا دستورات NotFound و اجرای سرور.


و اما بخش جذاب این کد حلقه Loop اون هست:

void loop() {}

آخه لوپ قشنگ تر از این هست؟ درسته من دشمن لوپ هستم!

همون طور که در اول آموزش اشاره کردم این کتابخونه نیازی به Handle کردن client هست در لوپ نداره و مزیت خیلی بزرگیه، میتونیم هر چی دوست داشتیم در loop بنویسیم :)))

بریم تست بگیریم! اگر همه چیز درست انجام شده باشه باید مثل قسمت قبل همه دستورات کار کنند.



به سوی HTML

می خواهیم وضعیت فعلی LED در صفحه Home نوشته شود!

اول یک تابع تعریف می کنیم که اگر پایه LED_BUILTIN صفر بود یک رشته به معنی روشن بودن LED برگرداند و در غیر این صورت رشته خاموش بودن را برگرداند. (اگر نمی دونید چرا ولتاژ پین با وضعیت LED برعکسه به آموزش قبل مراجعه کنید)

String getLedStatus(){
    if(digitalRead(LED_BUILTIN))
        return &quotLED is OFF!&quot
    else
        return &quotLED is ON!&quot
}

سپس باید این خروجی را در صفحه اول نمایش دهیم. یعنی باید دیتایی که ارسال می شود شامل خروجی این تابع باشد.

تابع هندل کردن صفحه اصلی را به صورت زیر تغییر می دهیم:

server.on(&quot/&quot, HTTP_GET, [](AsyncWebServerRequest *request){
    String index = &quot<html><head><title>My Async WebServer</title></head>&quot
    index += &quot<body><center>Hello, World</center></br>&quot
    index += &quot<center>&quot + getLedStatus() + &quot<center>&quot
    index += &quot</body></html>&quot
    request->send(200, &quottext/html&quot, index);
});

این تابع زمانی که صفحه درخواست شد یک رشته به نام index تهیه می کند که شامل چند کد HTML است.(مقدمات HTML در پی نوشت آورده شده است) در خط چهارم این کد تابع getLedStatus فراخوانی شده و پس از اجرا و خواندن ولتاژ منطقی پایه LED یک رشته مبنی بر وضعیت آن بر می گرداند و در کد HTML قرار میدهد.

و مورد آخر در این تابع داریم کد HTML ارسال میکنیم پس آرگومان دوم دستور send باید به "text/html" تغییر کند. اگر همون "text/plain" باشه چی میشه؟ امتحان کنید :)


در صورتی که با استفاده از on/ یا off/ وضعیت LED را تغییر دهید و سپس به صفحه اول مراجعه کنید وضعیت آن به درستی نمایش داده می شود (دقت کنید که حتما باید صفحه Refresh بشه یا مجدد لود بشه! برای آپدیت Real-Time وضعیت LED نیازمند Ajax, Js هستیم!)

در صفحه اول کد HTML به خوبی اجرا شده و title به عبارت دلخواه تغییر کرده و متن ها هم وسط صفحه به نمایش آمدند.



ریدایرکت (Redirect)

تا اینجا خوب اومدیم ولی یه مشکلی داره! باید کلی راه بریم on/ یا off/ رو بزنیم بعد کلی راه برگردیم توی صفحه اول وضعیتش رو ببینیم! پس بذاریم این راه هارو خودش بره :)

باید کاری کنیم که وقتی به یکی از آدرس های on/ یا off/ رفتیم دوباره مارو به صفحه اول برگردونه به این عمل Redirect میگیم. خیلی هم آسونه.

کافیه به در توابع led_on و led_off به جای اینکه بگیم یک صفحه جدید ارسال کن، Redirect کنیم پس به جای ()request->send کد زیر رو جایگزین میکنیم:

request->redirect(&quot/&quot);

آرگومان داخل redirect آدرسی است که میخواهیم ریدایرکت کنیم. می تواند یک آدرس داخلی باشد مثل "/" یا "login/" و هم میتواند یک آدرس خارجی باشد مثلا "virgool.io"

حالا به محض وارد کردن آدرس های on/ یا off/ عملیات مربوطه روی LED اجرا می شود و مجددا به صفحه اول باز میگردد.


خب الان نوبت برعکسشه! که از صفحه اول بتونیم به on/ یا off/ بریم. و این کار با HTML انجام میشه!

برای این کار دو لینک باید داشته باشیم که به صفحات on/ و off/ بروند و زمانی که این آدرس ها درخواست شدند LED تغییر وضعیت می دهد، متن وضعیت LED تغییر میکند و به صفحه اول باز میگردد. پس کد هندلینگ صفحه اول را به شرح زیر اصلاح میکنیم:

server.on(&quot/&quot, HTTP_GET, [](AsyncWebServerRequest *request){
    String index = &quot<html><head><title>My Async WebServer</title></head>"
    index += &quot<body><center>Hello, World</center></br>"
    index += &quot<center>&quot + getLedStatus() + &quot<center></br>"
    index += &quot<button><a href=\&quot/on\&quot>ON</a></button>"
    index += &quot<button><a href=\&quot/off\&quot>OFF</a></button>"
    index += &quot</body></html>"
    request->send(200, &quottext/html&quot, index);
});

خط پنجم و ششم دو دکمه که هر کدام به لینک مربوطه اشاره می کند قرار داده شده و نتیجه به صورت زیر میشود. (برای درج double quotation یا همون " در یک رشته باید آن را همراه با بک اسلش به صورت "\ نویسیم)

وب سرور نهایی
وب سرور نهایی



پی نوشت: HTML

_ این یک رفرنس HTML نیست _

هر صفحه وبی از کد HTML و... تشکیل شده و HTML یک زبان نشانه گذاری است. ساختار کلی آن به صورت زیر است:

<html>
    <head>
        <title> PAGE TITLE </title>
    </head>
    <body>
        CONTENT
    <body>
</html>

به هر کدوم از <sth> یک تگ HTML هستند و اغلب دارای تگ بسته شدن به صورت <sth/> هستند.

این ساختار در هر صفحه ای ثابت است و فقط عبارت هایی به آن اضافه می شود در PAGE TITLE اسم صفحه رو وارد می کنید. و در بین تگ های body بدنه یا همون صفحه رو ایجاد می کنیم.

توضیح برخی تگ ها:

  • تگ center: عبارتی که بین این تگ قرار گیرد در وسط صفحه (وسط بلاک) قرار می گیرد.
  • تگ br: این تگ یک خط جدید ایجاد می کند. همان r/n/ خودمان! این تگ، تگ بسته شدن ندارد و مجرد است.
  • تگ button: این تگ یک دکمه ایجاد می کند. و عبارتی که بین آن قرار میگیرد متن دکمه خواهد بود.
  • تگ a: با استفاده از این تگ میتوان لینک ایجاد کرد به این صورت که باید attribute (هر چیزی که بعدی از باز کردن تگ مینویسیم) مربوط به مرجع لینک (href) مقدار دهی شود. و عبارتی که بین آن قرار میگیرد متن لینک است. مثلا برای اینجا یک لینک به گوگل با عنوان GO to Google کد زیر را مینویسیم.
<a href=&quotgoogle.com&quot>GO to Google</a>

حال اگر یک تگ a را داخل button بگذاریم اول متن تگ a در متن button نمایش داده می شود و ثانیا با کلیک بر روی دکمه به لینک مربوطه خواهیم رفت.

اگر اطلاعات بیشتری در مورد HTML نیاز دارید میتوانید از لینک های زیر استفاده کنید.



چه کردیم و چه باید کنیم؟

هنوز کارمون تموم نشده!

  1. یه مشکل داریم هر کسی لینک رو داشته باشه میتونه LED رو خاموش و روشن کنه؛ فرض کنید به جای LED توسط یک رله لامپ اتاق رو خاموش روشن بکنه و قرار کلی حرص بخوریم! پس باید یک authentication برای اون تعریف کنیم که مثلا فقط با یوزرنیم پسورد بشه خاموش و روشنش کرد.
  2. بنظرتون ذخیره کردن کوهی از کد HTML اون هم String توی حافظه فلش درسته؟ قطعا افتضاحه!!! این مشکل رو هم باید حل کنیم. (پیش به سوی SPIFFS , LittleFS)
  3. باید یکم صفحه رو خوشگل ترش کنیم، چیه این اخه؟! (پیش به سوی bootstrap و css)

چقدر زیاد شد :(



کد های این آموزش رو می توانید از اینجا دریافت کنید.


همه موارد بالا رو در قسمت نهم و شاید هم در قسمت های بزرگ تر مساوی 9 انجام خواهیم داد :)

اگر سوالی، ابهامی، نقدی، نظری، پیشنهادی و کلا هر درد دلی دارین در قسمت نظرات همراهتون هستم.