إنترنت الأشياءلغة سي و متحكماتمقالات

كل ما يتعلق بـESP32 -الجزء الثالث

الـTCP والـHTTP عبر الـWiFi

أجزاء سلسلة كل ما يتعلق بـESP32

متعلقات

لقد تم تغطية العديد من المفاهيم المتعلقة بالـESP32 عبر الأجزاء السابقة، مما يسمح للانتقال لقضايا أكثر تعمقاً لاحقاً. فلقد تعرفنا في الجزء السابق على العمليات الأساسيّة للواي فاي في ESP-IDF بما في ذلك إجراء مسح للشبكات المتاحة والاتصال بأحدها. سنوظّف في هذا الجزء الاتصال الذي قمنا به وذلك للقيام باتصال TCP و عمليات HTTP، وسنكتشف بأية حال أن جزء كبير من برمجة الواي فاي في ESP32 أنه متعلق ببرمجة الـSocket بلغة السي باستخدام LwIP stack. كما قامت شركة Espressif ولتسهيل الأمور بطرح مكتبة متخصصة بالعمليات اللازمة بعمليات الـHTTP كمستخدم Client في ESP-IDF.

 

LwIP Stack

 

يستخدم الـESP32 الـLwIP stack، ولذلك للقيام باتصال TCP و عمليات HTTP حيث سيتم استدعاء توابع من LwIP حيث تحوي طبقة الـLwIP ‘s application API layers توابع خاصة للتعامل مع الـSocket وهذه التوابع متوافقة مع شكل توابع نظام BSD أي compatible with standard BSD-sockets. هذا الموضوع socket programming in C واسع ، ولذلك سنقوم بتغطية الأساسيات فقط.

سأستلف في ما يلي أجزاء من شرح رائع لموضوع TCP/IP socket programming in C من مقالة من موقع BinaryTides. يعرّف المقال في الموقع الـSocket على أنها نقاط اتصال “افتراضية” من أجل أي نوع من أنواع الاتصالات الشبكية بين مستخدمين host على الشبكة. مثال: عندما نحدد موقع غوغل للنفاذ له في المتصفح، فإنه يتم فتح Socket مرتبطة بالموقع لجلب المعلومات وعرضها. إن الجزء الأول الذي سنقوم به إذا هو إنشاء Socket عبر استدعاء التابع socket:

int socket(int domain, int type, int protocol);

  • يحدد الوسيط الأول عائلة بروتكول الاتصال (مثلاً: AF_INET6 من أجل بروتكولات IPv6 ).
  • يأخذ الوسيط الثاني قيمة SOCK_STREAM من أجل اتصال TCP أو SOCK_DGRAM من أجل اتصال UDP.
  • أما الوسيط الثالث فهو لتحديد بروتوكول معين ضمن عائلة الاتصال و يكون البروتوكول وحيد بالنسبة للـSocket ذات النوع المحدد بشكل طبيعي وبهذه الحالة تكون القيمة صفر.

يُعد استدعاء هذا التابع رقم، وهو رقم موصف لملف file descriptor خاص بالـSocket، ويستخدم هذا الرقم لاحقاً في توابع أخرى.

إذا إن الجزء الأول من برمجة الـSocket هو:

int socket_desc;
    socket_desc = socket(AF_INET , SOCK_STREAM , 0);
     
    if (socket_desc == -1)
    {
        printf("Could not create socket");
    }

 

من طرف المُخدّم

 

يجب ربط الـSocket بعنوان address ورقم منفذ port محدّدين، أو يمكن أن يكون IP غير محدد عبر الخيار INADDR_ANY. يمكن تحديد العنوان والمنفذ من خلال متحول من نوع struct sockaddr_in.

struct sockaddr_in  server

//Prepare the sockaddr_in structure
server.sin_family = AF_INET;
server.sin_addr.s_addr = INADDR_ANY;
server.sin_port = htons( 8888 );
     
if( bind(socket_desc,(struct sockaddr *)&server , sizeof(server)) < 0)
{
    printf("bind failed");
}
printf("bind done");

إن sockaddr_in struct في LwIP هو:
struct sockaddr_in {
  u_short         sin_family;     /* always AF_INET                          */
  u_short         sin_port;       /* the service port                        */
  struct in_addr  sin_addr;       /* the IP address                          */
  char            sin_zero[8];    /* unused (reserved for expansion          */
};

