[SOLVED] HTTP Server and Websockets together

sazanof
Posts: 22
Joined: Wed Sep 13, 2023 10:22 am

[SOLVED] HTTP Server and Websockets together

Postby sazanof » Tue Dec 19, 2023 6:23 am

Hello everyone!
I use http server and websockets in my firmware. The web server is a backend for processing get post requests. One of handlers I have is:

Code: Select all

/* Websockets URI handler */
httpd_uri_t websocket_server_uri = {
    .uri = "/ws",
    .method = HTTP_GET,
    .handler = websocket_server_handler,
    .user_ctx = rest_context,
    .is_websocket = true,
    .handle_ws_control_frames = true};
httpd_register_uri_handler(server, &websocket_server_uri);
//...............
static esp_err_t websocket_server_handler(httpd_req_t *req)
{
    esp_err_t ret = ws_handler_for_uri(req);
    return ret;
}
I took the code from here as the basis for the implementation of websockets:
https://github.com/espressif/esp-idf/tr ... wss_server

4 ntc thermistors, 6 ds18b20 sensors, 2 motion sensors and 3 relays are connected to my ESP 32.

Several tasks are created with different frequency for thermistors, ds18b20, motion sensors (every 10 seconds).
Example of task:

Code: Select all

void onewire_task(void *arg)
{
    char string_address[16] = {0};
    while (true)
    {
        for (int i = 0; i < MAX_SENSORS; i++)
        {
            if (addresses[i] != 0)
            {
                onewire_uint64_t_to_addr_str(addresses[i], string_address);
                uint8_t family_id = (uint8_t)addresses[i];
                switch (family_id)
                {
                case DS18X20_FAMILY_DS18B20:  // CASE ONE-WIRE TEMP SENSOR
                case DS18X20_FAMILY_DS18S20:  // CASE ONE-WIRE TEMP SENSOR
                case DS18X20_FAMILY_DS1822:   // CASE ONE-WIRE TEMP SENSOR
                case DS18X20_FAMILY_MAX31850: // CASE ONE-WIRE TEMP SENSOR
                    float temp;
                    esp_err_t res = ds18x20_read_temp(addresses[i], &temp);

                    if (res == ESP_OK)
                    {
                        queue_payload_ds18x20_t payload;
                        payload.family_id = (uint8_t)addresses[i];
                        payload.temp = temp;
                        payload.address = (uint64_t)addresses[i];

                        queue_message_t message;
                        message.type = QE_DS18X20;
                        message.payload = (void *)&payload;
                        queue_send(message);
                    }
                    break;

                default:
                    break;
                }
            }
        }
        ESP_LOGW(ONE_WIRE_TAG, "[onewire_task] Free memory: %ld bytes", esp_get_free_heap_size());
        vTaskDelay(ONEWIRE_TASK_TIMEOUT / portTICK_PERIOD_MS);
    }

    vTaskDelete(NULL);
}

////---------- somewhere-----//////
xTaskCreatePinnedToCore(onewire_task, "onewire_task", 4096, NULL, ONE_WIRE_TASK_PRIORITY, &onewire_task_handle, 1);
queue_send - the method that receives data and sends notifications (now these are websockets, but later I will add mqtt support)

Code: Select all

void queue_send(queue_message_t message)
{
    if (queue != NULL)
    {
        BaseType_t ret = xQueueSend(queue, &message, (1000 / portTICK_PERIOD_MS));
    }
}
How I receive from queue:

Code: Select all

void queue_recieve_task(void *arg)
{
    QueueHandle_t queue = (QueueHandle_t)arg;
    queue_message_t message;
    bool has_type = false;

    while (true)
    {
        if (xQueueReceive(queue, &message, (1000 / portTICK_PERIOD_MS)))
        {
            cJSON *json = cJSON_CreateObject();
            cJSON_AddNumberToObject(json, "type", message.type);
            cJSON *json_message = cJSON_CreateObject();

            switch (message.type)
            {
                case QE_DS18X20:
                has_type = true;
                queue_payload_ds18x20_t *ds18x20_payload = (queue_payload_ds18x20_t *)message.payload;
                char str[16] = {0};
                uint64_t_to_string((uint64_t)ds18x20_payload->address, str);

                cJSON_AddNumberToObject(json_message, "family", (uint8_t)ds18x20_payload->address);
                cJSON_AddNumberToObject(json_message, "temp", ds18x20_payload->temp);
                cJSON_AddStringToObject(json_message, "serial", str);

                ESP_LOGI(TAG_QUEUE, "[QE_DS18X20] SN: %s, temp %.2f°C, family_id %d", str, ds18x20_payload->temp, (uint8_t)ds18x20_payload->address);
                break;
                /// some code
            }
            if (has_type)
            {
                cJSON_AddItemToObject(json, "message", json_message);

                char *json_print = cJSON_PrintUnformatted(json);
                ws_send_broadcast_message((void *)json_print);
                free(json_print);
                has_type = false;
            }

            cJSON_Delete(json);
        }
    }
ws_send_broadcast_message method:

Code: Select all

esp_err_t ws_send_broadcast_message(void *data)
{
    if (p_server == NULL)
    {
        return ESP_FAIL;
    }
    httpd_ws_frame_t ws_pkt;
    memset(&ws_pkt, 0, sizeof(httpd_ws_frame_t));
    ws_pkt.payload = (uint8_t *)data;
    ws_pkt.len = strlen(data);
    ws_pkt.type = HTTPD_WS_TYPE_TEXT;

    for (int i = 0; i < MAX_CLIENTS; i++)
    {
        if (client_fds[i] > 0)
        {
            httpd_ws_send_frame_async(p_server, client_fds[i], &ws_pkt);
        }
    }

    return ESP_OK;
}
When executed, this method sends a message with sensor readings to all active websocket clients. Frontend (vue.js) takes these readings and processes them properly.

My problem is that if ws_send_broadcast_message is running, I cannot correctly execute any POST GET request (open the main page, or switch relays). It feels like the response from the server is "shuffled" and the page is partially returned. If the ws_send_broadcast_message method is not called, then everything works correctly.

When I make a GET request and a websockets message arrives at the same time, it turns out that this is the trouble. It's clear here in the screenshot
Снимок экрана от 2023-12-19 09-30-15.png
Снимок экрана от 2023-12-19 09-30-15.png (33.62 KiB) Viewed 6997 times
Can you tell me how this can be fixed?

How can I send and serve a websockets message at the same time with POST GET requests?
Last edited by sazanof on Tue Dec 19, 2023 6:34 pm, edited 1 time in total.

sazanof
Posts: 22
Joined: Wed Sep 13, 2023 10:22 am

Re: HTTP Server and Websockets together

Postby sazanof » Tue Dec 19, 2023 6:34 pm

[SOLUTION]
I think I've found the problem.
I have registered a websocket client every time each request is opened in a function defined in the
ws_open_fd
configuration

And so a message was sent to each client and mixed the response of the websocket with the response of the GET POST

Code: Select all

httpd_config_t config = HTTPD_DEFAULT_CONFIG();
    config.uri_match_fn = httpd_uri_match_wildcard;
    config.max_uri_handlers = 100;
    config.max_open_sockets = 5;
    config.global_user_ctx = keep_alive;
    // config.open_fn = ws_open_fd;
    config.close_fn = ws_close_fd;
So, I moved the addition of the socket client to handler /ws

Code: Select all

static esp_err_t websocket_server_handler(httpd_req_t *req)
{
    esp_err_t ret = ws_handler_for_uri(req, server); // <--- here // ws_open_fd(server, httpd_req_to_sockfd(req));
    return ret;
}
It's worked!

Who is online

Users browsing this forum: No registered users and 123 guests