الخطوة الأخيرة في التهيئة هي استدعاء التابع listen وذلك لبدء الاستماع على هذا الـSocket. يأخذ هذا التابع وسيطين: الأول هو رقم الموصف للـSocket ويحدد الثاني العدد الأعظمي للاتصالات المسموحة maximum number of queued connections.
listen(socket_desc , 3);

لاحقاً يقوم التابع accept بالتحقق من طلبات الاتصال المنتظرة ويعيد رقم موصّف جديد يتم استخدامه مثلاً في عملية الكتابة والقراءة، وأما الوسطاء لهذا التابع: فالأول هو الرقم الموصّف للـSocket والثاني متحول من نوع struct sockaddr ويستخدم لتخزين عنوان الـSocket المقابل.
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);

من الممكن لاحقاً تنفيذ القراءة والكتابة باستخدام التوابع read و write.

بوضع كل شيء معاً:

while(1)
{
   printf("Waiting for incoming connections...");
    int new_socket, c = sizeof(struct sockaddr_in);
    new_socket = accept(socket_desc, (struct sockaddr *)&client, (socklen_t*)&c);
    if (new_socket<0)
    {
            printf("accept failed");
    }
     else
{
char *message = "Hello Client , I have received your connection\n";
write(new_socket , message , strlen(message));

}
}

 

من طرف المستخدم

 

إذا كانت الـSocket المنشأة بغرض الاتصال بعنوان خارجي remote address كالنفاذ لموقع انترنت فإن التابع connect يستخدم عوضاً عن bind مع الوسطاء نفسها ومن دون حاجة لاستدعاء accept و listem. إن المخطط التالي المرفق في إجابة على Stackoverflow توضح ذلك.

ESP32 BSD Socket Creation

 

نقطة نفاذ أو ما يعرف بـ (SoftAP)

 

يلزمنا في تطبيق لاحق جعل الـESP32 بنمط العمل كنقطة نفاذ Access Point وهو النمط SoftAP. حيث يقوم التطبيق على اتصال مستخدم بشبكة الـESP32 ثم القيام باتصال TCP عبر telnet/PuTTY.

سنستخدم لتهيئة الـESP32 للعمل كنقطة نفاذ نفس التوابع المُستخدمة عندما تم ضبطها للاتصال بشبكة محددة في الدرس السابق، ولكن باستخدام متحول بقيم مختلفة من wifi_config_t.

wifi_config_t wifi_config = {
        .ap = {
            .ssid = EXAMPLE_ESP_WIFI_SSID,
            .ssid_len = strlen(EXAMPLE_ESP_WIFI_SSID),
            .password = EXAMPLE_ESP_WIFI_PASS,
            .max_connection = EXAMPLE_MAX_STA_CONN,
            .authmode = WIFI_AUTH_WPA_WPA2_PSK
        },
    };

حيث يحوي النوع wifi_config_t على جزئين وهما:
typedef union {
    wifi_ap_config_t  ap;  /**< configuration of AP */
    wifi_sta_config_t sta; /**< configuration of STA */
} wifi_config_t;

كنا قد استعملنا في المرة الأخيرة الجزء sta من المتحول ولكن هذه المرة سنستخدم الجزء ap. تبدو عملية تهيئة الـESP32 كنقطة نفاذ كالتالي:
void wifi_init_softap()
{

    tcpip_adapter_init();
    esp_event_loop_init(event_handler, NULL);

    wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();

    esp_wifi_init(&cfg);

    wifi_config_t wifi_config = {
        .ap = {
            .ssid = EXAMPLE_ESP_WIFI_SSID,
            .ssid_len = strlen(EXAMPLE_ESP_WIFI_SSID),
            .password = EXAMPLE_ESP_WIFI_PASS,
            .max_connection = EXAMPLE_MAX_STA_CONN,
            .authmode = WIFI_AUTH_WPA_WPA2_PSK
        },
    };

    if (strlen(EXAMPLE_ESP_WIFI_PASS) == 0) {
        wifi_config.ap.authmode = WIFI_AUTH_OPEN;
    }

    esp_wifi_set_mode(WIFI_MODE_AP);
    esp_wifi_set_config(ESP_IF_WIFI_AP, &wifi_config);
    esp_wifi_start();

    ESP_LOGI(TAG, "wifi_init_softap finished.SSID:%s password:%s",
             EXAMPLE_ESP_WIFI_SSID, EXAMPLE_ESP_WIFI_PASS);
}

ولنلقي نظرة الآن على التابع event_handler. يمكن للأحداث Events الخاصة بالـAP أن تكون واحدة من هذه:
SYSTEM_EVENT_AP_START,                 /**< ESP32 soft-AP start */
    SYSTEM_EVENT_AP_STOP,                  /**< ESP32 soft-AP stop */
    SYSTEM_EVENT_AP_STACONNECTED,          /**< a station connected to ESP32 soft-AP */
    SYSTEM_EVENT_AP_STADISCONNECTED,       /**< a station disconnected from ESP32 soft-AP */

وهذا مثال على تابع، ولمراجعة كيفية عمل التابع يمكن الاطلاع على ذلك في الدرس الماضي.
esp_err_t event_handler(void *ctx, system_event_t *event)
{

    tcpip_adapter_ip_info_t ip;

    switch(event->event_id) {

    case SYSTEM_EVENT_AP_STACONNECTED:

               ESP_LOGI(TAG,"station:"MACSTR"join”,MAC2STR(event->event_info.sta_connected.mac));
                tcpip_adapter_get_ip_info(TCPIP_ADAPTER_IF_AP, &ip);    
                ESP_LOGI(TAG, "SoftAP IP=%s", inet_ntoa(ip.ip.addr));
}
        
        break;

    case SYSTEM_EVENT_AP_STADISCONNECTED:
        ESP_LOGI(TAG, "station:"MACSTR"leave",
                 MAC2STR(event->event_info.sta_disconnected.mac));
        break;
    default:
        break;
    }
    return ESP_OK;
}

يحوي المتحول system_event_t على معلومات متعلقة بالمستخدمين المتصلين/غير المتصلين بنقطة نفاذ الـESP32 وذلك باستخدام event_info.sta_disconnected.mac مثلاً.

شيء آخر يستحق الذكر وهو التابع tcpip_adapter_get_ip_info والذي يستخدم للحصول على IP الشبكة، ونهاية التابع الذي يستخدم لتحويل القيمة العددية للـIP إلى قيمة نصية بالشكل (x.x.x.x).

tcpip_adapter_get_ip_info(TCPIP_ADAPTER_IF_AP, &ip);    
                ESP_LOGI(TAG, "SoftAP IP=%s", inet_ntoa(ip.ip.addr));

 

 

التطبيق الأول: HTTP Request

 

سنقوم في هذا المثال التطبيقي بتهيئة الـESP32 للاتصال بنقطة نفاذ معينة للوصول إلى الإنترنت وثم إنشاء Socket وباعتبار أننا سنكون في حالة مستخدم سيتصل بموقع فنحن client وبالتالي لن نستدعي accept أو listen.

سنقوم أيضاً باستدعاء خدمة الـDNS باعتبار أن رسائل الـHTTP ستكون لموقع ويب معين وذلك لتحويل اسم الموقع إلى IP. يتم ذلك باستدعاء getaddrinfo والذي سيخزّن النتيجة في متحول يتم تمريره له.

lwip_getaddrinfo(const char *nodename, const char *servname, const struct addrinfo *hints, struct addrinfo **res)
;

سنناقش أمراً أخيراً وقبل إعطاء كود المثال الكامل وهو إضافة خيارات مُتحكم بها من قائمة menuconfig خاصة بالمشروع نفسه وهذا محور الفقرة القادمة.

إعدادات خاصة من قائمة Menuconfig

 

إن الكود الذي يتعامل مع الواي فاي على سبيل المثال غالباً ما يحوي ضبط لكلمة السر واسم الشبكة المراد الاتصال بها وذلك بتعريف define في الملف المصدري. يمكن لتغير هذه القيمة من قائمة menuconfig أن يتم إنشاء ملف باسم Kconfig.projbuild يوضع في نفس مسار الكود المصدري ويحوي هذا الملف على المحتوى التالي المكتوب بلغة Kconfig:
menu "Example Configuration"

config WIFI_SSID
    string "WiFi SSID"
    default "myssid"
    help
        SSID (network name) for the example to connect to.

config WIFI_PASSWORD
    string "WiFi Password"
    default "mypassword"
    help
        WiFi password (WPA or WPA2) for the example to use.

        Can be left blank if the network has no security set.

endmenu

Kconfig ESP32

Kconfig ESP32

يجب أن يحوي الكود التعريفات بنفس النمط التالي:

#define EXAMPLE_WIFI_SSID CONFIG_WIFI_SSID
#define EXAMPLE_WIFI_PASS CONFIG_WIFI_PASSWORD

حيث يمكن لاسم الماكرو بحد ذاته أن يكون أي اسم، بينما القيمة يجب أن تكون من الشكل CONFIG_*** وذلك حسب الاسم المستخدم داخل الملف Kconfig.projbuild.

 

الكود

 

هذا الكود هو نسخة معدلّة ومبسّطة من المثال المرفق في ESP-IDF.

#include <string.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/event_groups.h"
#include "esp_system.h"
#include "esp_wifi.h"
#include "esp_event_loop.h"
#include "esp_log.h"
#include "nvs_flash.h"

#include "lwip/err.h"
#include "lwip/sockets.h"
#include "lwip/sys.h"
#include "lwip/netdb.h"
#include "lwip/dns.h"


#define EXAMPLE_WIFI_SSID CONFIG_WIFI_SSID
#define EXAMPLE_WIFI_PASS CONFIG_WIFI_PASSWORD

static EventGroupHandle_t wifi_event_group;
const int CONNECTED_BIT = BIT0;

#define WEB_SERVER "httpbin.org"
#define WEB_PORT 80
#define WEB_URL "http://httpbin.org"

static const char *TAG = "HTTP Request";

static const char *REQUEST = "GET " "/" " HTTP/1.0\r\n"
    "Host: "WEB_SERVER"\r\n"
    "User-Agent: esp-idf/1.0 esp32\r\n"
    "\r\n";

static esp_err_t event_handler(void *ctx, system_event_t *event)
{
    switch(event->event_id) {
    case SYSTEM_EVENT_STA_START:
        esp_wifi_connect();
        break;
    case SYSTEM_EVENT_STA_GOT_IP:
        xEventGroupSetBits(wifi_event_group, CONNECTED_BIT);
        break;
    case SYSTEM_EVENT_STA_DISCONNECTED:
        /* This is a workaround as ESP32 WiFi libs don't currently
           auto-reassociate. */
        esp_wifi_connect();
        xEventGroupClearBits(wifi_event_group, CONNECTED_BIT);
        break;
    default:
        break;
    }
    return ESP_OK;
}

static void initialise_wifi(void)
{
    tcpip_adapter_init();
    wifi_event_group = xEventGroupCreate();
    ESP_ERROR_CHECK( esp_event_loop_init(event_handler, NULL) );
    wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
    ESP_ERROR_CHECK( esp_wifi_init(&cfg) );
    ESP_ERROR_CHECK( esp_wifi_set_storage(WIFI_STORAGE_RAM) );
    wifi_config_t wifi_config = {
        .sta = {
            .ssid = EXAMPLE_WIFI_SSID,
            .password = EXAMPLE_WIFI_PASS,
        },
    };
    ESP_LOGI(TAG, "Setting WiFi configuration SSID %s...", wifi_config.sta.ssid);
    ESP_ERROR_CHECK( esp_wifi_set_mode(WIFI_MODE_STA) );
    ESP_ERROR_CHECK( esp_wifi_set_config(ESP_IF_WIFI_STA, &wifi_config) );
    ESP_ERROR_CHECK( esp_wifi_start() );
}

static void http_get_task(void *pvParameters)
{
    const struct addrinfo hints = {
        .ai_family = AF_INET,
        .ai_socktype = SOCK_STREAM,
    };
    struct addrinfo *res;
    struct in_addr *addr;
    int socket_desc, r;
    char recv_buf[64];

    while(1) {
        xEventGroupWaitBits(wifi_event_group, CONNECTED_BIT,
                            false, true, portMAX_DELAY);
        ESP_LOGI(TAG, "Connected to AP");

        socket_desc = socket(hints.ai_family, hints.ai_socktype, 0);
        
        if(socket_desc < 0) { ESP_LOGE(TAG, "... Failed to allocate socket."); freeaddrinfo(res); vTaskDelay(1000 / portTICK_PERIOD_MS); continue; } ESP_LOGI(TAG, "... allocated socket"); int err = getaddrinfo(WEB_SERVER, "80", &hints, &res); if(err != 0 || res == NULL) { ESP_LOGE(TAG, "DNS lookup failed err=%d res=%p", err, res); vTaskDelay(1000 / portTICK_PERIOD_MS); continue; } addr = &((struct sockaddr_in *)res->ai_addr)->sin_addr;
        ESP_LOGI(TAG, "DNS lookup succeeded. IP=%s", inet_ntoa(*addr));


        if(connect(socket_desc, res->ai_addr, res->ai_addrlen) != 0) {
            ESP_LOGE(TAG, "... socket connect failed errno=%d", errno);
            close(socket_desc);
            freeaddrinfo(res);
            vTaskDelay(4000 / portTICK_PERIOD_MS);
            continue;
        }

        ESP_LOGI(TAG, "... connected");
        freeaddrinfo(res);

        if (write(socket_desc, REQUEST, strlen(REQUEST)) < 0) {
            ESP_LOGE(TAG, "... socket send failed");
            close(socket_desc);
            vTaskDelay(4000 / portTICK_PERIOD_MS);
            continue;
        }
        ESP_LOGI(TAG, "... socket send success");


        do {
            bzero(recv_buf, sizeof(recv_buf));
            r = read(socket_desc, recv_buf, sizeof(recv_buf)-1);
            for(int i = 0; i < r; i++) { putchar(recv_buf[i]); } } while(r > 0);

        ESP_LOGI(TAG, "... done reading from socket. Last read return=%d errno=%d\r\n", r, errno);
        close(socket_desc);
        for(int countdown = 10; countdown >= 0; countdown--) {
            ESP_LOGI(TAG, "%d... ", countdown);
            vTaskDelay(1000 / portTICK_PERIOD_MS);
        }
        ESP_LOGI(TAG, "Starting again!");
    }
}

void app_main()
{
    ESP_ERROR_CHECK( nvs_flash_init() );
    initialise_wifi();
    xTaskCreate(&http_get_task, "http_get_task", 4096, NULL, 5, NULL);
}

 

التطبيق الثاني: ESP HTTP Client

 

إن التعامل مع الـHTTP ليس بسهولة دوماً حيث قد تتضمن عملية authentication (على مستوى اسم مستخدم/كلمة مرور أو HTTPS) ،أو إعادة توجيه redirection ، أو عمليات GET/POST/DELETE/ .. إلخ. لهذا السبب قدمت ESP-IDF مجموعة توابع للقيام بـHTTP/S requests بأنواعها ولكن كـclient فقط. هناك 3 خطوات أساسية لاستخدام هذه المكتبة:

1- تعريف متحول من  esp_http_client_config_t وتعبئته بالمعلومات المطلوبة متضمّنةً رابط الموقع. مثال:

  • عملية GET/POST:

esp_http_client_config_t config = {
        .url = "http://httpbin.org/get",
        .event_handler = _http_event_handler,
    };

  • Authentication لاسم مستخدم وكلمة السر:

esp_http_client_config_t config = {
        .url = "http://user:passwd@httpbin.org/basic-auth/user/passwd",
        .event_handler = _http_event_handler,
        .auth_type = HTTP_AUTH_TYPE_BASIC,
    };

2- استدعاء esp_http_client_init التابع:
esp_http_client_handle_t client = esp_http_client_init(&config);

3- استدعاء esp_http_client_perform() حيث ينفذ هذا التابع كل العمليات المطلوبة من فتح للاتصال وارسال/استقبال المعطيات وتسكير الاتصال عند اللزوم.

يتم استدعاء توابع أخرى بناءً على العمليّة المطلوبة، مثلاً: من أجل عملية POST نستدعي

esp_http_client_set_post_field(client, post_data, strlen(post_data));

الكود كاملاً:
#include <string.h>
#include <stdlib.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_log.h"
#include "esp_system.h"
#include "nvs_flash.h"
#include "esp_wifi.h"
#include "esp_event_loop.h"
#include "freertos/event_groups.h"

#include "esp_http_client.h"

#define MAX_HTTP_RECV_BUFFER 512
static const char *TAG = "HTTP_CLIENT";

static EventGroupHandle_t wifi_event_group;


const int CONNECTED_BIT = BIT0;


static esp_err_t event_handler(void *ctx, system_event_t *event)
{
    switch (event->event_id) {
        case SYSTEM_EVENT_STA_START:
            esp_wifi_connect();
            break;
        case SYSTEM_EVENT_STA_GOT_IP:
            xEventGroupSetBits(wifi_event_group, CONNECTED_BIT);
            break;
        case SYSTEM_EVENT_STA_DISCONNECTED:
            /* This is a workaround as ESP32 WiFi libs don't currently
               auto-reassociate. */
            esp_wifi_connect();
            xEventGroupClearBits(wifi_event_group, CONNECTED_BIT);
            break;
        default:
            break;
    }
    return ESP_OK;
}


esp_err_t _http_event_handler(esp_http_client_event_t *evt)
{
    switch(evt->event_id) {
        case HTTP_EVENT_ERROR:
            ESP_LOGD(TAG, "HTTP_EVENT_ERROR");
            break;
        case HTTP_EVENT_ON_CONNECTED:
            ESP_LOGD(TAG, "HTTP_EVENT_ON_CONNECTED");
            break;
        case HTTP_EVENT_HEADER_SENT:
            ESP_LOGD(TAG, "HTTP_EVENT_HEADER_SENT");
            break;
        case HTTP_EVENT_ON_HEADER:
            ESP_LOGD(TAG, "HTTP_EVENT_ON_HEADER, key=%s, value=%s", evt->header_key, evt->header_value);
            break;
        case HTTP_EVENT_ON_DATA:
            ESP_LOGD(TAG, "HTTP_EVENT_ON_DATA, len=%d", evt->data_len);
            if (!esp_http_client_is_chunked_response(evt->client)) {
                // Write out data
                // printf("%.*s", evt->data_len, (char*)evt->data);
            }

            break;
        case HTTP_EVENT_ON_FINISH:
            ESP_LOGD(TAG, "HTTP_EVENT_ON_FINISH");
            break;
        case HTTP_EVENT_DISCONNECTED:
            ESP_LOGD(TAG, "HTTP_EVENT_DISCONNECTED");
            break;
    }
    return ESP_OK;
}

static void http_rest()
{
    esp_http_client_config_t config = {
        .url = "http://random-quote-generator.herokuapp.com/api/quotes/random",
        .event_handler = _http_event_handler,
    };
    esp_http_client_handle_t client = esp_http_client_init(&config);

    // GET
    esp_err_t err = esp_http_client_perform(client);
    if (err == ESP_OK) {
        int content_length =  esp_http_client_get_content_length(client);
        ESP_LOGI(TAG, "HTTP GET Status = %d, content_length = %d",
                esp_http_client_get_status_code(client),
                content_length);
        char *buffer = malloc(MAX_HTTP_RECV_BUFFER);
        
        int read_len = esp_http_client_read(client, buffer, content_length);
        buffer[read_len] = 0;
        ESP_LOGI(TAG, "HTTP Stream reader Status = %d, content_length = %d, Buffer=%.*s",
                esp_http_client_get_status_code(client),
                read_len,
                read_len,
                buffer);
        free(buffer);
    } else {
        ESP_LOGE(TAG, "HTTP GET request failed: %s", esp_err_to_name(err));
    }


    esp_http_client_cleanup(client);
}

static void http_test_task(void *pvParameters)
{
        xEventGroupWaitBits(wifi_event_group, CONNECTED_BIT, false, true, portMAX_DELAY);

    ESP_LOGI(TAG, "Connected to AP, begin http example");
    http_rest();
    ESP_LOGI(TAG, "Finish http example");
    vTaskDelete(NULL);
}

void app_wifi_initialise(void)
{
    tcpip_adapter_init();
    wifi_event_group = xEventGroupCreate();
    ESP_ERROR_CHECK(esp_event_loop_init(event_handler, NULL));
    wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
    ESP_ERROR_CHECK(esp_wifi_init(&cfg));
    ESP_ERROR_CHECK(esp_wifi_set_storage(WIFI_STORAGE_RAM));
    wifi_config_t wifi_config = {
        .sta = {
            .ssid = CONFIG_WIFI_SSID,
            .password = CONFIG_WIFI_PASSWORD,
        },
    };
    ESP_LOGI(TAG, "Setting WiFi configuration SSID %s...", wifi_config.sta.ssid);
    ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA));
    ESP_ERROR_CHECK(esp_wifi_set_config(ESP_IF_WIFI_STA, &wifi_config));
    ESP_ERROR_CHECK(esp_wifi_start());

}

void app_main()
{
    esp_err_t ret = nvs_flash_init();
    if (ret == ESP_ERR_NVS_NO_FREE_PAGES) {
      ESP_ERROR_CHECK(nvs_flash_erase());
      ret = nvs_flash_init();
    }
    ESP_ERROR_CHECK(ret);
    app_wifi_initialise();

    xTaskCreate(&http_test_task, "http_test_task", 8192, NULL, 5, NULL);
}

وهذا خرج البرنامج من أجل استدعاء GET من موقع خاص لإعطاء مقولات عشوائية في كل استدعاء للرابط API (http://random-quote-generator.herokuapp.com/api/quotes/random).

 

ESP32 Random qoute API

وهذا تجريب آخر لموقع خاص لرسم الكلمات باستخدام فن الآسكي (http://artii.herokuapp.com/make?text=ASCII+art).

ESP32 ASCII ART API

يرجى الاطلاع على المثال الكامل لمشاهدة حالات استعمال متعددة من مستودع  ESP-IDF.

 

التطبيق الثالث: TCP Server

 

سنقوم في التطبيق الثالث والأخير بإنشاء سيرفر TCP يستمع لطلبات الاتصال ويقوم بالرد عليها بعبارة ترحيبيّة. في هذا التطبيق سنطبع IP الأجهزة المتصلة حيث ستكون الـESP32 في نمط SoftAP وسنقوم باستخدام المنفذ 8888.

قمنا مسبقاً بشرح كيفية تهيئة الـESP32 بنمط الـSoftAP ومن أجل الاتصال كـtelnet بالشبكة يجب أن نعرف عنوان IP الشبكة عبر التابع tcpip_adapter_get_ip_info.

تذكير: تعمل الـESP32 كسيرفر وبالتالي يجب استدعاء التابعين bind و listen بعد إنشاء الـSocket.

أخيراً، يمكننا استخدام الكود التالي لطباعة عنوان المستخدمين المتصلين بالشبكة:

wifi_sta_list_t wifi_sta_lis;
tcpip_adapter_sta_list_t tcpip_sta_list;
esp_wifi_ap_get_sta_list(&wifi_sta_lis);
tcpip_adapter_get_sta_list(&wifi_sta_lis, &tcpip_sta_list);

for (int i=0; i<wifi_sta_lis.num; i++){
                  printf("StaInfo:"MACSTR","IPSTR"",MAC2STR(wifi_sta_lis.sta[i].mac),IP2STR(&tcpip_sta_list.sta[i].ip));
}

الكود كاملاً:
#include <string.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/event_groups.h"
#include "esp_system.h"
#include "esp_wifi.h"
#include "esp_event_loop.h"
#include "esp_log.h"
#include "nvs_flash.h"
#include "tcpip_adapter.h"
#include <sys/socket.h>
#include "lwip/err.h"
#include "lwip/sys.h"
#include <netdb.h>


#define EXAMPLE_ESP_WIFI_MODE_AP   CONFIG_ESP_WIFI_MODE_AP //TRUE:AP FALSE:STA
#define EXAMPLE_ESP_WIFI_SSID      CONFIG_ESP_WIFI_SSID
#define EXAMPLE_ESP_WIFI_PASS      CONFIG_ESP_WIFI_PASSWORD
#define EXAMPLE_MAX_STA_CONN       CONFIG_MAX_STA_CONN

static EventGroupHandle_t wifi_event_group;

const int CONNECTED_BIT = BIT0;


static const char *TAG = "simple wifi";

static esp_err_t event_handler(void *ctx, system_event_t *event)
{
        wifi_sta_list_t wifi_sta_lis;
    tcpip_adapter_sta_list_t tcpip_sta_list;
    tcpip_adapter_ip_info_t ip;
    switch(event->event_id) {

    case SYSTEM_EVENT_AP_STACONNECTED:

                ESP_LOGI(TAG,"station:"MACSTR"join",MAC2STR(event->event_info.sta_connected.mac));
                
                 esp_wifi_ap_get_sta_list(&wifi_sta_lis);
                  tcpip_adapter_get_sta_list(&wifi_sta_lis, &tcpip_sta_list);

                  for (int i=0; i<wifi_sta_lis.num; i++){ printf("StaInfo:"MACSTR","IPSTR"",MAC2STR(wifi_sta_lis.sta[i].mac),IP2STR(&tcpip_sta_list.sta[i].ip)); tcpip_adapter_get_ip_info(TCPIP_ADAPTER_IF_AP, &ip); ESP_LOGI(TAG, "IP=%s", inet_ntoa(ip.ip.addr)); } xEventGroupSetBits(wifi_event_group, CONNECTED_BIT); break; case SYSTEM_EVENT_AP_STADISCONNECTED: ESP_LOGI(TAG, "station:"MACSTR"leave",MAC2STR(event->event_info.sta_disconnected.mac));
        xEventGroupClearBits(wifi_event_group, CONNECTED_BIT);
        break;
    default:
        break;
    }
    return ESP_OK;
}

static void http_get_task(void *pvParameters)
{
    const struct addrinfo hints = {
        .ai_family = AF_INET,
        .ai_socktype = SOCK_STREAM,
    };
    struct addrinfo *addr;
    int socket_desc,new_socket,c;
    struct sockaddr_in  server,client;

            xEventGroupWaitBits(wifi_event_group, CONNECTED_BIT,
                            false, true, portMAX_DELAY);
        ESP_LOGI(TAG, "SoftAP Started");

        socket_desc = socket(hints.ai_family, hints.ai_socktype, 0);
        
        if(socket_desc < 0) {
            ESP_LOGE(TAG, "... Failed to allocate socket.");
        }
        ESP_LOGI(TAG, "... allocated socket");
        
        server.sin_family = AF_INET;
        server.sin_addr.s_addr = INADDR_ANY;
        server.sin_port = htons( 8888 );
        if(bind(socket_desc,(struct sockaddr *)&server , sizeof(server)) < 0)
        {
            ESP_LOGI(TAG,"bind failed");
        }
        ESP_LOGI(TAG,"bind done");
        
        //Listen
        listen(socket_desc , 3);
        
    while(1) {

    puts("Waiting for incoming connections...");
    c = sizeof(struct sockaddr_in);
    new_socket = accept(socket_desc, (struct sockaddr *)&client, (socklen_t*)&c);
    if (new_socket<0)
    {
        perror("accept failed");
        return ;
    }
     else
     {
    ESP_LOGI(TAG,"Connection accepted");
     
    char *message = "Hello Client , I have received your connection. But I have to go now, bye\n";
    write(new_socket , message , strlen(message));


    close(socket_desc);

    }
      vTaskDelay(100 / portTICK_PERIOD_MS);
    }
}

void wifi_init_softap()
{
    wifi_event_group = xEventGroupCreate();

    tcpip_adapter_init();
    ESP_ERROR_CHECK(esp_event_loop_init(event_handler, NULL));

    wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
    ESP_ERROR_CHECK(esp_wifi_init(&cfg));
    wifi_config_t wifi_config = {
        .ap = {
            .ssid = EXAMPLE_ESP_WIFI_SSID,
            .ssid_len = strlen(EXAMPLE_ESP_WIFI_SSID),
            .password = EXAMPLE_ESP_WIFI_PASS,
            .max_connection = EXAMPLE_MAX_STA_CONN,
            .authmode = WIFI_AUTH_WPA_WPA2_PSK
        },
    };
    if (strlen(EXAMPLE_ESP_WIFI_PASS) == 0) {
        wifi_config.ap.authmode = WIFI_AUTH_OPEN;
    }

    ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_AP));
    ESP_ERROR_CHECK(esp_wifi_set_config(ESP_IF_WIFI_AP, &wifi_config));
    ESP_ERROR_CHECK(esp_wifi_start());

    ESP_LOGI(TAG, "wifi_init_softap finished.SSID:%s password:%s",
             EXAMPLE_ESP_WIFI_SSID, EXAMPLE_ESP_WIFI_PASS);
}


void app_main()
{
    nvs_flash_init();  
    ESP_LOGI(TAG, "ESP_WIFI_MODE_AP");
    xTaskCreate(&http_get_task, "http_get_task", 4096, NULL, 5, NULL);
    wifi_init_softap();
}

ESP32 TCP server response

أجزاء سلسلة كل ما يتعلق بـESP32

متعلقات

Yahya Tawil

يحيى هو مدير التحرير في عتاديات ويؤمن بأهمية المحتوى المكتوب المجاني والنوعي والعملي. خبرته في مجال النظم المضمّنة تتركز في كتابة البرامج المضمنة وتصميم الدارات المطبوعة والنظرية وإنشاء المحتوى.

اترك تعليقاً

لن يتم نشر عنوان بريدك الإلكتروني. الحقول الإلزامية مشار إليها بـ *

هذا الموقع يستخدم Akismet للحدّ من التعليقات المزعجة والغير مرغوبة. تعرّف على كيفية معالجة بيانات تعليقك.

زر الذهاب إلى الأعلى