From f3a0586663037da8bef9aba7e56a23a26dd36c05 Mon Sep 17 00:00:00 2001 From: Tuan Date: Thu, 20 Jun 2019 15:37:40 +0800 Subject: [PATCH 01/65] esp_websocket_client: Add websocket client component Closes https://github.com/espressif/esp-idf/issues/2829 * Original commit: espressif/esp-idf@2a2d932cfe2404057c71bc91d9d9416200e67a03 --- .../esp_websocket_client/CMakeLists.txt | 4 + components/esp_websocket_client/component.mk | 0 .../esp_websocket_client.c | 606 ++++++++++++++++++ .../include/esp_websocket_client.h | 195 ++++++ .../protocols/esp_websocket_client.rst | 70 ++ examples/protocols/websocket/CMakeLists.txt | 10 + examples/protocols/websocket/Makefile | 10 + examples/protocols/websocket/README.md | 1 + examples/protocols/websocket/example_test.py | 41 ++ .../protocols/websocket/main/CMakeLists.txt | 4 + .../websocket/main/Kconfig.projbuild | 9 + .../protocols/websocket/main/component.mk | 0 .../websocket/main/websocket_example.c | 103 +++ 13 files changed, 1053 insertions(+) create mode 100644 components/esp_websocket_client/CMakeLists.txt create mode 100644 components/esp_websocket_client/component.mk create mode 100644 components/esp_websocket_client/esp_websocket_client.c create mode 100644 components/esp_websocket_client/include/esp_websocket_client.h create mode 100644 docs/en/api-reference/protocols/esp_websocket_client.rst create mode 100644 examples/protocols/websocket/CMakeLists.txt create mode 100644 examples/protocols/websocket/Makefile create mode 100644 examples/protocols/websocket/README.md create mode 100644 examples/protocols/websocket/example_test.py create mode 100644 examples/protocols/websocket/main/CMakeLists.txt create mode 100644 examples/protocols/websocket/main/Kconfig.projbuild create mode 100644 examples/protocols/websocket/main/component.mk create mode 100644 examples/protocols/websocket/main/websocket_example.c diff --git a/components/esp_websocket_client/CMakeLists.txt b/components/esp_websocket_client/CMakeLists.txt new file mode 100644 index 000000000..723199ce0 --- /dev/null +++ b/components/esp_websocket_client/CMakeLists.txt @@ -0,0 +1,4 @@ +set(COMPONENT_SRCS "esp_websocket_client.c") +set(COMPONENT_ADD_INCLUDEDIRS "include") +set(COMPONENT_REQUIRES lwip esp-tls tcp_transport nghttp) +register_component() diff --git a/components/esp_websocket_client/component.mk b/components/esp_websocket_client/component.mk new file mode 100644 index 000000000..e69de29bb diff --git a/components/esp_websocket_client/esp_websocket_client.c b/components/esp_websocket_client/esp_websocket_client.c new file mode 100644 index 000000000..9d4b1f053 --- /dev/null +++ b/components/esp_websocket_client/esp_websocket_client.c @@ -0,0 +1,606 @@ +// Copyright 2015-2018 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at + +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include + +#include "esp_websocket_client.h" +#include "esp_transport.h" +#include "esp_transport_tcp.h" +#include "esp_transport_ssl.h" +#include "esp_transport_ws.h" +#include "esp_transport_utils.h" +/* using uri parser */ +#include "http_parser.h" +#include "freertos/task.h" +#include "freertos/semphr.h" +#include "freertos/queue.h" +#include "freertos/event_groups.h" +#include "esp_log.h" +#include "esp_timer.h" + +static const char *TAG = "WEBSOCKET_CLIENT"; + +#define WEBSOCKET_TCP_DEFAULT_PORT (80) +#define WEBSOCKET_SSL_DEFAULT_PORT (443) +#define WEBSOCKET_BUFFER_SIZE_BYTE (1024) +#define WEBSOCKET_RECONNECT_TIMEOUT_MS (10*1000) +#define WEBSOCKET_TASK_PRIORITY (5) +#define WEBSOCKET_TASK_STACK (4*1024) +#define WEBSOCKET_NETWORK_TIMEOUT_MS (10*1000) +#define WEBSOCKET_PING_TIMEOUT_MS (10*1000) +#define WEBSOCKET_EVENT_QUEUE_SIZE (1) +#define WEBSOCKET_SEND_EVENT_TIMEOUT_MS (1000/portTICK_RATE_MS) + +const static int STOPPED_BIT = BIT0; + +ESP_EVENT_DEFINE_BASE(WEBSOCKET_EVENTS); + +typedef struct { + int task_stack; + int task_prio; + char *uri; + char *host; + char *path; + char *scheme; + char *username; + char *password; + int port; + bool auto_reconnect; + void *user_context; + int network_timeout_ms; +} websocket_config_storage_t; + +typedef enum { + WEBSOCKET_STATE_ERROR = -1, + WEBSOCKET_STATE_UNKNOW = 0, + WEBSOCKET_STATE_INIT, + WEBSOCKET_STATE_CONNECTED, + WEBSOCKET_STATE_WAIT_TIMEOUT, +} websocket_client_state_t; + +struct esp_websocket_client { + esp_event_loop_handle_t event_handle; + esp_transport_list_handle_t transport_list; + esp_transport_handle_t transport; + websocket_config_storage_t *config; + websocket_client_state_t state; + uint64_t keepalive_tick_ms; + uint64_t reconnect_tick_ms; + uint64_t ping_tick_ms; + int wait_timeout_ms; + int auto_reconnect; + bool run; + EventGroupHandle_t status_bits; + xSemaphoreHandle lock; + char *rx_buffer; + char *tx_buffer; + int buffer_size; +}; + +static uint64_t _tick_get_ms() +{ + return esp_timer_get_time()/1000; +} + +static esp_err_t esp_websocket_client_dispatch_event(esp_websocket_client_handle_t client, + esp_websocket_event_id_t event, + const char *data, + int data_len) +{ + esp_err_t err; + esp_websocket_event_data_t event_data; + event_data.data_ptr = data; + event_data.data_len = data_len; + + if ((err = esp_event_post_to(client->event_handle, + WEBSOCKET_EVENTS, event, + &event_data, + sizeof(esp_websocket_event_data_t), + WEBSOCKET_SEND_EVENT_TIMEOUT_MS)) != ESP_OK) { + return err; + } + return esp_event_loop_run(client->event_handle, WEBSOCKET_SEND_EVENT_TIMEOUT_MS); +} + +static esp_err_t esp_websocket_client_abort_connection(esp_websocket_client_handle_t client) +{ + esp_transport_close(client->transport); + client->wait_timeout_ms = WEBSOCKET_RECONNECT_TIMEOUT_MS; + client->reconnect_tick_ms = _tick_get_ms(); + client->state = WEBSOCKET_STATE_WAIT_TIMEOUT; + ESP_LOGI(TAG, "Reconnect after %d ms", client->wait_timeout_ms); + esp_websocket_client_dispatch_event(client, WEBSOCKET_EVENT_DISCONNECTED, NULL, 0); + return ESP_OK; +} + +static esp_err_t esp_websocket_client_set_config(esp_websocket_client_handle_t client, const esp_websocket_client_config_t *config) +{ + websocket_config_storage_t *cfg = client->config; + cfg->task_prio = config->task_prio; + if (cfg->task_prio <= 0) { + cfg->task_prio = WEBSOCKET_TASK_PRIORITY; + } + + cfg->task_stack = config->task_stack; + if (cfg->task_stack == 0) { + cfg->task_stack = WEBSOCKET_TASK_STACK; + } + + if (config->host) { + cfg->host = strdup(config->host); + ESP_TRANSPORT_MEM_CHECK(TAG, cfg->host, return ESP_ERR_NO_MEM); + } + + if (config->port) { + cfg->port = config->port; + } + + if (config->username) { + free(cfg->username); + cfg->username = strdup(config->username); + ESP_TRANSPORT_MEM_CHECK(TAG, cfg->username, { + free(cfg->host); + return ESP_ERR_NO_MEM; + }); + } + + if (config->password) { + free(cfg->password); + cfg->password = strdup(config->password); + ESP_TRANSPORT_MEM_CHECK(TAG, cfg->password, { + free(cfg->host); + free(cfg->username); + return ESP_ERR_NO_MEM; + }); + } + + if (config->uri) { + free(cfg->uri); + cfg->uri = strdup(config->uri); + ESP_TRANSPORT_MEM_CHECK(TAG, cfg->uri, { + free(cfg->host); + free(cfg->username); + free(cfg->password); + return ESP_ERR_NO_MEM; + }); + } + if (config->path) { + free(cfg->path); + cfg->path = strdup(config->path); + ESP_TRANSPORT_MEM_CHECK(TAG, cfg->path, { + free(cfg->uri); + free(cfg->host); + free(cfg->username); + free(cfg->password); + return ESP_ERR_NO_MEM; + }); + } + + cfg->network_timeout_ms = WEBSOCKET_NETWORK_TIMEOUT_MS; + cfg->user_context = config->user_context; + cfg->auto_reconnect = true; + if (config->disable_auto_reconnect) { + cfg->auto_reconnect = false; + } + + + return ESP_OK; +} + +static esp_err_t esp_websocket_client_destroy_config(esp_websocket_client_handle_t client) +{ + if (client == NULL) { + return ESP_ERR_INVALID_ARG; + } + websocket_config_storage_t *cfg = client->config; + if (client->config == NULL) { + return ESP_ERR_INVALID_ARG; + } + free(cfg->host); + free(cfg->uri); + free(cfg->path); + free(cfg->scheme); + free(cfg->username); + free(cfg->password); + memset(cfg, 0, sizeof(websocket_config_storage_t)); + free(client->config); + client->config = NULL; + return ESP_OK; +} + +esp_websocket_client_handle_t esp_websocket_client_init(const esp_websocket_client_config_t *config) +{ + esp_websocket_client_handle_t client = calloc(1, sizeof(struct esp_websocket_client)); + ESP_TRANSPORT_MEM_CHECK(TAG, client, return NULL); + + esp_event_loop_args_t event_args = { + .queue_size = WEBSOCKET_EVENT_QUEUE_SIZE, + .task_name = NULL // no task will be created + }; + + if (esp_event_loop_create(&event_args, &client->event_handle) != ESP_OK) { + ESP_LOGE(TAG, "Error create event handler for websocket client"); + free(client); + return NULL; + } + + client->lock = xSemaphoreCreateMutex(); + ESP_TRANSPORT_MEM_CHECK(TAG, client->lock, goto _websocket_init_fail); + + client->transport_list = esp_transport_list_init(); + ESP_TRANSPORT_MEM_CHECK(TAG, client->transport_list, goto _websocket_init_fail); + + esp_transport_handle_t tcp = esp_transport_tcp_init(); + ESP_TRANSPORT_MEM_CHECK(TAG, tcp, goto _websocket_init_fail); + + esp_transport_set_default_port(tcp, WEBSOCKET_TCP_DEFAULT_PORT); + esp_transport_list_add(client->transport_list, tcp, "_tcp"); // need to save to transport list, for cleanup + + + esp_transport_handle_t ws = esp_transport_ws_init(tcp); + ESP_TRANSPORT_MEM_CHECK(TAG, ws, goto _websocket_init_fail); + + esp_transport_set_default_port(ws, WEBSOCKET_TCP_DEFAULT_PORT); + esp_transport_list_add(client->transport_list, ws, "ws"); + if (config->transport == WEBSOCKET_TRANSPORT_OVER_TCP) { + asprintf(&client->config->scheme, "ws"); + ESP_TRANSPORT_MEM_CHECK(TAG, client->config->scheme, goto _websocket_init_fail); + } + + esp_transport_handle_t ssl = esp_transport_ssl_init(); + ESP_TRANSPORT_MEM_CHECK(TAG, ssl, goto _websocket_init_fail); + + esp_transport_set_default_port(ssl, WEBSOCKET_SSL_DEFAULT_PORT); + if (config->cert_pem) { + esp_transport_ssl_set_cert_data(ssl, config->cert_pem, strlen(config->cert_pem)); + } + esp_transport_list_add(client->transport_list, ssl, "_ssl"); // need to save to transport list, for cleanup + + esp_transport_handle_t wss = esp_transport_ws_init(ssl); + ESP_TRANSPORT_MEM_CHECK(TAG, wss, goto _websocket_init_fail); + + esp_transport_set_default_port(wss, WEBSOCKET_SSL_DEFAULT_PORT); + + esp_transport_list_add(client->transport_list, wss, "wss"); + if (config->transport == WEBSOCKET_TRANSPORT_OVER_TCP) { + asprintf(&client->config->scheme, "wss"); + ESP_TRANSPORT_MEM_CHECK(TAG, client->config->scheme, goto _websocket_init_fail); + } + + client->config = calloc(1, sizeof(websocket_config_storage_t)); + ESP_TRANSPORT_MEM_CHECK(TAG, client->config, goto _websocket_init_fail); + + if (config->uri) { + if (esp_websocket_client_set_uri(client, config->uri) != ESP_OK) { + ESP_LOGE(TAG, "Invalid uri"); + goto _websocket_init_fail; + } + } + + if (esp_websocket_client_set_config(client, config) != ESP_OK) { + ESP_LOGE(TAG, "Failed to set the configuration"); + goto _websocket_init_fail; + } + + if (client->config->scheme == NULL) { + asprintf(&client->config->scheme, "ws"); + ESP_TRANSPORT_MEM_CHECK(TAG, client->config->scheme, goto _websocket_init_fail); + } + + client->keepalive_tick_ms = _tick_get_ms(); + client->reconnect_tick_ms = _tick_get_ms(); + client->ping_tick_ms = _tick_get_ms(); + + int buffer_size = config->buffer_size; + if (buffer_size <= 0) { + buffer_size = WEBSOCKET_BUFFER_SIZE_BYTE; + } + client->rx_buffer = malloc(buffer_size); + ESP_TRANSPORT_MEM_CHECK(TAG, client->rx_buffer, { + goto _websocket_init_fail; + }); + client->tx_buffer = malloc(buffer_size); + ESP_TRANSPORT_MEM_CHECK(TAG, client->tx_buffer, { + goto _websocket_init_fail; + }); + client->status_bits = xEventGroupCreate(); + ESP_TRANSPORT_MEM_CHECK(TAG, client->status_bits, { + goto _websocket_init_fail; + }); + + client->buffer_size = buffer_size; + return client; + +_websocket_init_fail: + esp_websocket_client_destroy(client); + return NULL; +} + +esp_err_t esp_websocket_client_destroy(esp_websocket_client_handle_t client) +{ + if (client == NULL) { + return ESP_ERR_INVALID_ARG; + } + if (client->run) { + esp_websocket_client_stop(client); + } + if (client->event_handle) { + esp_event_loop_delete(client->event_handle); + } + esp_websocket_client_destroy_config(client); + esp_transport_list_destroy(client->transport_list); + vQueueDelete(client->lock); + free(client->tx_buffer); + free(client->rx_buffer); + if (client->status_bits) { + vEventGroupDelete(client->status_bits); + } + free(client); + client = NULL; + return ESP_OK; +} + +esp_err_t esp_websocket_client_set_uri(esp_websocket_client_handle_t client, const char *uri) +{ + if (client == NULL || uri == NULL) { + return ESP_ERR_INVALID_ARG; + } + struct http_parser_url puri; + http_parser_url_init(&puri); + int parser_status = http_parser_parse_url(uri, strlen(uri), 0, &puri); + if (parser_status != 0) { + ESP_LOGE(TAG, "Error parse uri = %s", uri); + return ESP_FAIL; + } + if (puri.field_data[UF_SCHEMA].len) { + free(client->config->scheme); + asprintf(&client->config->scheme, "%.*s", puri.field_data[UF_SCHEMA].len, uri + puri.field_data[UF_SCHEMA].off); + ESP_TRANSPORT_MEM_CHECK(TAG, client->config->scheme, return ESP_ERR_NO_MEM); + } + + if (puri.field_data[UF_HOST].len) { + free(client->config->host); + asprintf(&client->config->host, "%.*s", puri.field_data[UF_HOST].len, uri + puri.field_data[UF_HOST].off); + ESP_TRANSPORT_MEM_CHECK(TAG, client->config->host, return ESP_ERR_NO_MEM); + } + + + if (puri.field_data[UF_PATH].len) { + free(client->config->path); + asprintf(&client->config->path, "%.*s", puri.field_data[UF_PATH].len, uri + puri.field_data[UF_PATH].off); + ESP_TRANSPORT_MEM_CHECK(TAG, client->config->path, return ESP_ERR_NO_MEM); + + esp_transport_handle_t trans = esp_transport_list_get_transport(client->transport_list, "ws"); + if (trans) { + esp_transport_ws_set_path(trans, client->config->path); + } + trans = esp_transport_list_get_transport(client->transport_list, "wss"); + if (trans) { + esp_transport_ws_set_path(trans, client->config->path); + } + } + if (puri.field_data[UF_PORT].off) { + client->config->port = strtol((const char*)(uri + puri.field_data[UF_PORT].off), NULL, 10); + } + + if (puri.field_data[UF_USERINFO].len) { + char *user_info = NULL; + asprintf(&user_info, "%.*s", puri.field_data[UF_USERINFO].len, uri + puri.field_data[UF_USERINFO].off); + if (user_info) { + char *pass = strchr(user_info, ':'); + if (pass) { + pass[0] = 0; //terminal username + pass ++; + free(client->config->password); + client->config->password = strdup(pass); + ESP_TRANSPORT_MEM_CHECK(TAG, client->config->password, return ESP_ERR_NO_MEM); + } + free(client->config->username); + client->config->username = strdup(user_info); + ESP_TRANSPORT_MEM_CHECK(TAG, client->config->username, return ESP_ERR_NO_MEM); + free(user_info); + } else { + return ESP_ERR_NO_MEM; + } + } + return ESP_OK; +} + +static void esp_websocket_client_task(void *pv) +{ + int rlen; + esp_websocket_client_handle_t client = (esp_websocket_client_handle_t) pv; + client->run = true; + + //get transport by scheme + client->transport = esp_transport_list_get_transport(client->transport_list, client->config->scheme); + + if (client->transport == NULL) { + ESP_LOGE(TAG, "There are no transports valid, stop websocket client"); + client->run = false; + } + //default port + if (client->config->port == 0) { + client->config->port = esp_transport_get_default_port(client->transport); + } + + client->state = WEBSOCKET_STATE_INIT; + xEventGroupClearBits(client->status_bits, STOPPED_BIT); + int read_select; + while (client->run) { + switch ((int)client->state) { + case WEBSOCKET_STATE_INIT: + if (client->transport == NULL) { + ESP_LOGE(TAG, "There are no transport"); + client->run = false; + break; + } + + if (esp_transport_connect(client->transport, + client->config->host, + client->config->port, + client->config->network_timeout_ms) < 0) { + ESP_LOGE(TAG, "Error transport connect"); + esp_websocket_client_abort_connection(client); + break; + } + ESP_LOGD(TAG, "Transport connected to %s://%s:%d", client->config->scheme, client->config->host, client->config->port); + + client->state = WEBSOCKET_STATE_CONNECTED; + esp_websocket_client_dispatch_event(client, WEBSOCKET_EVENT_CONNECTED, NULL, 0); + + break; + case WEBSOCKET_STATE_CONNECTED: + read_select = esp_transport_poll_read(client->transport, 1000); //Poll every 1000ms + if (read_select < 0) { + ESP_LOGE(TAG, "Network error, errorno"); + esp_websocket_client_abort_connection(client); + break; + } + if (_tick_get_ms() - client->ping_tick_ms > WEBSOCKET_PING_TIMEOUT_MS) { + client->ping_tick_ms = _tick_get_ms(); + // Send PING + esp_transport_write(client->transport, NULL, 0, client->config->network_timeout_ms); + } + if (read_select == 0) { + ESP_LOGD(TAG, "Timeout..."); + continue; + } + client->ping_tick_ms = _tick_get_ms(); + + rlen = esp_transport_read(client->transport, client->rx_buffer, client->buffer_size, client->config->network_timeout_ms); + if (rlen < 0) { + ESP_LOGE(TAG, "Error read data"); + esp_websocket_client_abort_connection(client); + break; + } + if (rlen > 0) { + esp_websocket_client_dispatch_event(client, WEBSOCKET_EVENT_DATA, client->rx_buffer, rlen); + } + break; + case WEBSOCKET_STATE_WAIT_TIMEOUT: + + if (!client->config->auto_reconnect) { + client->run = false; + break; + } + if (_tick_get_ms() - client->reconnect_tick_ms > client->wait_timeout_ms) { + client->state = WEBSOCKET_STATE_INIT; + client->reconnect_tick_ms = _tick_get_ms(); + ESP_LOGD(TAG, "Reconnecting..."); + } + vTaskDelay(client->wait_timeout_ms / 2 / portTICK_RATE_MS); + break; + } + } + + esp_transport_close(client->transport); + xEventGroupSetBits(client->status_bits, STOPPED_BIT); + client->state = WEBSOCKET_STATE_UNKNOW; + vTaskDelete(NULL); +} + +esp_err_t esp_websocket_client_start(esp_websocket_client_handle_t client) +{ + if (client == NULL) { + return ESP_ERR_INVALID_ARG; + } + if (client->state >= WEBSOCKET_STATE_INIT) { + ESP_LOGE(TAG, "The client has started"); + return ESP_FAIL; + } + if (xTaskCreate(esp_websocket_client_task, "websocket_task", client->config->task_stack, client, client->config->task_prio, NULL) != pdTRUE) { + ESP_LOGE(TAG, "Error create websocket task"); + return ESP_FAIL; + } + xEventGroupClearBits(client->status_bits, STOPPED_BIT); + return ESP_OK; +} + +esp_err_t esp_websocket_client_stop(esp_websocket_client_handle_t client) +{ + if (client == NULL) { + return ESP_ERR_INVALID_ARG; + } + if (!client->run) { + ESP_LOGW(TAG, "Client was not started"); + return ESP_FAIL; + } + client->run = false; + xEventGroupWaitBits(client->status_bits, STOPPED_BIT, false, true, portMAX_DELAY); + client->state = WEBSOCKET_STATE_UNKNOW; + return ESP_OK; +} + +int esp_websocket_client_send(esp_websocket_client_handle_t client, const char *data, int len, TickType_t timeout) +{ + int need_write = len; + int wlen = 0, widx = 0; + + if (client == NULL || data == NULL || len <= 0) { + ESP_LOGE(TAG, "Invalid arguments"); + return ESP_FAIL; + } + + if (!esp_websocket_client_is_connected(client)) { + ESP_LOGE(TAG, "Websocket client is not connected"); + return ESP_FAIL; + } + + if (client->transport == NULL) { + ESP_LOGE(TAG, "Invalid transport"); + return ESP_FAIL; + } + + if (xSemaphoreTake(client->lock, timeout) != pdPASS) { + return ESP_FAIL; + } + + while (widx < len) { + if (need_write > client->buffer_size) { + need_write = client->buffer_size; + } + memcpy(client->tx_buffer, data + widx, need_write); + wlen = esp_transport_write(client->transport, + (char *)client->tx_buffer, + need_write, + client->config->network_timeout_ms); + if (wlen <= 0) { + xSemaphoreGive(client->lock); + return wlen; + } + widx += wlen; + need_write = len - widx; + } + xSemaphoreGive(client->lock); + return widx; +} + +bool esp_websocket_client_is_connected(esp_websocket_client_handle_t client) +{ + if (client == NULL) { + return false; + } + return client->state == WEBSOCKET_STATE_CONNECTED; +} + +esp_err_t esp_websocket_register_events(esp_websocket_client_handle_t client, + esp_websocket_event_id_t event, + esp_event_handler_t event_handler, + void* event_handler_arg) { + if (client == NULL) { + return ESP_ERR_INVALID_ARG; + } + return esp_event_handler_register_with(client->event_handle, WEBSOCKET_EVENTS, event, event_handler, event_handler_arg); +} diff --git a/components/esp_websocket_client/include/esp_websocket_client.h b/components/esp_websocket_client/include/esp_websocket_client.h new file mode 100644 index 000000000..898fab5ae --- /dev/null +++ b/components/esp_websocket_client/include/esp_websocket_client.h @@ -0,0 +1,195 @@ +// Copyright 2015-2018 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at + +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef _ESP_WEBSOCKET_CLIENT_H_ +#define _ESP_WEBSOCKET_CLIENT_H_ + + +#include +#include +#include +#include "freertos/FreeRTOS.h" +#include "esp_err.h" +#include "esp_event.h" +#include "esp_event_loop.h" + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct esp_websocket_client* esp_websocket_client_handle_t; + +ESP_EVENT_DECLARE_BASE(WEBSOCKET_EVENTS); // declaration of the task events family + +/** + * @brief Websocket Client events id + */ +typedef enum { + WEBSOCKET_EVENT_ANY = -1, + WEBSOCKET_EVENT_ERROR = 0, /*!< This event occurs when there are any errors during execution */ + WEBSOCKET_EVENT_CONNECTED, /*!< Once the Websocket has been connected to the server, no data exchange has been performed */ + WEBSOCKET_EVENT_DISCONNECTED, /*!< The connection has been disconnected */ + WEBSOCKET_EVENT_DATA, /*!< When receiving data from the server, possibly multiple portions of the packet */ + WEBSOCKET_EVENT_MAX +} esp_websocket_event_id_t; + +/** + * @brief Websocket event data + */ +typedef struct { + const char *data_ptr; /*!< Data pointer */ + int data_len; /*!< Data length */ +} esp_websocket_event_data_t; + +/** + * @brief Websocket Client transport + */ +typedef enum { + WEBSOCKET_TRANSPORT_UNKNOWN = 0x0, /*!< Transport unknown */ + WEBSOCKET_TRANSPORT_OVER_TCP, /*!< Transport over tcp */ + WEBSOCKET_TRANSPORT_OVER_SSL, /*!< Transport over ssl */ +} esp_websocket_transport_t; + +/** + * @brief Websocket Client events data + */ +typedef struct { + esp_websocket_event_id_t event_id; /*!< event_id, to know the cause of the event */ + esp_websocket_client_handle_t client; /*!< esp_websocket_client_handle_t context */ + void *user_context;/*!< user_data context, from esp_websocket_client_config_t user_data */ + char *data; /*!< data of the event */ + int data_len; /*!< length of data */ +} esp_websocket_event_t; + +typedef esp_websocket_event_t* esp_websocket_event_handle_t; +typedef esp_err_t (* websocket_event_callback_t)(esp_websocket_event_handle_t event); + +/** + * @brief Websocket client setup configuration + */ +typedef struct { + const char *uri; /*!< Websocket URI, the information on the URI can be overrides the other fields below, if any */ + const char *host; /*!< Domain or IP as string */ + int port; /*!< Port to connect, default depend on esp_websocket_transport_t (80 or 443) */ + const char *username; /*!< Using for Http authentication - Not supported for now */ + const char *password; /*!< Using for Http authentication - Not supported for now */ + const char *path; /*!< HTTP Path, if not set, default is `/` */ + bool disable_auto_reconnect; /*!< Disable the automatic reconnect function when disconnected */ + void *user_context; /*!< HTTP user data context */ + int task_prio; /*!< Websocket task priority */ + int task_stack; /*!< Websocket task stack */ + int buffer_size; /*!< Websocket buffer size */ + const char *cert_pem; /*!< SSL Certification, PEM format as string, if the client requires to verify server */ + esp_websocket_transport_t transport; /*!< Websocket transport type, see `esp_websocket_transport_t */ +} esp_websocket_client_config_t; + +/** + * @brief Start a Websocket session + * This function must be the first function to call, + * and it returns a esp_websocket_client_handle_t that you must use as input to other functions in the interface. + * This call MUST have a corresponding call to esp_websocket_client_destroy when the operation is complete. + * + * @param[in] config The configuration + * + * @return + * - `esp_websocket_client_handle_t` + * - NULL if any errors + */ +esp_websocket_client_handle_t esp_websocket_client_init(const esp_websocket_client_config_t *config); + +/** + * @brief Set URL for client, when performing this behavior, the options in the URL will replace the old ones + * Must stop the WebSocket client before set URI if the client has been connected + * + * @param[in] client The client + * @param[in] uri The uri + * + * @return esp_err_t + */ +esp_err_t esp_websocket_client_set_uri(esp_websocket_client_handle_t client, const char *uri); + +/** + * @brief Open the WebSocket connection + * + * @param[in] client The client + * + * @return esp_err_t + */ +esp_err_t esp_websocket_client_start(esp_websocket_client_handle_t client); + +/** + * @brief Close the WebSocket connection + * + * @param[in] client The client + * + * @return esp_err_t + */ +esp_err_t esp_websocket_client_stop(esp_websocket_client_handle_t client); + +/** + * @brief Destroy the WebSocket connection and free all resources. + * This function must be the last function to call for an session. + * It is the opposite of the esp_websocket_client_init function and must be called with the same handle as input that a esp_websocket_client_init call returned. + * This might close all connections this handle has used. + * + * @param[in] client The client + * + * @return esp_err_t + */ +esp_err_t esp_websocket_client_destroy(esp_websocket_client_handle_t client); + +/** + * @brief Write data to the WebSocket connection + * + * @param[in] client The client + * @param[in] data The data + * @param[in] len The length + * @param[in] timeout Write data timeout + * + * @return + * - Number of data was sent + * - (-1) if any errors + */ +int esp_websocket_client_send(esp_websocket_client_handle_t client, const char *data, int len, TickType_t timeout); + +/** + * @brief Check the WebSocket connection status + * + * @param[in] client The client handle + * + * @return + * - true + * - false + */ +bool esp_websocket_client_is_connected(esp_websocket_client_handle_t client); + +/** + * @brief Register the Websocket Events + * + * @param client The client handle + * @param event The event id + * @param event_handler The callback function + * @param event_handler_arg User context + * @return esp_err_t + */ +esp_err_t esp_websocket_register_events(esp_websocket_client_handle_t client, + esp_websocket_event_id_t event, + esp_event_handler_t event_handler, + void* event_handler_arg); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/docs/en/api-reference/protocols/esp_websocket_client.rst b/docs/en/api-reference/protocols/esp_websocket_client.rst new file mode 100644 index 000000000..cd4db2413 --- /dev/null +++ b/docs/en/api-reference/protocols/esp_websocket_client.rst @@ -0,0 +1,70 @@ +ESP WebSocket Client +==================== + +Overview +-------- +The ESP WebSocket client is an implementation of `WebSocket protocol client `_ for ESP32 + +Features +-------- + * supports WebSocket over TCP, SSL with mbedtls + * Easy to setup with URI + * Multiple instances (Multiple clients in one application) + +Configuration +------------- +URI +^^^ + +- Supports ``ws``, ``wss`` schemes +- WebSocket samples: + + - ``ws://websocket.org``: WebSocket over TCP, default port 80 + - ``wss://websocket.org``: WebSocket over SSL, default port 443 + +- Minimal configurations: + +.. code:: c + + const esp_websocket_client_config_t ws_cfg = { + .uri = "ws://websocket.org", + }; + +- If there are any options related to the URI in + ``esp_websocket_client_config_t``, the option defined by the URI will be + overridden. Sample: + +.. code:: c + + const esp_websocket_client_config_t ws_cfg = { + .uri = "ws://websocket.org:123", + .port = 4567, + }; + //WebSocket client will connect to websocket.org using port 4567 + +SSL +^^^ + +- Get certificate from server, example: ``websocket.org`` + ``openssl s_client -showcerts -connect websocket.org:443 /dev/null|openssl x509 -outform PEM >websocket_org.pem`` +- Configuration: + +.. code:: cpp + + const esp_websocket_client_config_t ws_cfg = { + .uri = "wss://websocket.org", + .cert_pem = (const char *)websocket_org_pem_start, + }; + +For more options on ``esp_websocket_client_config_t``, please refer to API reference below + +Application Example +------------------- +Simple WebSocket example that uses esp_websocket_client to establish a websocket connection and send/receive data with the `websocket.org `_ Server: :example:`protocols/websocket`. + + +API Reference +------------- + +.. include:: /_build/inc/esp_websocket_client.inc + diff --git a/examples/protocols/websocket/CMakeLists.txt b/examples/protocols/websocket/CMakeLists.txt new file mode 100644 index 000000000..f77ad23e5 --- /dev/null +++ b/examples/protocols/websocket/CMakeLists.txt @@ -0,0 +1,10 @@ +# The following four lines of boilerplate have to be in your project's CMakeLists +# in this exact order for cmake to work correctly +cmake_minimum_required(VERSION 3.5) + +# (Not part of the boilerplate) +# This example uses an extra component for common functions such as Wi-Fi and Ethernet connection. +set(EXTRA_COMPONENT_DIRS $ENV{IDF_PATH}/examples/common_components/protocol_examples_common) + +include($ENV{IDF_PATH}/tools/cmake/project.cmake) +project(websocket-example) diff --git a/examples/protocols/websocket/Makefile b/examples/protocols/websocket/Makefile new file mode 100644 index 000000000..3b37d3206 --- /dev/null +++ b/examples/protocols/websocket/Makefile @@ -0,0 +1,10 @@ +# +# This is a project Makefile. It is assumed the directory this Makefile resides in is a +# project subdirectory. +# +PROJECT_NAME := websocket-example + +EXTRA_COMPONENT_DIRS = $(IDF_PATH)/examples/common_components/protocol_examples_common + +include $(IDF_PATH)/make/project.mk + diff --git a/examples/protocols/websocket/README.md b/examples/protocols/websocket/README.md new file mode 100644 index 000000000..2969444fd --- /dev/null +++ b/examples/protocols/websocket/README.md @@ -0,0 +1 @@ +# Websocket Sample application diff --git a/examples/protocols/websocket/example_test.py b/examples/protocols/websocket/example_test.py new file mode 100644 index 000000000..ef0c3b2f2 --- /dev/null +++ b/examples/protocols/websocket/example_test.py @@ -0,0 +1,41 @@ +import re +import os +import sys +import IDF + +# this is a test case write with tiny-test-fw. +# to run test cases outside tiny-test-fw, +# we need to set environment variable `TEST_FW_PATH`, +# then get and insert `TEST_FW_PATH` to sys path before import FW module +test_fw_path = os.getenv("TEST_FW_PATH") +if test_fw_path and test_fw_path not in sys.path: + sys.path.insert(0, test_fw_path) + + +@IDF.idf_example_test(env_tag="Example_WIFI", ignore=True) +def test_examples_protocol_websocket(env, extra_data): + """ + steps: | + 1. join AP + 2. connect to ws://echo.websocket.org + 3. send and receive data + """ + dut1 = env.get_dut("websocket", "examples/protocols/websocket") + # check and log bin size + binary_file = os.path.join(dut1.app.binary_path, "websocket-example.bin") + bin_size = os.path.getsize(binary_file) + IDF.log_performance("websocket_bin_size", "{}KB".format(bin_size // 1024)) + IDF.check_performance("websocket_bin_size", bin_size // 1024) + # start test + dut1.start_app() + dut1.expect("Waiting for wifi ...") + dut1.expect("Connection established...", timeout=30) + dut1.expect("WEBSOCKET_EVENT_CONNECTED") + for i in range(0, 10): + dut1.expect(re.compile(r"Sending hello (\d)")) + dut1.expect(re.compile(r"Received=hello (\d)")) + dut1.expect("Websocket Stopped") + + +if __name__ == '__main__': + test_examples_protocol_websocket() diff --git a/examples/protocols/websocket/main/CMakeLists.txt b/examples/protocols/websocket/main/CMakeLists.txt new file mode 100644 index 000000000..caf642155 --- /dev/null +++ b/examples/protocols/websocket/main/CMakeLists.txt @@ -0,0 +1,4 @@ +set(COMPONENT_SRCS "websocket_example.c") +set(COMPONENT_ADD_INCLUDEDIRS ".") + +register_component() diff --git a/examples/protocols/websocket/main/Kconfig.projbuild b/examples/protocols/websocket/main/Kconfig.projbuild new file mode 100644 index 000000000..6af61c8f9 --- /dev/null +++ b/examples/protocols/websocket/main/Kconfig.projbuild @@ -0,0 +1,9 @@ +menu "Example Configuration" + + config WEBSOCKET_URI + string "Websocket endpoint URI" + default "ws://echo.websocket.org"; + help + URL of websocket endpoint this example connects to and sends echo + +endmenu diff --git a/examples/protocols/websocket/main/component.mk b/examples/protocols/websocket/main/component.mk new file mode 100644 index 000000000..e69de29bb diff --git a/examples/protocols/websocket/main/websocket_example.c b/examples/protocols/websocket/main/websocket_example.c new file mode 100644 index 000000000..120a5b4fd --- /dev/null +++ b/examples/protocols/websocket/main/websocket_example.c @@ -0,0 +1,103 @@ +/* ESP HTTP Client Example + + This example code is in the Public Domain (or CC0 licensed, at your option.) + + Unless required by applicable law or agreed to in writing, this + software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR + CONDITIONS OF ANY KIND, either express or implied. +*/ + + +#include +#include "esp_wifi.h" +#include "esp_system.h" +#include "nvs_flash.h" +#include "esp_event_loop.h" +#include "protocol_examples_common.h" + +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "freertos/event_groups.h" + + +#include "esp_log.h" +#include "esp_websocket_client.h" +#include "esp_event.h" +#include "esp_event_loop.h" + +static const char *TAG = "WEBSOCKET"; +static const char *WEBSOCKET_ECHO_ENDPOINT = CONFIG_WEBSOCKET_URI; + + +static void websocket_event_handler(void *handler_args, esp_event_base_t base, int32_t event_id, void *event_data) +{ + // esp_websocket_client_handle_t client = (esp_websocket_client_handle_t)handler_args; + esp_websocket_event_data_t *data = (esp_websocket_event_data_t *)event_data; + switch (event_id) { + case WEBSOCKET_EVENT_CONNECTED: + ESP_LOGI(TAG, "WEBSOCKET_EVENT_CONNECTED"); + + + break; + case WEBSOCKET_EVENT_DISCONNECTED: + ESP_LOGI(TAG, "WEBSOCKET_EVENT_DISCONNECTED"); + break; + + case WEBSOCKET_EVENT_DATA: + ESP_LOGI(TAG, "WEBSOCKET_EVENT_DATA"); + ESP_LOGW(TAG, "Received=%.*s\r\n", data->data_len, (char*)data->data_ptr); + break; + case WEBSOCKET_EVENT_ERROR: + ESP_LOGI(TAG, "WEBSOCKET_EVENT_ERROR"); + break; + } +} + +static void websocket_app_start(void) +{ + ESP_LOGI(TAG, "Connectiong to %s...", WEBSOCKET_ECHO_ENDPOINT); + + const esp_websocket_client_config_t websocket_cfg = { + .uri = WEBSOCKET_ECHO_ENDPOINT, // or wss://echo.websocket.org for websocket secure + }; + + esp_websocket_client_handle_t client = esp_websocket_client_init(&websocket_cfg); + esp_websocket_register_events(client, WEBSOCKET_EVENT_ANY, websocket_event_handler, (void *)client); + + esp_websocket_client_start(client); + char data[32]; + int i = 0; + while (i < 10) { + if (esp_websocket_client_is_connected(client)) { + int len = sprintf(data, "hello %04d", i++); + ESP_LOGI(TAG, "Sending %s", data); + esp_websocket_client_send(client, data, len, portMAX_DELAY); + } + vTaskDelay(1000 / portTICK_RATE_MS); + } + esp_websocket_client_stop(client); + ESP_LOGI(TAG, "Websocket Stopped"); + esp_websocket_client_destroy(client); +} + +void app_main() +{ + ESP_LOGI(TAG, "[APP] Startup.."); + ESP_LOGI(TAG, "[APP] Free memory: %d bytes", esp_get_free_heap_size()); + ESP_LOGI(TAG, "[APP] IDF version: %s", esp_get_idf_version()); + esp_log_level_set("*", ESP_LOG_INFO); + esp_log_level_set("WEBSOCKET_CLIENT", ESP_LOG_DEBUG); + esp_log_level_set("TRANS_TCP", ESP_LOG_DEBUG); + + ESP_ERROR_CHECK(nvs_flash_init()); + tcpip_adapter_init(); + ESP_ERROR_CHECK(esp_event_loop_create_default()); + + /* This helper function configures Wi-Fi or Ethernet, as selected in menuconfig. + * Read "Establishing Wi-Fi or Ethernet Connection" section in + * examples/protocols/README.md for more information about this function. + */ + ESP_ERROR_CHECK(example_connect()); + + websocket_app_start(); +} From 35d6f9a2c60ed04039f341d8873a00aa830a953d Mon Sep 17 00:00:00 2001 From: Renz Christian Bagaporo Date: Sun, 28 Apr 2019 15:38:46 +0800 Subject: [PATCH 02/65] examples: use new component registration api * Original commit: espressif/esp-idf@6771eead80534c51efb2033c04769ef5893b4838 --- components/esp_websocket_client/CMakeLists.txt | 7 +++---- examples/protocols/websocket/main/CMakeLists.txt | 6 ++---- 2 files changed, 5 insertions(+), 8 deletions(-) diff --git a/components/esp_websocket_client/CMakeLists.txt b/components/esp_websocket_client/CMakeLists.txt index 723199ce0..f366a278d 100644 --- a/components/esp_websocket_client/CMakeLists.txt +++ b/components/esp_websocket_client/CMakeLists.txt @@ -1,4 +1,3 @@ -set(COMPONENT_SRCS "esp_websocket_client.c") -set(COMPONENT_ADD_INCLUDEDIRS "include") -set(COMPONENT_REQUIRES lwip esp-tls tcp_transport nghttp) -register_component() +idf_component_register(SRCS "esp_websocket_client.c" + INCLUDE_DIRS "include" + REQUIRES lwip esp-tls tcp_transport nghttp) diff --git a/examples/protocols/websocket/main/CMakeLists.txt b/examples/protocols/websocket/main/CMakeLists.txt index caf642155..bff26f108 100644 --- a/examples/protocols/websocket/main/CMakeLists.txt +++ b/examples/protocols/websocket/main/CMakeLists.txt @@ -1,4 +1,2 @@ -set(COMPONENT_SRCS "websocket_example.c") -set(COMPONENT_ADD_INCLUDEDIRS ".") - -register_component() +idf_component_register(SRCS "websocket_example.c" + INCLUDE_DIRS ".") From 13a40d23446a658273794ca62665657d1f391ab4 Mon Sep 17 00:00:00 2001 From: David Cermak Date: Wed, 3 Jul 2019 17:06:38 +0200 Subject: [PATCH 03/65] ws_client: removed dependency on internal tcp_transport header * Original commit: espressif/esp-idf@d1433564ecfc885f80a7a261a88ab87d227cf1c2 --- .../esp_websocket_client.c | 54 ++++++++++--------- 1 file changed, 29 insertions(+), 25 deletions(-) diff --git a/components/esp_websocket_client/esp_websocket_client.c b/components/esp_websocket_client/esp_websocket_client.c index 9d4b1f053..e545559dc 100644 --- a/components/esp_websocket_client/esp_websocket_client.c +++ b/components/esp_websocket_client/esp_websocket_client.c @@ -19,7 +19,6 @@ #include "esp_transport_tcp.h" #include "esp_transport_ssl.h" #include "esp_transport_ws.h" -#include "esp_transport_utils.h" /* using uri parser */ #include "http_parser.h" #include "freertos/task.h" @@ -42,6 +41,11 @@ static const char *TAG = "WEBSOCKET_CLIENT"; #define WEBSOCKET_EVENT_QUEUE_SIZE (1) #define WEBSOCKET_SEND_EVENT_TIMEOUT_MS (1000/portTICK_RATE_MS) +#define ESP_WS_CLIENT_MEM_CHECK(TAG, a, action) if (!(a)) { \ + ESP_LOGE(TAG,"%s:%d (%s): %s", __FILE__, __LINE__, __FUNCTION__, "Memory exhausted"); \ + action; \ + } + const static int STOPPED_BIT = BIT0; ESP_EVENT_DEFINE_BASE(WEBSOCKET_EVENTS); @@ -139,7 +143,7 @@ static esp_err_t esp_websocket_client_set_config(esp_websocket_client_handle_t c if (config->host) { cfg->host = strdup(config->host); - ESP_TRANSPORT_MEM_CHECK(TAG, cfg->host, return ESP_ERR_NO_MEM); + ESP_WS_CLIENT_MEM_CHECK(TAG, cfg->host, return ESP_ERR_NO_MEM); } if (config->port) { @@ -149,7 +153,7 @@ static esp_err_t esp_websocket_client_set_config(esp_websocket_client_handle_t c if (config->username) { free(cfg->username); cfg->username = strdup(config->username); - ESP_TRANSPORT_MEM_CHECK(TAG, cfg->username, { + ESP_WS_CLIENT_MEM_CHECK(TAG, cfg->username, { free(cfg->host); return ESP_ERR_NO_MEM; }); @@ -158,7 +162,7 @@ static esp_err_t esp_websocket_client_set_config(esp_websocket_client_handle_t c if (config->password) { free(cfg->password); cfg->password = strdup(config->password); - ESP_TRANSPORT_MEM_CHECK(TAG, cfg->password, { + ESP_WS_CLIENT_MEM_CHECK(TAG, cfg->password, { free(cfg->host); free(cfg->username); return ESP_ERR_NO_MEM; @@ -168,7 +172,7 @@ static esp_err_t esp_websocket_client_set_config(esp_websocket_client_handle_t c if (config->uri) { free(cfg->uri); cfg->uri = strdup(config->uri); - ESP_TRANSPORT_MEM_CHECK(TAG, cfg->uri, { + ESP_WS_CLIENT_MEM_CHECK(TAG, cfg->uri, { free(cfg->host); free(cfg->username); free(cfg->password); @@ -178,7 +182,7 @@ static esp_err_t esp_websocket_client_set_config(esp_websocket_client_handle_t c if (config->path) { free(cfg->path); cfg->path = strdup(config->path); - ESP_TRANSPORT_MEM_CHECK(TAG, cfg->path, { + ESP_WS_CLIENT_MEM_CHECK(TAG, cfg->path, { free(cfg->uri); free(cfg->host); free(cfg->username); @@ -222,7 +226,7 @@ static esp_err_t esp_websocket_client_destroy_config(esp_websocket_client_handle esp_websocket_client_handle_t esp_websocket_client_init(const esp_websocket_client_config_t *config) { esp_websocket_client_handle_t client = calloc(1, sizeof(struct esp_websocket_client)); - ESP_TRANSPORT_MEM_CHECK(TAG, client, return NULL); + ESP_WS_CLIENT_MEM_CHECK(TAG, client, return NULL); esp_event_loop_args_t event_args = { .queue_size = WEBSOCKET_EVENT_QUEUE_SIZE, @@ -236,30 +240,30 @@ esp_websocket_client_handle_t esp_websocket_client_init(const esp_websocket_clie } client->lock = xSemaphoreCreateMutex(); - ESP_TRANSPORT_MEM_CHECK(TAG, client->lock, goto _websocket_init_fail); + ESP_WS_CLIENT_MEM_CHECK(TAG, client->lock, goto _websocket_init_fail); client->transport_list = esp_transport_list_init(); - ESP_TRANSPORT_MEM_CHECK(TAG, client->transport_list, goto _websocket_init_fail); + ESP_WS_CLIENT_MEM_CHECK(TAG, client->transport_list, goto _websocket_init_fail); esp_transport_handle_t tcp = esp_transport_tcp_init(); - ESP_TRANSPORT_MEM_CHECK(TAG, tcp, goto _websocket_init_fail); + ESP_WS_CLIENT_MEM_CHECK(TAG, tcp, goto _websocket_init_fail); esp_transport_set_default_port(tcp, WEBSOCKET_TCP_DEFAULT_PORT); esp_transport_list_add(client->transport_list, tcp, "_tcp"); // need to save to transport list, for cleanup esp_transport_handle_t ws = esp_transport_ws_init(tcp); - ESP_TRANSPORT_MEM_CHECK(TAG, ws, goto _websocket_init_fail); + ESP_WS_CLIENT_MEM_CHECK(TAG, ws, goto _websocket_init_fail); esp_transport_set_default_port(ws, WEBSOCKET_TCP_DEFAULT_PORT); esp_transport_list_add(client->transport_list, ws, "ws"); if (config->transport == WEBSOCKET_TRANSPORT_OVER_TCP) { asprintf(&client->config->scheme, "ws"); - ESP_TRANSPORT_MEM_CHECK(TAG, client->config->scheme, goto _websocket_init_fail); + ESP_WS_CLIENT_MEM_CHECK(TAG, client->config->scheme, goto _websocket_init_fail); } esp_transport_handle_t ssl = esp_transport_ssl_init(); - ESP_TRANSPORT_MEM_CHECK(TAG, ssl, goto _websocket_init_fail); + ESP_WS_CLIENT_MEM_CHECK(TAG, ssl, goto _websocket_init_fail); esp_transport_set_default_port(ssl, WEBSOCKET_SSL_DEFAULT_PORT); if (config->cert_pem) { @@ -268,18 +272,18 @@ esp_websocket_client_handle_t esp_websocket_client_init(const esp_websocket_clie esp_transport_list_add(client->transport_list, ssl, "_ssl"); // need to save to transport list, for cleanup esp_transport_handle_t wss = esp_transport_ws_init(ssl); - ESP_TRANSPORT_MEM_CHECK(TAG, wss, goto _websocket_init_fail); + ESP_WS_CLIENT_MEM_CHECK(TAG, wss, goto _websocket_init_fail); esp_transport_set_default_port(wss, WEBSOCKET_SSL_DEFAULT_PORT); esp_transport_list_add(client->transport_list, wss, "wss"); if (config->transport == WEBSOCKET_TRANSPORT_OVER_TCP) { asprintf(&client->config->scheme, "wss"); - ESP_TRANSPORT_MEM_CHECK(TAG, client->config->scheme, goto _websocket_init_fail); + ESP_WS_CLIENT_MEM_CHECK(TAG, client->config->scheme, goto _websocket_init_fail); } client->config = calloc(1, sizeof(websocket_config_storage_t)); - ESP_TRANSPORT_MEM_CHECK(TAG, client->config, goto _websocket_init_fail); + ESP_WS_CLIENT_MEM_CHECK(TAG, client->config, goto _websocket_init_fail); if (config->uri) { if (esp_websocket_client_set_uri(client, config->uri) != ESP_OK) { @@ -295,7 +299,7 @@ esp_websocket_client_handle_t esp_websocket_client_init(const esp_websocket_clie if (client->config->scheme == NULL) { asprintf(&client->config->scheme, "ws"); - ESP_TRANSPORT_MEM_CHECK(TAG, client->config->scheme, goto _websocket_init_fail); + ESP_WS_CLIENT_MEM_CHECK(TAG, client->config->scheme, goto _websocket_init_fail); } client->keepalive_tick_ms = _tick_get_ms(); @@ -307,15 +311,15 @@ esp_websocket_client_handle_t esp_websocket_client_init(const esp_websocket_clie buffer_size = WEBSOCKET_BUFFER_SIZE_BYTE; } client->rx_buffer = malloc(buffer_size); - ESP_TRANSPORT_MEM_CHECK(TAG, client->rx_buffer, { + ESP_WS_CLIENT_MEM_CHECK(TAG, client->rx_buffer, { goto _websocket_init_fail; }); client->tx_buffer = malloc(buffer_size); - ESP_TRANSPORT_MEM_CHECK(TAG, client->tx_buffer, { + ESP_WS_CLIENT_MEM_CHECK(TAG, client->tx_buffer, { goto _websocket_init_fail; }); client->status_bits = xEventGroupCreate(); - ESP_TRANSPORT_MEM_CHECK(TAG, client->status_bits, { + ESP_WS_CLIENT_MEM_CHECK(TAG, client->status_bits, { goto _websocket_init_fail; }); @@ -366,20 +370,20 @@ esp_err_t esp_websocket_client_set_uri(esp_websocket_client_handle_t client, con if (puri.field_data[UF_SCHEMA].len) { free(client->config->scheme); asprintf(&client->config->scheme, "%.*s", puri.field_data[UF_SCHEMA].len, uri + puri.field_data[UF_SCHEMA].off); - ESP_TRANSPORT_MEM_CHECK(TAG, client->config->scheme, return ESP_ERR_NO_MEM); + ESP_WS_CLIENT_MEM_CHECK(TAG, client->config->scheme, return ESP_ERR_NO_MEM); } if (puri.field_data[UF_HOST].len) { free(client->config->host); asprintf(&client->config->host, "%.*s", puri.field_data[UF_HOST].len, uri + puri.field_data[UF_HOST].off); - ESP_TRANSPORT_MEM_CHECK(TAG, client->config->host, return ESP_ERR_NO_MEM); + ESP_WS_CLIENT_MEM_CHECK(TAG, client->config->host, return ESP_ERR_NO_MEM); } if (puri.field_data[UF_PATH].len) { free(client->config->path); asprintf(&client->config->path, "%.*s", puri.field_data[UF_PATH].len, uri + puri.field_data[UF_PATH].off); - ESP_TRANSPORT_MEM_CHECK(TAG, client->config->path, return ESP_ERR_NO_MEM); + ESP_WS_CLIENT_MEM_CHECK(TAG, client->config->path, return ESP_ERR_NO_MEM); esp_transport_handle_t trans = esp_transport_list_get_transport(client->transport_list, "ws"); if (trans) { @@ -404,11 +408,11 @@ esp_err_t esp_websocket_client_set_uri(esp_websocket_client_handle_t client, con pass ++; free(client->config->password); client->config->password = strdup(pass); - ESP_TRANSPORT_MEM_CHECK(TAG, client->config->password, return ESP_ERR_NO_MEM); + ESP_WS_CLIENT_MEM_CHECK(TAG, client->config->password, return ESP_ERR_NO_MEM); } free(client->config->username); client->config->username = strdup(user_info); - ESP_TRANSPORT_MEM_CHECK(TAG, client->config->username, return ESP_ERR_NO_MEM); + ESP_WS_CLIENT_MEM_CHECK(TAG, client->config->username, return ESP_ERR_NO_MEM); free(user_info); } else { return ESP_ERR_NO_MEM; From f7186760834ce08bc578a8fdec07fddfbf046186 Mon Sep 17 00:00:00 2001 From: David Cermak Date: Thu, 20 Jun 2019 17:48:11 +0200 Subject: [PATCH 04/65] ws_client: fix double delete issue in ws client initialization * Original commit: espressif/esp-idf@9b507c45c86cf491466d705cd7896c6f6e500d0d --- .../esp_websocket_client.c | 26 +++---------------- 1 file changed, 4 insertions(+), 22 deletions(-) diff --git a/components/esp_websocket_client/esp_websocket_client.c b/components/esp_websocket_client/esp_websocket_client.c index e545559dc..a066c288c 100644 --- a/components/esp_websocket_client/esp_websocket_client.c +++ b/components/esp_websocket_client/esp_websocket_client.c @@ -153,42 +153,24 @@ static esp_err_t esp_websocket_client_set_config(esp_websocket_client_handle_t c if (config->username) { free(cfg->username); cfg->username = strdup(config->username); - ESP_WS_CLIENT_MEM_CHECK(TAG, cfg->username, { - free(cfg->host); - return ESP_ERR_NO_MEM; - }); + ESP_WS_CLIENT_MEM_CHECK(TAG, cfg->username, return ESP_ERR_NO_MEM); } if (config->password) { free(cfg->password); cfg->password = strdup(config->password); - ESP_WS_CLIENT_MEM_CHECK(TAG, cfg->password, { - free(cfg->host); - free(cfg->username); - return ESP_ERR_NO_MEM; - }); + ESP_WS_CLIENT_MEM_CHECK(TAG, cfg->password, return ESP_ERR_NO_MEM); } if (config->uri) { free(cfg->uri); cfg->uri = strdup(config->uri); - ESP_WS_CLIENT_MEM_CHECK(TAG, cfg->uri, { - free(cfg->host); - free(cfg->username); - free(cfg->password); - return ESP_ERR_NO_MEM; - }); + ESP_WS_CLIENT_MEM_CHECK(TAG, cfg->uri, return ESP_ERR_NO_MEM); } if (config->path) { free(cfg->path); cfg->path = strdup(config->path); - ESP_WS_CLIENT_MEM_CHECK(TAG, cfg->path, { - free(cfg->uri); - free(cfg->host); - free(cfg->username); - free(cfg->password); - return ESP_ERR_NO_MEM; - }); + ESP_WS_CLIENT_MEM_CHECK(TAG, cfg->path, return ESP_ERR_NO_MEM); } cfg->network_timeout_ms = WEBSOCKET_NETWORK_TIMEOUT_MS; From da74a4a4895efaed8bb88910a6ac4b9415a84ce4 Mon Sep 17 00:00:00 2001 From: Anton Maklakov Date: Tue, 16 Jul 2019 16:33:30 +0700 Subject: [PATCH 05/65] tools: Mass fixing of empty prototypes (for -Wstrict-prototypes) * Original commit: espressif/esp-idf@afbaf74007e89d016dbade4072bf2e7a3874139a --- components/esp_websocket_client/esp_websocket_client.c | 2 +- examples/protocols/websocket/main/websocket_example.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/components/esp_websocket_client/esp_websocket_client.c b/components/esp_websocket_client/esp_websocket_client.c index a066c288c..cc77267d0 100644 --- a/components/esp_websocket_client/esp_websocket_client.c +++ b/components/esp_websocket_client/esp_websocket_client.c @@ -92,7 +92,7 @@ struct esp_websocket_client { int buffer_size; }; -static uint64_t _tick_get_ms() +static uint64_t _tick_get_ms(void) { return esp_timer_get_time()/1000; } diff --git a/examples/protocols/websocket/main/websocket_example.c b/examples/protocols/websocket/main/websocket_example.c index 120a5b4fd..6b64ef25d 100644 --- a/examples/protocols/websocket/main/websocket_example.c +++ b/examples/protocols/websocket/main/websocket_example.c @@ -80,7 +80,7 @@ static void websocket_app_start(void) esp_websocket_client_destroy(client); } -void app_main() +void app_main(void) { ESP_LOGI(TAG, "[APP] Startup.."); ESP_LOGI(TAG, "[APP] Free memory: %d bytes", esp_get_free_heap_size()); From 4d644954fede0b7164ecfa6c6598d6b0d557e359 Mon Sep 17 00:00:00 2001 From: liu zhifu Date: Fri, 5 Jul 2019 16:58:04 +0800 Subject: [PATCH 06/65] esp_wifi: wifi support new event mechanism 1. WiFi support new event mechanism 2. Update examples to use new event mechanism * Original commit: espressif/esp-idf@003a9872b7de69d799e9d37521cfbcaff9b37e85 --- components/esp_websocket_client/include/esp_websocket_client.h | 1 - examples/protocols/websocket/main/websocket_example.c | 3 +-- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/components/esp_websocket_client/include/esp_websocket_client.h b/components/esp_websocket_client/include/esp_websocket_client.h index 898fab5ae..a8bcc5e2f 100644 --- a/components/esp_websocket_client/include/esp_websocket_client.h +++ b/components/esp_websocket_client/include/esp_websocket_client.h @@ -22,7 +22,6 @@ #include "freertos/FreeRTOS.h" #include "esp_err.h" #include "esp_event.h" -#include "esp_event_loop.h" #ifdef __cplusplus extern "C" { diff --git a/examples/protocols/websocket/main/websocket_example.c b/examples/protocols/websocket/main/websocket_example.c index 6b64ef25d..901d2ad92 100644 --- a/examples/protocols/websocket/main/websocket_example.c +++ b/examples/protocols/websocket/main/websocket_example.c @@ -12,7 +12,7 @@ #include "esp_wifi.h" #include "esp_system.h" #include "nvs_flash.h" -#include "esp_event_loop.h" +#include "esp_event.h" #include "protocol_examples_common.h" #include "freertos/FreeRTOS.h" @@ -23,7 +23,6 @@ #include "esp_log.h" #include "esp_websocket_client.h" #include "esp_event.h" -#include "esp_event_loop.h" static const char *TAG = "WEBSOCKET"; static const char *WEBSOCKET_ECHO_ENDPOINT = CONFIG_WEBSOCKET_URI; From 343fbfdcc913a6af9705df9c84b6348756251391 Mon Sep 17 00:00:00 2001 From: "Michael (XIAO Xufeng)" Date: Thu, 29 Aug 2019 17:54:14 +0800 Subject: [PATCH 07/65] ci: limit example test to ESP32s * Original commit: espressif/esp-idf@63329b169bd1a3bc153bf37a7187f4ea17012852 --- examples/protocols/websocket/example_test.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/examples/protocols/websocket/example_test.py b/examples/protocols/websocket/example_test.py index ef0c3b2f2..4ca10b204 100644 --- a/examples/protocols/websocket/example_test.py +++ b/examples/protocols/websocket/example_test.py @@ -2,6 +2,7 @@ import re import os import sys import IDF +from IDF.IDFDUT import ESP32DUT # this is a test case write with tiny-test-fw. # to run test cases outside tiny-test-fw, @@ -20,7 +21,7 @@ def test_examples_protocol_websocket(env, extra_data): 2. connect to ws://echo.websocket.org 3. send and receive data """ - dut1 = env.get_dut("websocket", "examples/protocols/websocket") + dut1 = env.get_dut("websocket", "examples/protocols/websocket", dut_class=ESP32DUT) # check and log bin size binary_file = os.path.join(dut1.app.binary_path, "websocket-example.bin") bin_size = os.path.getsize(binary_file) From bfc88ab76c2bf7d562db13a272ee3f748feb59d9 Mon Sep 17 00:00:00 2001 From: David Cermak Date: Thu, 22 Aug 2019 21:01:08 +0200 Subject: [PATCH 08/65] ws_client: fixed transport config option when server address configured as host, port, transport rather then uri closes https://github.com/espressif/esp-idf/issues/3891 * Original commit: espressif/esp-idf@adee25d90e100a169e959f94db23621f6ffab0e6 --- components/esp_websocket_client/esp_websocket_client.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/components/esp_websocket_client/esp_websocket_client.c b/components/esp_websocket_client/esp_websocket_client.c index cc77267d0..a3226350e 100644 --- a/components/esp_websocket_client/esp_websocket_client.c +++ b/components/esp_websocket_client/esp_websocket_client.c @@ -224,6 +224,9 @@ esp_websocket_client_handle_t esp_websocket_client_init(const esp_websocket_clie client->lock = xSemaphoreCreateMutex(); ESP_WS_CLIENT_MEM_CHECK(TAG, client->lock, goto _websocket_init_fail); + client->config = calloc(1, sizeof(websocket_config_storage_t)); + ESP_WS_CLIENT_MEM_CHECK(TAG, client->config, goto _websocket_init_fail); + client->transport_list = esp_transport_list_init(); ESP_WS_CLIENT_MEM_CHECK(TAG, client->transport_list, goto _websocket_init_fail); @@ -259,14 +262,11 @@ esp_websocket_client_handle_t esp_websocket_client_init(const esp_websocket_clie esp_transport_set_default_port(wss, WEBSOCKET_SSL_DEFAULT_PORT); esp_transport_list_add(client->transport_list, wss, "wss"); - if (config->transport == WEBSOCKET_TRANSPORT_OVER_TCP) { + if (config->transport == WEBSOCKET_TRANSPORT_OVER_SSL) { asprintf(&client->config->scheme, "wss"); ESP_WS_CLIENT_MEM_CHECK(TAG, client->config->scheme, goto _websocket_init_fail); } - client->config = calloc(1, sizeof(websocket_config_storage_t)); - ESP_WS_CLIENT_MEM_CHECK(TAG, client->config, goto _websocket_init_fail); - if (config->uri) { if (esp_websocket_client_set_uri(client, config->uri) != ESP_OK) { ESP_LOGE(TAG, "Invalid uri"); From 67949f94f4a5450cb5f692d7ed3eab79e47e21ba Mon Sep 17 00:00:00 2001 From: David Cermak Date: Thu, 22 Aug 2019 21:25:20 +0200 Subject: [PATCH 09/65] ws_client: fixed path config issue when ws server configured using host and path instead of uri closes https://github.com/espressif/esp-idf/issues/3892 * Original commit: espressif/esp-idf@c0ba9e19fc6cff79f5760b991c259970bd4abeab --- .../esp_websocket_client.c | 26 ++++++++++++------- 1 file changed, 17 insertions(+), 9 deletions(-) diff --git a/components/esp_websocket_client/esp_websocket_client.c b/components/esp_websocket_client/esp_websocket_client.c index a3226350e..6bd1cf2ae 100644 --- a/components/esp_websocket_client/esp_websocket_client.c +++ b/components/esp_websocket_client/esp_websocket_client.c @@ -205,6 +205,18 @@ static esp_err_t esp_websocket_client_destroy_config(esp_websocket_client_handle return ESP_OK; } +static void set_websocket_client_path(esp_websocket_client_handle_t client) +{ + esp_transport_handle_t trans = esp_transport_list_get_transport(client->transport_list, "ws"); + if (trans) { + esp_transport_ws_set_path(trans, client->config->path); + } + trans = esp_transport_list_get_transport(client->transport_list, "wss"); + if (trans) { + esp_transport_ws_set_path(trans, client->config->path); + } +} + esp_websocket_client_handle_t esp_websocket_client_init(const esp_websocket_client_config_t *config) { esp_websocket_client_handle_t client = calloc(1, sizeof(struct esp_websocket_client)); @@ -284,6 +296,10 @@ esp_websocket_client_handle_t esp_websocket_client_init(const esp_websocket_clie ESP_WS_CLIENT_MEM_CHECK(TAG, client->config->scheme, goto _websocket_init_fail); } + if (client->config->path) { + set_websocket_client_path(client); + } + client->keepalive_tick_ms = _tick_get_ms(); client->reconnect_tick_ms = _tick_get_ms(); client->ping_tick_ms = _tick_get_ms(); @@ -366,15 +382,7 @@ esp_err_t esp_websocket_client_set_uri(esp_websocket_client_handle_t client, con free(client->config->path); asprintf(&client->config->path, "%.*s", puri.field_data[UF_PATH].len, uri + puri.field_data[UF_PATH].off); ESP_WS_CLIENT_MEM_CHECK(TAG, client->config->path, return ESP_ERR_NO_MEM); - - esp_transport_handle_t trans = esp_transport_list_get_transport(client->transport_list, "ws"); - if (trans) { - esp_transport_ws_set_path(trans, client->config->path); - } - trans = esp_transport_list_get_transport(client->transport_list, "wss"); - if (trans) { - esp_transport_ws_set_path(trans, client->config->path); - } + set_websocket_client_path(client); } if (puri.field_data[UF_PORT].off) { client->config->port = strtol((const char*)(uri + puri.field_data[UF_PORT].off), NULL, 10); From 2553d65e64c18ac091825bfddca193648b2e15de Mon Sep 17 00:00:00 2001 From: David Cermak Date: Thu, 22 Aug 2019 22:00:41 +0200 Subject: [PATCH 10/65] ws_client: added subprotocol configuration option to websocket client closes https://github.com/espressif/esp-idf/issues/3893 * Original commit: espressif/esp-idf@de6ea396f17be820153da6acaf977c1bf11806fb --- .../esp_websocket_client.c | 23 +++++++++++-------- .../include/esp_websocket_client.h | 1 + 2 files changed, 14 insertions(+), 10 deletions(-) diff --git a/components/esp_websocket_client/esp_websocket_client.c b/components/esp_websocket_client/esp_websocket_client.c index 6bd1cf2ae..dc7333419 100644 --- a/components/esp_websocket_client/esp_websocket_client.c +++ b/components/esp_websocket_client/esp_websocket_client.c @@ -63,6 +63,7 @@ typedef struct { bool auto_reconnect; void *user_context; int network_timeout_ms; + char *subprotocol; } websocket_config_storage_t; typedef enum { @@ -172,6 +173,11 @@ static esp_err_t esp_websocket_client_set_config(esp_websocket_client_handle_t c cfg->path = strdup(config->path); ESP_WS_CLIENT_MEM_CHECK(TAG, cfg->path, return ESP_ERR_NO_MEM); } + if (config->subprotocol) { + free(cfg->subprotocol); + cfg->subprotocol = strdup(config->subprotocol); + ESP_WS_CLIENT_MEM_CHECK(TAG, cfg->subprotocol, return ESP_ERR_NO_MEM); + } cfg->network_timeout_ms = WEBSOCKET_NETWORK_TIMEOUT_MS; cfg->user_context = config->user_context; @@ -199,21 +205,20 @@ static esp_err_t esp_websocket_client_destroy_config(esp_websocket_client_handle free(cfg->scheme); free(cfg->username); free(cfg->password); + free(cfg->subprotocol); memset(cfg, 0, sizeof(websocket_config_storage_t)); free(client->config); client->config = NULL; return ESP_OK; } -static void set_websocket_client_path(esp_websocket_client_handle_t client) +static void set_websocket_transport_optional_settings(esp_websocket_client_handle_t client, esp_transport_handle_t trans) { - esp_transport_handle_t trans = esp_transport_list_get_transport(client->transport_list, "ws"); - if (trans) { + if (trans && client->config->path) { esp_transport_ws_set_path(trans, client->config->path); } - trans = esp_transport_list_get_transport(client->transport_list, "wss"); - if (trans) { - esp_transport_ws_set_path(trans, client->config->path); + if (trans && client->config->subprotocol) { + esp_transport_ws_set_subprotocol(trans, client->config->subprotocol); } } @@ -296,9 +301,8 @@ esp_websocket_client_handle_t esp_websocket_client_init(const esp_websocket_clie ESP_WS_CLIENT_MEM_CHECK(TAG, client->config->scheme, goto _websocket_init_fail); } - if (client->config->path) { - set_websocket_client_path(client); - } + set_websocket_transport_optional_settings(client, esp_transport_list_get_transport(client->transport_list, "ws")); + set_websocket_transport_optional_settings(client, esp_transport_list_get_transport(client->transport_list, "wss")); client->keepalive_tick_ms = _tick_get_ms(); client->reconnect_tick_ms = _tick_get_ms(); @@ -382,7 +386,6 @@ esp_err_t esp_websocket_client_set_uri(esp_websocket_client_handle_t client, con free(client->config->path); asprintf(&client->config->path, "%.*s", puri.field_data[UF_PATH].len, uri + puri.field_data[UF_PATH].off); ESP_WS_CLIENT_MEM_CHECK(TAG, client->config->path, return ESP_ERR_NO_MEM); - set_websocket_client_path(client); } if (puri.field_data[UF_PORT].off) { client->config->port = strtol((const char*)(uri + puri.field_data[UF_PORT].off), NULL, 10); diff --git a/components/esp_websocket_client/include/esp_websocket_client.h b/components/esp_websocket_client/include/esp_websocket_client.h index a8bcc5e2f..4f1e0a3fc 100644 --- a/components/esp_websocket_client/include/esp_websocket_client.h +++ b/components/esp_websocket_client/include/esp_websocket_client.h @@ -91,6 +91,7 @@ typedef struct { int buffer_size; /*!< Websocket buffer size */ const char *cert_pem; /*!< SSL Certification, PEM format as string, if the client requires to verify server */ esp_websocket_transport_t transport; /*!< Websocket transport type, see `esp_websocket_transport_t */ + char *subprotocol; /*!< Websocket subprotocol */ } esp_websocket_client_config_t; /** From 23f6a1d46e3b9ec38ac27455c71e8f54d0c9fedd Mon Sep 17 00:00:00 2001 From: David Cermak Date: Mon, 26 Aug 2019 08:30:09 +0200 Subject: [PATCH 11/65] ws_client: fixed posting to event loop with websocket timeout Executing event loop `esp_event_loop_run()` with timeout causes delays in receiving events from user code. Fixed by removing the timeout to post synchronously. closes https://github.com/espressif/esp-idf/issues/3957 * Original commit: espressif/esp-idf@50505068c45fbe97611be9b7f2c30b8160cbb9e3 --- components/esp_websocket_client/esp_websocket_client.c | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/components/esp_websocket_client/esp_websocket_client.c b/components/esp_websocket_client/esp_websocket_client.c index dc7333419..c6fe5ce86 100644 --- a/components/esp_websocket_client/esp_websocket_client.c +++ b/components/esp_websocket_client/esp_websocket_client.c @@ -39,7 +39,6 @@ static const char *TAG = "WEBSOCKET_CLIENT"; #define WEBSOCKET_NETWORK_TIMEOUT_MS (10*1000) #define WEBSOCKET_PING_TIMEOUT_MS (10*1000) #define WEBSOCKET_EVENT_QUEUE_SIZE (1) -#define WEBSOCKET_SEND_EVENT_TIMEOUT_MS (1000/portTICK_RATE_MS) #define ESP_WS_CLIENT_MEM_CHECK(TAG, a, action) if (!(a)) { \ ESP_LOGE(TAG,"%s:%d (%s): %s", __FILE__, __LINE__, __FUNCTION__, "Memory exhausted"); \ @@ -112,10 +111,10 @@ static esp_err_t esp_websocket_client_dispatch_event(esp_websocket_client_handle WEBSOCKET_EVENTS, event, &event_data, sizeof(esp_websocket_event_data_t), - WEBSOCKET_SEND_EVENT_TIMEOUT_MS)) != ESP_OK) { + portMAX_DELAY)) != ESP_OK) { return err; } - return esp_event_loop_run(client->event_handle, WEBSOCKET_SEND_EVENT_TIMEOUT_MS); + return esp_event_loop_run(client->event_handle, 0); } static esp_err_t esp_websocket_client_abort_connection(esp_websocket_client_handle_t client) From f5a26c4d32f2d7136007c565481c0477c9de064a Mon Sep 17 00:00:00 2001 From: David Cermak Date: Wed, 25 Sep 2019 15:30:01 +0200 Subject: [PATCH 12/65] websocket_client: fix URI parsing to include also query part in websocket connection path closes https://github.com/espressif/esp-idf/issues/4090 * Original commit: espressif/esp-idf@271e6c4c9c57ca6715c1435a71fe3974cd2b18b3 --- .../esp_websocket_client/esp_websocket_client.c | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/components/esp_websocket_client/esp_websocket_client.c b/components/esp_websocket_client/esp_websocket_client.c index c6fe5ce86..413742d4a 100644 --- a/components/esp_websocket_client/esp_websocket_client.c +++ b/components/esp_websocket_client/esp_websocket_client.c @@ -381,9 +381,16 @@ esp_err_t esp_websocket_client_set_uri(esp_websocket_client_handle_t client, con } - if (puri.field_data[UF_PATH].len) { + if (puri.field_data[UF_PATH].len || puri.field_data[UF_QUERY].len) { free(client->config->path); - asprintf(&client->config->path, "%.*s", puri.field_data[UF_PATH].len, uri + puri.field_data[UF_PATH].off); + if (puri.field_data[UF_QUERY].len == 0) { + asprintf(&client->config->path, "%.*s", puri.field_data[UF_PATH].len, uri + puri.field_data[UF_PATH].off); + } else if (puri.field_data[UF_PATH].len == 0) { + asprintf(&client->config->path, "/?%.*s", puri.field_data[UF_QUERY].len, uri + puri.field_data[UF_QUERY].off); + } else { + asprintf(&client->config->path, "%.*s?%.*s", puri.field_data[UF_PATH].len, uri + puri.field_data[UF_PATH].off, + puri.field_data[UF_QUERY].len, uri + puri.field_data[UF_QUERY].off); + } ESP_WS_CLIENT_MEM_CHECK(TAG, client->config->path, return ESP_ERR_NO_MEM); } if (puri.field_data[UF_PORT].off) { From fe26b734b55077c882b07e3d268a6a1ac934cb86 Mon Sep 17 00:00:00 2001 From: Roland Dobai Date: Fri, 4 Oct 2019 12:14:05 +0200 Subject: [PATCH 13/65] Cosmetic Kconfig fixes * Original commit: espressif/esp-idf@d3ed17acd7360caafb5e7d84f8eedd2c353801ce --- examples/protocols/websocket/main/Kconfig.projbuild | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/protocols/websocket/main/Kconfig.projbuild b/examples/protocols/websocket/main/Kconfig.projbuild index 6af61c8f9..7790aa248 100644 --- a/examples/protocols/websocket/main/Kconfig.projbuild +++ b/examples/protocols/websocket/main/Kconfig.projbuild @@ -2,7 +2,7 @@ menu "Example Configuration" config WEBSOCKET_URI string "Websocket endpoint URI" - default "ws://echo.websocket.org"; + default "ws://echo.websocket.org" help URL of websocket endpoint this example connects to and sends echo From f55d8391c9e327f51664bbe91dc98557bdc759f4 Mon Sep 17 00:00:00 2001 From: David Cermak Date: Mon, 16 Sep 2019 15:22:29 +0200 Subject: [PATCH 14/65] ws_client: fix for not sending ping responses, updated to pass events also for PING and PONG messages, added interfaces to send both binary and text data closes https://github.com/espressif/esp-idf/issues/3982 * Original commit: espressif/esp-idf@abf9345b85559f4a922e8387f48336fb09994041 --- .../esp_websocket_client.c | 36 +++++++++++++++---- .../include/esp_websocket_client.h | 32 +++++++++++++++-- .../websocket/main/websocket_example.c | 1 + 3 files changed, 61 insertions(+), 8 deletions(-) diff --git a/components/esp_websocket_client/esp_websocket_client.c b/components/esp_websocket_client/esp_websocket_client.c index 413742d4a..fcfc86cec 100644 --- a/components/esp_websocket_client/esp_websocket_client.c +++ b/components/esp_websocket_client/esp_websocket_client.c @@ -90,6 +90,7 @@ struct esp_websocket_client { char *rx_buffer; char *tx_buffer; int buffer_size; + ws_transport_opcodes_t last_opcode; }; static uint64_t _tick_get_ms(void) @@ -106,6 +107,7 @@ static esp_err_t esp_websocket_client_dispatch_event(esp_websocket_client_handle esp_websocket_event_data_t event_data; event_data.data_ptr = data; event_data.data_len = data_len; + event_data.op_code = client->last_opcode; if ((err = esp_event_post_to(client->event_handle, WEBSOCKET_EVENTS, event, @@ -474,7 +476,7 @@ static void esp_websocket_client_task(void *pv) if (_tick_get_ms() - client->ping_tick_ms > WEBSOCKET_PING_TIMEOUT_MS) { client->ping_tick_ms = _tick_get_ms(); // Send PING - esp_transport_write(client->transport, NULL, 0, client->config->network_timeout_ms); + esp_transport_ws_send_raw(client->transport, WS_TRANSPORT_OPCODES_PING, NULL, 0, client->config->network_timeout_ms); } if (read_select == 0) { ESP_LOGD(TAG, "Timeout..."); @@ -488,8 +490,15 @@ static void esp_websocket_client_task(void *pv) esp_websocket_client_abort_connection(client); break; } - if (rlen > 0) { + if (rlen >= 0) { + client->last_opcode = esp_transport_ws_get_read_opcode(client->transport); esp_websocket_client_dispatch_event(client, WEBSOCKET_EVENT_DATA, client->rx_buffer, rlen); + // if a PING message received -> send out the PONG + if (client->last_opcode == WS_TRANSPORT_OPCODES_PING) { + const char *data = (rlen == 0) ? NULL : client->rx_buffer; + esp_transport_ws_send_raw(client->transport, WS_TRANSPORT_OPCODES_PONG, data, rlen, + client->config->network_timeout_ms); + } } break; case WEBSOCKET_STATE_WAIT_TIMEOUT: @@ -546,7 +555,24 @@ esp_err_t esp_websocket_client_stop(esp_websocket_client_handle_t client) return ESP_OK; } +static int esp_websocket_client_send_with_opcode(esp_websocket_client_handle_t client, ws_transport_opcodes_t opcode, const char *data, int len, TickType_t timeout); + +int esp_websocket_client_send_text(esp_websocket_client_handle_t client, const char *data, int len, TickType_t timeout) +{ + return esp_websocket_client_send_with_opcode(client, WS_TRANSPORT_OPCODES_TEXT, data, len, timeout); +} + int esp_websocket_client_send(esp_websocket_client_handle_t client, const char *data, int len, TickType_t timeout) +{ + return esp_websocket_client_send_with_opcode(client, WS_TRANSPORT_OPCODES_BINARY, data, len, timeout); +} + +int esp_websocket_client_send_bin(esp_websocket_client_handle_t client, const char *data, int len, TickType_t timeout) +{ + return esp_websocket_client_send_with_opcode(client, WS_TRANSPORT_OPCODES_BINARY, data, len, timeout); +} + +static int esp_websocket_client_send_with_opcode(esp_websocket_client_handle_t client, ws_transport_opcodes_t opcode, const char *data, int len, TickType_t timeout) { int need_write = len; int wlen = 0, widx = 0; @@ -575,10 +601,8 @@ int esp_websocket_client_send(esp_websocket_client_handle_t client, const char * need_write = client->buffer_size; } memcpy(client->tx_buffer, data + widx, need_write); - wlen = esp_transport_write(client->transport, - (char *)client->tx_buffer, - need_write, - client->config->network_timeout_ms); + // send with ws specific way and specific opcode + wlen = esp_transport_ws_send_raw(client->transport, opcode, (char *)client->tx_buffer, need_write, timeout); if (wlen <= 0) { xSemaphoreGive(client->lock); return wlen; diff --git a/components/esp_websocket_client/include/esp_websocket_client.h b/components/esp_websocket_client/include/esp_websocket_client.h index 4f1e0a3fc..444343c82 100644 --- a/components/esp_websocket_client/include/esp_websocket_client.h +++ b/components/esp_websocket_client/include/esp_websocket_client.h @@ -49,6 +49,7 @@ typedef enum { typedef struct { const char *data_ptr; /*!< Data pointer */ int data_len; /*!< Data length */ + uint8_t op_code; /*!< Received opcode */ } esp_websocket_event_data_t; /** @@ -72,7 +73,6 @@ typedef struct { } esp_websocket_event_t; typedef esp_websocket_event_t* esp_websocket_event_handle_t; -typedef esp_err_t (* websocket_event_callback_t)(esp_websocket_event_handle_t event); /** * @brief Websocket client setup configuration @@ -150,7 +150,7 @@ esp_err_t esp_websocket_client_stop(esp_websocket_client_handle_t client); esp_err_t esp_websocket_client_destroy(esp_websocket_client_handle_t client); /** - * @brief Write data to the WebSocket connection + * @brief Generic write data to the WebSocket connection; defaults to binary send * * @param[in] client The client * @param[in] data The data @@ -163,6 +163,34 @@ esp_err_t esp_websocket_client_destroy(esp_websocket_client_handle_t client); */ int esp_websocket_client_send(esp_websocket_client_handle_t client, const char *data, int len, TickType_t timeout); +/** + * @brief Write binary data to the WebSocket connection (data send with WS OPCODE=02, i.e. binary) + * + * @param[in] client The client + * @param[in] data The data + * @param[in] len The length + * @param[in] timeout Write data timeout + * + * @return + * - Number of data was sent + * - (-1) if any errors + */ +int esp_websocket_client_send_bin(esp_websocket_client_handle_t client, const char *data, int len, TickType_t timeout); + +/** + * @brief Write textual data to the WebSocket connection (data send with WS OPCODE=01, i.e. text) + * + * @param[in] client The client + * @param[in] data The data + * @param[in] len The length + * @param[in] timeout Write data timeout + * + * @return + * - Number of data was sent + * - (-1) if any errors + */ +int esp_websocket_client_send_text(esp_websocket_client_handle_t client, const char *data, int len, TickType_t timeout); + /** * @brief Check the WebSocket connection status * diff --git a/examples/protocols/websocket/main/websocket_example.c b/examples/protocols/websocket/main/websocket_example.c index 901d2ad92..f82ac9494 100644 --- a/examples/protocols/websocket/main/websocket_example.c +++ b/examples/protocols/websocket/main/websocket_example.c @@ -44,6 +44,7 @@ static void websocket_event_handler(void *handler_args, esp_event_base_t base, i case WEBSOCKET_EVENT_DATA: ESP_LOGI(TAG, "WEBSOCKET_EVENT_DATA"); + ESP_LOGI(TAG, "Received opcode=%d", data->op_code); ESP_LOGW(TAG, "Received=%.*s\r\n", data->data_len, (char*)data->data_ptr); break; case WEBSOCKET_EVENT_ERROR: From d0121b964d8875d4e0197d12349d054888b54f1b Mon Sep 17 00:00:00 2001 From: David Cermak Date: Mon, 7 Oct 2019 16:24:57 +0200 Subject: [PATCH 15/65] websocket_client: fix locking mechanism in ws-client task and when sending data closes https://github.com/espressif/esp-idf/issues/4169 * Original commit: espressif/esp-idf@7c5011f411b7662feb50fd1e53114bec390d8c2e --- .../esp_websocket_client.c | 58 ++++++++++++------- 1 file changed, 36 insertions(+), 22 deletions(-) diff --git a/components/esp_websocket_client/esp_websocket_client.c b/components/esp_websocket_client/esp_websocket_client.c index fcfc86cec..b509463cf 100644 --- a/components/esp_websocket_client/esp_websocket_client.c +++ b/components/esp_websocket_client/esp_websocket_client.c @@ -239,7 +239,7 @@ esp_websocket_client_handle_t esp_websocket_client_init(const esp_websocket_clie return NULL; } - client->lock = xSemaphoreCreateMutex(); + client->lock = xSemaphoreCreateRecursiveMutex(); ESP_WS_CLIENT_MEM_CHECK(TAG, client->lock, goto _websocket_init_fail); client->config = calloc(1, sizeof(websocket_config_storage_t)); @@ -424,6 +424,7 @@ esp_err_t esp_websocket_client_set_uri(esp_websocket_client_handle_t client, con static void esp_websocket_client_task(void *pv) { + const int lock_timeout = portMAX_DELAY; int rlen; esp_websocket_client_handle_t client = (esp_websocket_client_handle_t) pv; client->run = true; @@ -442,8 +443,12 @@ static void esp_websocket_client_task(void *pv) client->state = WEBSOCKET_STATE_INIT; xEventGroupClearBits(client->status_bits, STOPPED_BIT); - int read_select; + int read_select = 0; while (client->run) { + if (xSemaphoreTakeRecursive(client->lock, lock_timeout) != pdPASS) { + ESP_LOGE(TAG, "Failed to lock ws-client tasks, exitting the task..."); + break; + } switch ((int)client->state) { case WEBSOCKET_STATE_INIT: if (client->transport == NULL) { @@ -451,7 +456,6 @@ static void esp_websocket_client_task(void *pv) client->run = false; break; } - if (esp_transport_connect(client->transport, client->config->host, client->config->port, @@ -467,20 +471,14 @@ static void esp_websocket_client_task(void *pv) break; case WEBSOCKET_STATE_CONNECTED: - read_select = esp_transport_poll_read(client->transport, 1000); //Poll every 1000ms - if (read_select < 0) { - ESP_LOGE(TAG, "Network error, errorno"); - esp_websocket_client_abort_connection(client); - break; - } if (_tick_get_ms() - client->ping_tick_ms > WEBSOCKET_PING_TIMEOUT_MS) { client->ping_tick_ms = _tick_get_ms(); - // Send PING + ESP_LOGD(TAG, "Sending PING..."); esp_transport_ws_send_raw(client->transport, WS_TRANSPORT_OPCODES_PING, NULL, 0, client->config->network_timeout_ms); } if (read_select == 0) { - ESP_LOGD(TAG, "Timeout..."); - continue; + ESP_LOGV(TAG, "Read poll timeout: skipping esp_transport_read()..."); + break; } client->ping_tick_ms = _tick_get_ms(); @@ -512,9 +510,19 @@ static void esp_websocket_client_task(void *pv) client->reconnect_tick_ms = _tick_get_ms(); ESP_LOGD(TAG, "Reconnecting..."); } - vTaskDelay(client->wait_timeout_ms / 2 / portTICK_RATE_MS); break; } + xSemaphoreGiveRecursive(client->lock); + if (WEBSOCKET_STATE_CONNECTED == client->state) { + read_select = esp_transport_poll_read(client->transport, 1000); //Poll every 1000ms + if (read_select < 0) { + ESP_LOGE(TAG, "Network error: esp_transport_poll_read() returned %d, errno=%d", read_select, errno); + esp_websocket_client_abort_connection(client); + } + } else if (WEBSOCKET_STATE_WAIT_TIMEOUT == client->state) { + // waiting for reconnecting... + vTaskDelay(client->wait_timeout_ms / 2 / portTICK_RATE_MS); + } } esp_transport_close(client->transport); @@ -576,25 +584,28 @@ static int esp_websocket_client_send_with_opcode(esp_websocket_client_handle_t c { int need_write = len; int wlen = 0, widx = 0; + int ret = ESP_FAIL; if (client == NULL || data == NULL || len <= 0) { ESP_LOGE(TAG, "Invalid arguments"); return ESP_FAIL; } + if (xSemaphoreTakeRecursive(client->lock, timeout) != pdPASS) { + ESP_LOGE(TAG, "Could not lock ws-client within %d timeout", timeout); + return ESP_FAIL; + } + if (!esp_websocket_client_is_connected(client)) { ESP_LOGE(TAG, "Websocket client is not connected"); - return ESP_FAIL; + goto unlock_and_return; } if (client->transport == NULL) { ESP_LOGE(TAG, "Invalid transport"); - return ESP_FAIL; + goto unlock_and_return; } - if (xSemaphoreTake(client->lock, timeout) != pdPASS) { - return ESP_FAIL; - } while (widx < len) { if (need_write > client->buffer_size) { @@ -604,14 +615,17 @@ static int esp_websocket_client_send_with_opcode(esp_websocket_client_handle_t c // send with ws specific way and specific opcode wlen = esp_transport_ws_send_raw(client->transport, opcode, (char *)client->tx_buffer, need_write, timeout); if (wlen <= 0) { - xSemaphoreGive(client->lock); - return wlen; + ret = wlen; + ESP_LOGE(TAG, "Network error: esp_transport_write() returned %d, errno=%d", ret, errno); + goto unlock_and_return; } widx += wlen; need_write = len - widx; } - xSemaphoreGive(client->lock); - return widx; + ret = widx; +unlock_and_return: + xSemaphoreGiveRecursive(client->lock); + return ret; } bool esp_websocket_client_is_connected(esp_websocket_client_handle_t client) From a41e3383b335a131aaf9887c0743c4bbe4365742 Mon Sep 17 00:00:00 2001 From: David Cermak Date: Sat, 31 Aug 2019 16:19:21 +0200 Subject: [PATCH 16/65] examples: protocol examples which use common connection component updated to use esp_netif_init instead of tcpip_adapter in initialization code * Original commit: espressif/esp-idf@a49b934ef895690f2b5e3709340db856e27475e2 --- examples/protocols/websocket/main/websocket_example.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/protocols/websocket/main/websocket_example.c b/examples/protocols/websocket/main/websocket_example.c index f82ac9494..82e7ba385 100644 --- a/examples/protocols/websocket/main/websocket_example.c +++ b/examples/protocols/websocket/main/websocket_example.c @@ -90,7 +90,7 @@ void app_main(void) esp_log_level_set("TRANS_TCP", ESP_LOG_DEBUG); ESP_ERROR_CHECK(nvs_flash_init()); - tcpip_adapter_init(); + esp_netif_init(); ESP_ERROR_CHECK(esp_event_loop_create_default()); /* This helper function configures Wi-Fi or Ethernet, as selected in menuconfig. From 1fcc001ae82b598349ab57280a3b63fbff5fae07 Mon Sep 17 00:00:00 2001 From: David Cermak Date: Tue, 12 Nov 2019 17:42:51 +0100 Subject: [PATCH 17/65] ws_client: fix handling timeouts by websocket client. tcp-transport component did not support wait forever. this update uses value of -1 to request this state. websocket client uses timeouts in RTOS ticks. fixed recalculation to ms (including special value of -1) to use correctly tcp-transport component Closes https://github.com/espressif/esp-idf/issues/4316 * Original commit: espressif/esp-idf@e1f982921a08022ca4307900fc058ccacccd26d0 --- components/esp_websocket_client/esp_websocket_client.c | 4 ++-- .../esp_websocket_client/include/esp_websocket_client.h | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/components/esp_websocket_client/esp_websocket_client.c b/components/esp_websocket_client/esp_websocket_client.c index b509463cf..08d2ba21f 100644 --- a/components/esp_websocket_client/esp_websocket_client.c +++ b/components/esp_websocket_client/esp_websocket_client.c @@ -606,14 +606,14 @@ static int esp_websocket_client_send_with_opcode(esp_websocket_client_handle_t c goto unlock_and_return; } - while (widx < len) { if (need_write > client->buffer_size) { need_write = client->buffer_size; } memcpy(client->tx_buffer, data + widx, need_write); // send with ws specific way and specific opcode - wlen = esp_transport_ws_send_raw(client->transport, opcode, (char *)client->tx_buffer, need_write, timeout); + wlen = esp_transport_ws_send_raw(client->transport, opcode, (char *)client->tx_buffer, need_write, + (timeout==portMAX_DELAY)? -1 : timeout * portTICK_PERIOD_MS); if (wlen <= 0) { ret = wlen; ESP_LOGE(TAG, "Network error: esp_transport_write() returned %d, errno=%d", ret, errno); diff --git a/components/esp_websocket_client/include/esp_websocket_client.h b/components/esp_websocket_client/include/esp_websocket_client.h index 444343c82..3dbe1df9a 100644 --- a/components/esp_websocket_client/include/esp_websocket_client.h +++ b/components/esp_websocket_client/include/esp_websocket_client.h @@ -155,7 +155,7 @@ esp_err_t esp_websocket_client_destroy(esp_websocket_client_handle_t client); * @param[in] client The client * @param[in] data The data * @param[in] len The length - * @param[in] timeout Write data timeout + * @param[in] timeout Write data timeout in RTOS ticks * * @return * - Number of data was sent @@ -169,7 +169,7 @@ int esp_websocket_client_send(esp_websocket_client_handle_t client, const char * * @param[in] client The client * @param[in] data The data * @param[in] len The length - * @param[in] timeout Write data timeout + * @param[in] timeout Write data timeout in RTOS ticks * * @return * - Number of data was sent @@ -183,7 +183,7 @@ int esp_websocket_client_send_bin(esp_websocket_client_handle_t client, const ch * @param[in] client The client * @param[in] data The data * @param[in] len The length - * @param[in] timeout Write data timeout + * @param[in] timeout Write data timeout in RTOS ticks * * @return * - Number of data was sent From a48b0fafe873c4e3ab136642023603f421114dc3 Mon Sep 17 00:00:00 2001 From: "David N. Junod" Date: Fri, 15 Nov 2019 04:15:55 -0700 Subject: [PATCH 18/65] Add User-Agent and additional headers to esp_websocket_client Merges https://github.com/espressif/esp-idf/pull/4345 * Original commit: espressif/esp-idf@9200250f512146e348f84ebfc76f9e82e2070da2 --- .../esp_websocket_client.c | 20 +++++++++++++++++++ .../include/esp_websocket_client.h | 2 ++ 2 files changed, 22 insertions(+) diff --git a/components/esp_websocket_client/esp_websocket_client.c b/components/esp_websocket_client/esp_websocket_client.c index 08d2ba21f..031b25876 100644 --- a/components/esp_websocket_client/esp_websocket_client.c +++ b/components/esp_websocket_client/esp_websocket_client.c @@ -63,6 +63,8 @@ typedef struct { void *user_context; int network_timeout_ms; char *subprotocol; + char *user_agent; + char *headers; } websocket_config_storage_t; typedef enum { @@ -179,6 +181,16 @@ static esp_err_t esp_websocket_client_set_config(esp_websocket_client_handle_t c cfg->subprotocol = strdup(config->subprotocol); ESP_WS_CLIENT_MEM_CHECK(TAG, cfg->subprotocol, return ESP_ERR_NO_MEM); } + if (config->user_agent) { + free(cfg->user_agent); + cfg->user_agent = strdup(config->user_agent); + ESP_WS_CLIENT_MEM_CHECK(TAG, cfg->user_agent, return ESP_ERR_NO_MEM); + } + if (config->headers) { + free(cfg->headers); + cfg->headers = strdup(config->headers); + ESP_WS_CLIENT_MEM_CHECK(TAG, cfg->headers, return ESP_ERR_NO_MEM); + } cfg->network_timeout_ms = WEBSOCKET_NETWORK_TIMEOUT_MS; cfg->user_context = config->user_context; @@ -207,6 +219,8 @@ static esp_err_t esp_websocket_client_destroy_config(esp_websocket_client_handle free(cfg->username); free(cfg->password); free(cfg->subprotocol); + free(cfg->user_agent); + free(cfg->headers); memset(cfg, 0, sizeof(websocket_config_storage_t)); free(client->config); client->config = NULL; @@ -221,6 +235,12 @@ static void set_websocket_transport_optional_settings(esp_websocket_client_handl if (trans && client->config->subprotocol) { esp_transport_ws_set_subprotocol(trans, client->config->subprotocol); } + if (trans && client->config->user_agent) { + esp_transport_ws_set_user_agent(trans, client->config->user_agent); + } + if (trans && client->config->headers) { + esp_transport_ws_set_headers(trans, client->config->headers); + } } esp_websocket_client_handle_t esp_websocket_client_init(const esp_websocket_client_config_t *config) diff --git a/components/esp_websocket_client/include/esp_websocket_client.h b/components/esp_websocket_client/include/esp_websocket_client.h index 3dbe1df9a..1add2c0f8 100644 --- a/components/esp_websocket_client/include/esp_websocket_client.h +++ b/components/esp_websocket_client/include/esp_websocket_client.h @@ -92,6 +92,8 @@ typedef struct { const char *cert_pem; /*!< SSL Certification, PEM format as string, if the client requires to verify server */ esp_websocket_transport_t transport; /*!< Websocket transport type, see `esp_websocket_transport_t */ char *subprotocol; /*!< Websocket subprotocol */ + char *user_agent; /*!< Websocket user-agent */ + char *headers; /*!< Websocket additional headers */ } esp_websocket_client_config_t; /** From f21a2f32e00633a703206288d4604fb36177e05b Mon Sep 17 00:00:00 2001 From: He Yin Ling Date: Wed, 27 Nov 2019 11:58:07 +0800 Subject: [PATCH 19/65] test: update example and unit tests with new import roles: tiny_test_fw is a python package now. import it using normal way. * Original commit: espressif/esp-idf@c906e2afee9455bf55b7b163815dce69de766879 --- examples/protocols/websocket/example_test.py | 19 +++++-------------- 1 file changed, 5 insertions(+), 14 deletions(-) diff --git a/examples/protocols/websocket/example_test.py b/examples/protocols/websocket/example_test.py index 4ca10b204..8c21c6c03 100644 --- a/examples/protocols/websocket/example_test.py +++ b/examples/protocols/websocket/example_test.py @@ -1,19 +1,10 @@ import re import os -import sys -import IDF -from IDF.IDFDUT import ESP32DUT -# this is a test case write with tiny-test-fw. -# to run test cases outside tiny-test-fw, -# we need to set environment variable `TEST_FW_PATH`, -# then get and insert `TEST_FW_PATH` to sys path before import FW module -test_fw_path = os.getenv("TEST_FW_PATH") -if test_fw_path and test_fw_path not in sys.path: - sys.path.insert(0, test_fw_path) +import ttfw_idf -@IDF.idf_example_test(env_tag="Example_WIFI", ignore=True) +@ttfw_idf.idf_example_test(env_tag="Example_WIFI", ignore=True) def test_examples_protocol_websocket(env, extra_data): """ steps: | @@ -21,12 +12,12 @@ def test_examples_protocol_websocket(env, extra_data): 2. connect to ws://echo.websocket.org 3. send and receive data """ - dut1 = env.get_dut("websocket", "examples/protocols/websocket", dut_class=ESP32DUT) + dut1 = env.get_dut("websocket", "examples/protocols/websocket", dut_class=ttfw_idf.ESP32DUT) # check and log bin size binary_file = os.path.join(dut1.app.binary_path, "websocket-example.bin") bin_size = os.path.getsize(binary_file) - IDF.log_performance("websocket_bin_size", "{}KB".format(bin_size // 1024)) - IDF.check_performance("websocket_bin_size", bin_size // 1024) + ttfw_idf.log_performance("websocket_bin_size", "{}KB".format(bin_size // 1024)) + ttfw_idf.check_performance("websocket_bin_size", bin_size // 1024) # start test dut1.start_app() dut1.expect("Waiting for wifi ...") From 3b0488cfdc1fae5e141756d688f041a781860bd4 Mon Sep 17 00:00:00 2001 From: Marius Vikhammer Date: Tue, 15 Oct 2019 17:27:47 +0800 Subject: [PATCH 20/65] websocket_client: added example_test with a local websocket server - Added a example test that connects to a local python websocket server. - Added readme for websocket_client example. Closes IDF-907 * Original commit: espressif/esp-idf@67c5225c149bd8889eecb3d013e3971453924350 --- examples/protocols/websocket/README.md | 57 ++++++ examples/protocols/websocket/example_test.py | 181 +++++++++++++++++- .../websocket/main/Kconfig.projbuild | 14 ++ .../websocket/main/websocket_example.c | 45 ++++- examples/protocols/websocket/sdkconfig.ci | 3 + 5 files changed, 281 insertions(+), 19 deletions(-) create mode 100644 examples/protocols/websocket/sdkconfig.ci diff --git a/examples/protocols/websocket/README.md b/examples/protocols/websocket/README.md index 2969444fd..c434a7c13 100644 --- a/examples/protocols/websocket/README.md +++ b/examples/protocols/websocket/README.md @@ -1 +1,58 @@ # Websocket Sample application + +(See the README.md file in the upper level 'examples' directory for more information about examples.) +This example will shows how to set up and communicate over a websocket. + +## How to Use Example + +### Hardware Required + +This example can be executed on any ESP32 board, the only required interface is WiFi and connection to internet or a local server. + +### Configure the project + +* Open the project configuration menu (`idf.py menuconfig`) +* Configure Wi-Fi or Ethernet under "Example Connection Configuration" menu. See "Establishing Wi-Fi or Ethernet Connection" section in [examples/protocols/README.md](../../README.md) for more details. +* Configure the websocket endpoint URI under "Example Configuration", if "WEBSOCKET_URI_FROM_STDIN" is selected then the example application will connect to the URI it reads from stdin (used for testing) + +### Build and Flash + +Build the project and flash it to the board, then run monitor tool to view serial output: + +``` +idf.py -p PORT flash monitor +``` + +(To exit the serial monitor, type ``Ctrl-]``.) + +See the Getting Started Guide for full steps to configure and use ESP-IDF to build projects. + +## Example Output + +``` +I (482) system_api: Base MAC address is not set, read default base MAC address from BLK0 of EFUSE +I (2492) example_connect: Ethernet Link Up +I (4472) tcpip_adapter: eth ip: 192.168.2.137, mask: 255.255.255.0, gw: 192.168.2.2 +I (4472) example_connect: Connected to Ethernet +I (4472) example_connect: IPv4 address: 192.168.2.137 +I (4472) example_connect: IPv6 address: fe80:0000:0000:0000:bedd:c2ff:fed4:a92b +I (4482) WEBSOCKET: Connecting to ws://echo.websocket.org... +I (5012) WEBSOCKET: WEBSOCKET_EVENT_CONNECTED +I (5492) WEBSOCKET: Sending hello 0000 +I (6052) WEBSOCKET: WEBSOCKET_EVENT_DATA +W (6052) WEBSOCKET: Received=hello 0000 + +I (6492) WEBSOCKET: Sending hello 0001 +I (7052) WEBSOCKET: WEBSOCKET_EVENT_DATA +W (7052) WEBSOCKET: Received=hello 0001 + +I (7492) WEBSOCKET: Sending hello 0002 +I (8082) WEBSOCKET: WEBSOCKET_EVENT_DATA +W (8082) WEBSOCKET: Received=hello 0002 + +I (8492) WEBSOCKET: Sending hello 0003 +I (9152) WEBSOCKET: WEBSOCKET_EVENT_DATA +W (9162) WEBSOCKET: Received=hello 0003 + +``` + diff --git a/examples/protocols/websocket/example_test.py b/examples/protocols/websocket/example_test.py index 8c21c6c03..6308513c6 100644 --- a/examples/protocols/websocket/example_test.py +++ b/examples/protocols/websocket/example_test.py @@ -1,15 +1,158 @@ +from __future__ import print_function +from __future__ import unicode_literals import re import os +import socket +import hashlib +import base64 +from threading import Thread import ttfw_idf -@ttfw_idf.idf_example_test(env_tag="Example_WIFI", ignore=True) +def get_my_ip(): + s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + try: + # doesn't even have to be reachable + s.connect(('10.255.255.255', 1)) + IP = s.getsockname()[0] + except Exception: + IP = '127.0.0.1' + finally: + s.close() + return IP + + +# Simple Websocket server for testing purposes +class Websocket: + HEADER_LEN = 6 + + def __init__(self, port): + self.port = port + self.socket = socket.socket() + self.socket.settimeout(10.0) + + def __enter__(self): + try: + self.socket.bind(('', self.port)) + except socket.error as e: + print("Bind failed:{}".format(e)) + raise + + self.socket.listen(1) + self.server_thread = Thread(target=self.run_server) + self.server_thread.start() + + def __exit__(self, exc_type, exc_value, traceback): + self.server_thread.join() + self.socket.close() + self.conn.close() + + def run_server(self): + self.conn, address = self.socket.accept() # accept new connection + self.conn.settimeout(10.0) + print("Connection from: {}".format(address)) + + self.establish_connection() + + # Echo data until client closes connection + self.echo_data() + + def establish_connection(self): + while True: + try: + # receive data stream. it won't accept data packet greater than 1024 bytes + data = self.conn.recv(1024).decode() + if not data: + # exit if data is not received + raise + + if "Upgrade: websocket" in data and "Connection: Upgrade" in data: + self.handshake(data) + return + except socket.error as err: + print("Unable to establish a websocket connection: {}, {}".format(err)) + raise + + def handshake(self, data): + # Magic string from RFC + MAGIC_STRING = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11" + headers = data.split("\r\n") + + for header in headers: + if "Sec-WebSocket-Key" in header: + client_key = header.split()[1] + + if client_key: + resp_key = client_key + MAGIC_STRING + resp_key = base64.standard_b64encode(hashlib.sha1(resp_key.encode()).digest()) + + resp = "HTTP/1.1 101 Switching Protocols\r\n" + \ + "Upgrade: websocket\r\n" + \ + "Connection: Upgrade\r\n" + \ + "Sec-WebSocket-Accept: {}\r\n\r\n".format(resp_key.decode()) + + self.conn.send(resp.encode()) + + def echo_data(self): + while(True): + try: + header = bytearray(self.conn.recv(self.HEADER_LEN, socket.MSG_WAITALL)) + if not header: + # exit if data is not received + return + + # Remove mask bit + payload_len = ~(1 << 7) & header[1] + + payload = bytearray(self.conn.recv(payload_len, socket.MSG_WAITALL)) + frame = header + payload + + decoded_payload = self.decode_frame(frame) + + echo_frame = self.encode_frame(decoded_payload) + self.conn.send(echo_frame) + except socket.error as err: + print("Stopped echoing data: {}".format(err)) + + def decode_frame(self, frame): + # Mask out MASK bit from payload length, this len is only valid for short messages (<126) + payload_len = ~(1 << 7) & frame[1] + + mask = frame[2:self.HEADER_LEN] + + encrypted_payload = frame[self.HEADER_LEN:self.HEADER_LEN + payload_len] + payload = bytearray() + + for i in range(payload_len): + payload.append(encrypted_payload[i] ^ mask[i % 4]) + + return payload + + def encode_frame(self, payload): + # Set FIN = 1 and OP_CODE = 1 (text) + header = (1 << 7) | (1 << 0) + + frame = bytearray(header) + frame.append(len(payload)) + frame += payload + + return frame + + +def test_echo(dut): + dut.expect("WEBSOCKET_EVENT_CONNECTED") + for i in range(0, 10): + dut.expect(re.compile(r"Received=hello (\d)")) + dut.expect("Websocket Stopped") + + +@ttfw_idf.idf_example_test(env_tag="Example_WIFI") def test_examples_protocol_websocket(env, extra_data): """ - steps: | + steps: 1. join AP - 2. connect to ws://echo.websocket.org + 2. connect to uri specified in the config 3. send and receive data """ dut1 = env.get_dut("websocket", "examples/protocols/websocket", dut_class=ttfw_idf.ESP32DUT) @@ -18,15 +161,33 @@ def test_examples_protocol_websocket(env, extra_data): bin_size = os.path.getsize(binary_file) ttfw_idf.log_performance("websocket_bin_size", "{}KB".format(bin_size // 1024)) ttfw_idf.check_performance("websocket_bin_size", bin_size // 1024) + + try: + if "CONFIG_WEBSOCKET_URI_FROM_STDIN" in dut1.app.get_sdkconfig(): + uri_from_stdin = True + else: + uri = dut1.app.get_sdkconfig()["CONFIG_WEBSOCKET_URI"].strip('"') + uri_from_stdin = False + + except Exception: + print('ENV_TEST_FAILURE: Cannot find uri settings in sdkconfig') + raise + # start test dut1.start_app() - dut1.expect("Waiting for wifi ...") - dut1.expect("Connection established...", timeout=30) - dut1.expect("WEBSOCKET_EVENT_CONNECTED") - for i in range(0, 10): - dut1.expect(re.compile(r"Sending hello (\d)")) - dut1.expect(re.compile(r"Received=hello (\d)")) - dut1.expect("Websocket Stopped") + + if uri_from_stdin: + server_port = 4455 + with Websocket(server_port): + uri = "ws://{}:{}".format(get_my_ip(), server_port) + print("DUT connecting to {}".format(uri)) + dut1.expect("Please enter uri of websocket endpoint", timeout=30) + dut1.write(uri) + test_echo(dut1) + + else: + print("DUT connecting to {}".format(uri)) + test_echo(dut1) if __name__ == '__main__': diff --git a/examples/protocols/websocket/main/Kconfig.projbuild b/examples/protocols/websocket/main/Kconfig.projbuild index 7790aa248..0613b9033 100644 --- a/examples/protocols/websocket/main/Kconfig.projbuild +++ b/examples/protocols/websocket/main/Kconfig.projbuild @@ -1,7 +1,21 @@ menu "Example Configuration" + choice WEBSOCKET_URI_SOURCE + prompt "Websocket URI source" + default WEBSOCKET_URI_FROM_STRING + help + Selects the source of the URI used in the example. + + config WEBSOCKET_URI_FROM_STRING + bool "From string" + + config WEBSOCKET_URI_FROM_STDIN + bool "From stdin" + endchoice + config WEBSOCKET_URI string "Websocket endpoint URI" + depends on WEBSOCKET_URI_FROM_STRING default "ws://echo.websocket.org" help URL of websocket endpoint this example connects to and sends echo diff --git a/examples/protocols/websocket/main/websocket_example.c b/examples/protocols/websocket/main/websocket_example.c index 82e7ba385..d14b02660 100644 --- a/examples/protocols/websocket/main/websocket_example.c +++ b/examples/protocols/websocket/main/websocket_example.c @@ -25,23 +25,36 @@ #include "esp_event.h" static const char *TAG = "WEBSOCKET"; -static const char *WEBSOCKET_ECHO_ENDPOINT = CONFIG_WEBSOCKET_URI; +#if CONFIG_WEBSOCKET_URI_FROM_STDIN +static void get_string(char *line, size_t size) +{ + int count = 0; + while (count < size) { + int c = fgetc(stdin); + if (c == '\n') { + line[count] = '\0'; + break; + } else if (c > 0 && c < 127) { + line[count] = c; + ++count; + } + vTaskDelay(10 / portTICK_PERIOD_MS); + } +} + +#endif /* CONFIG_WEBSOCKET_URI_FROM_STDIN */ static void websocket_event_handler(void *handler_args, esp_event_base_t base, int32_t event_id, void *event_data) { - // esp_websocket_client_handle_t client = (esp_websocket_client_handle_t)handler_args; esp_websocket_event_data_t *data = (esp_websocket_event_data_t *)event_data; switch (event_id) { case WEBSOCKET_EVENT_CONNECTED: ESP_LOGI(TAG, "WEBSOCKET_EVENT_CONNECTED"); - - break; case WEBSOCKET_EVENT_DISCONNECTED: ESP_LOGI(TAG, "WEBSOCKET_EVENT_DISCONNECTED"); break; - case WEBSOCKET_EVENT_DATA: ESP_LOGI(TAG, "WEBSOCKET_EVENT_DATA"); ESP_LOGI(TAG, "Received opcode=%d", data->op_code); @@ -55,11 +68,23 @@ static void websocket_event_handler(void *handler_args, esp_event_base_t base, i static void websocket_app_start(void) { - ESP_LOGI(TAG, "Connectiong to %s...", WEBSOCKET_ECHO_ENDPOINT); + esp_websocket_client_config_t websocket_cfg = {}; - const esp_websocket_client_config_t websocket_cfg = { - .uri = WEBSOCKET_ECHO_ENDPOINT, // or wss://echo.websocket.org for websocket secure - }; + #if CONFIG_WEBSOCKET_URI_FROM_STDIN + char line[128]; + + ESP_LOGI(TAG, "Please enter uri of websocket endpoint"); + get_string(line, sizeof(line)); + + websocket_cfg.uri = line; + ESP_LOGI(TAG, "Endpoint uri: %s\n", line); + + #else + websocket_cfg.uri = CONFIG_WEBSOCKET_URI; + + #endif /* CONFIG_WEBSOCKET_URI_FROM_STDIN */ + + ESP_LOGI(TAG, "Connecting to %s...", websocket_cfg.uri); esp_websocket_client_handle_t client = esp_websocket_client_init(&websocket_cfg); esp_websocket_register_events(client, WEBSOCKET_EVENT_ANY, websocket_event_handler, (void *)client); @@ -75,6 +100,8 @@ static void websocket_app_start(void) } vTaskDelay(1000 / portTICK_RATE_MS); } + // Give server some time to respond before closing + vTaskDelay(3000 / portTICK_RATE_MS); esp_websocket_client_stop(client); ESP_LOGI(TAG, "Websocket Stopped"); esp_websocket_client_destroy(client); diff --git a/examples/protocols/websocket/sdkconfig.ci b/examples/protocols/websocket/sdkconfig.ci new file mode 100644 index 000000000..a0b7712a2 --- /dev/null +++ b/examples/protocols/websocket/sdkconfig.ci @@ -0,0 +1,3 @@ +CONFIG_WEBSOCKET_URI_FROM_STDIN=y +CONFIG_WEBSOCKET_URI_FROM_STRING=n + From 09453e46947c387039376684b4882b84c99252bb Mon Sep 17 00:00:00 2001 From: David Cermak Date: Fri, 29 Nov 2019 10:54:02 +0100 Subject: [PATCH 21/65] esp_netif, examples: esp_netif_init() moved into ESP_ERROR_CHECK() esp_netif_init() returns standard esp_err_t error code (unlike tcpip_adapter init), so shall be checked for the return value Also to make the initialization code more consistent. * Original commit: espressif/esp-idf@31b270238789fa64fa4483dd2c7cbaca71b1f1b7 --- examples/protocols/websocket/main/websocket_example.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/protocols/websocket/main/websocket_example.c b/examples/protocols/websocket/main/websocket_example.c index d14b02660..ffe49ff34 100644 --- a/examples/protocols/websocket/main/websocket_example.c +++ b/examples/protocols/websocket/main/websocket_example.c @@ -117,7 +117,7 @@ void app_main(void) esp_log_level_set("TRANS_TCP", ESP_LOG_DEBUG); ESP_ERROR_CHECK(nvs_flash_init()); - esp_netif_init(); + ESP_ERROR_CHECK(esp_netif_init()); ESP_ERROR_CHECK(esp_event_loop_create_default()); /* This helper function configures Wi-Fi or Ethernet, as selected in menuconfig. From a6be8e2e3dc953c2db44c871fa874983fa4aa5c1 Mon Sep 17 00:00:00 2001 From: Marius Vikhammer Date: Thu, 19 Dec 2019 17:32:14 +0800 Subject: [PATCH 22/65] websocket: added missing event data user_context was missing from websocket event data, added. Also added the websocket client handle to the event data. Removed unused event data struct. Closes: IDF-1271 * Original commit: espressif/esp-idf@7c0e3765ec009acaf2ef439e98895598b5fd9aaf --- .../esp_websocket_client.c | 4 ++++ .../include/esp_websocket_client.h | 21 +++++-------------- 2 files changed, 9 insertions(+), 16 deletions(-) diff --git a/components/esp_websocket_client/esp_websocket_client.c b/components/esp_websocket_client/esp_websocket_client.c index 031b25876..e9b412631 100644 --- a/components/esp_websocket_client/esp_websocket_client.c +++ b/components/esp_websocket_client/esp_websocket_client.c @@ -107,6 +107,10 @@ static esp_err_t esp_websocket_client_dispatch_event(esp_websocket_client_handle { esp_err_t err; esp_websocket_event_data_t event_data; + + event_data.client = client; + event_data.user_context = client->config->user_context; + event_data.data_ptr = data; event_data.data_len = data_len; event_data.op_code = client->last_opcode; diff --git a/components/esp_websocket_client/include/esp_websocket_client.h b/components/esp_websocket_client/include/esp_websocket_client.h index 1add2c0f8..0f8c64e31 100644 --- a/components/esp_websocket_client/include/esp_websocket_client.h +++ b/components/esp_websocket_client/include/esp_websocket_client.h @@ -47,9 +47,11 @@ typedef enum { * @brief Websocket event data */ typedef struct { - const char *data_ptr; /*!< Data pointer */ - int data_len; /*!< Data length */ - uint8_t op_code; /*!< Received opcode */ + const char *data_ptr; /*!< Data pointer */ + int data_len; /*!< Data length */ + uint8_t op_code; /*!< Received opcode */ + esp_websocket_client_handle_t client; /*!< esp_websocket_client_handle_t context */ + void *user_context; /*!< user_data context, from esp_websocket_client_config_t user_data */ } esp_websocket_event_data_t; /** @@ -61,19 +63,6 @@ typedef enum { WEBSOCKET_TRANSPORT_OVER_SSL, /*!< Transport over ssl */ } esp_websocket_transport_t; -/** - * @brief Websocket Client events data - */ -typedef struct { - esp_websocket_event_id_t event_id; /*!< event_id, to know the cause of the event */ - esp_websocket_client_handle_t client; /*!< esp_websocket_client_handle_t context */ - void *user_context;/*!< user_data context, from esp_websocket_client_config_t user_data */ - char *data; /*!< data of the event */ - int data_len; /*!< length of data */ -} esp_websocket_event_t; - -typedef esp_websocket_event_t* esp_websocket_event_handle_t; - /** * @brief Websocket client setup configuration */ From aec6a75d4034fd168f93de87bb787987245db837 Mon Sep 17 00:00:00 2001 From: Marius Vikhammer Date: Thu, 14 Nov 2019 18:09:38 +0800 Subject: [PATCH 23/65] tcp_transport/ws_client: websockets now correctly handle messages longer than buffer transport_ws can now be read multiple times in a row to read frames larger than the buffer. Added reporting of total payload length and offset to the user in websocket_client. Added local example test for long messages. Closes IDF-1083 * Original commit: espressif/esp-idf@ffeda3003c92102d2d5b145c9adb3ea3105cbbda --- .../esp_websocket_client.c | 59 ++++++--- .../include/esp_websocket_client.h | 16 +-- examples/protocols/websocket/example_test.py | 116 ++++++++++++++---- .../websocket/main/websocket_example.c | 59 ++++++--- 4 files changed, 177 insertions(+), 73 deletions(-) diff --git a/components/esp_websocket_client/esp_websocket_client.c b/components/esp_websocket_client/esp_websocket_client.c index e9b412631..d00bdbbe9 100644 --- a/components/esp_websocket_client/esp_websocket_client.c +++ b/components/esp_websocket_client/esp_websocket_client.c @@ -93,6 +93,8 @@ struct esp_websocket_client { char *tx_buffer; int buffer_size; ws_transport_opcodes_t last_opcode; + int payload_len; + int payload_offset; }; static uint64_t _tick_get_ms(void) @@ -101,19 +103,20 @@ static uint64_t _tick_get_ms(void) } static esp_err_t esp_websocket_client_dispatch_event(esp_websocket_client_handle_t client, - esp_websocket_event_id_t event, - const char *data, - int data_len) + esp_websocket_event_id_t event, + const char *data, + int data_len) { esp_err_t err; esp_websocket_event_data_t event_data; event_data.client = client; event_data.user_context = client->config->user_context; - event_data.data_ptr = data; event_data.data_len = data_len; event_data.op_code = client->last_opcode; + event_data.payload_len = client->payload_len; + event_data.payload_offset = client->payload_offset; if ((err = esp_event_post_to(client->event_handle, WEBSOCKET_EVENTS, event, @@ -446,10 +449,38 @@ esp_err_t esp_websocket_client_set_uri(esp_websocket_client_handle_t client, con return ESP_OK; } +static esp_err_t esp_websocket_client_recv(esp_websocket_client_handle_t client) +{ + int rlen; + client->payload_offset = 0; + do { + rlen = esp_transport_read(client->transport, client->rx_buffer, client->buffer_size, client->config->network_timeout_ms); + if (rlen < 0) { + ESP_LOGE(TAG, "Error read data"); + esp_websocket_client_abort_connection(client); + return ESP_FAIL; + } + client->payload_len = esp_transport_ws_get_read_payload_len(client->transport); + client->last_opcode = esp_transport_ws_get_read_opcode(client->transport); + + esp_websocket_client_dispatch_event(client, WEBSOCKET_EVENT_DATA, client->rx_buffer, rlen); + + client->payload_offset += rlen; + } while (client->payload_offset < client->payload_len); + + // if a PING message received -> send out the PONG, this will not work for PING messages with payload longer than buffer len + if (client->last_opcode == WS_TRANSPORT_OPCODES_PING) { + const char *data = (client->payload_len == 0) ? NULL : client->rx_buffer; + esp_transport_ws_send_raw(client->transport, WS_TRANSPORT_OPCODES_PONG, data, client->payload_len, + client->config->network_timeout_ms); + } + + return ESP_OK; +} + static void esp_websocket_client_task(void *pv) { const int lock_timeout = portMAX_DELAY; - int rlen; esp_websocket_client_handle_t client = (esp_websocket_client_handle_t) pv; client->run = true; @@ -506,22 +537,11 @@ static void esp_websocket_client_task(void *pv) } client->ping_tick_ms = _tick_get_ms(); - rlen = esp_transport_read(client->transport, client->rx_buffer, client->buffer_size, client->config->network_timeout_ms); - if (rlen < 0) { - ESP_LOGE(TAG, "Error read data"); + if (esp_websocket_client_recv(client) == ESP_FAIL) { + ESP_LOGE(TAG, "Error receive data"); esp_websocket_client_abort_connection(client); break; } - if (rlen >= 0) { - client->last_opcode = esp_transport_ws_get_read_opcode(client->transport); - esp_websocket_client_dispatch_event(client, WEBSOCKET_EVENT_DATA, client->rx_buffer, rlen); - // if a PING message received -> send out the PONG - if (client->last_opcode == WS_TRANSPORT_OPCODES_PING) { - const char *data = (rlen == 0) ? NULL : client->rx_buffer; - esp_transport_ws_send_raw(client->transport, WS_TRANSPORT_OPCODES_PONG, data, rlen, - client->config->network_timeout_ms); - } - } break; case WEBSOCKET_STATE_WAIT_TIMEOUT: @@ -663,7 +683,8 @@ bool esp_websocket_client_is_connected(esp_websocket_client_handle_t client) esp_err_t esp_websocket_register_events(esp_websocket_client_handle_t client, esp_websocket_event_id_t event, esp_event_handler_t event_handler, - void* event_handler_arg) { + void *event_handler_arg) +{ if (client == NULL) { return ESP_ERR_INVALID_ARG; } diff --git a/components/esp_websocket_client/include/esp_websocket_client.h b/components/esp_websocket_client/include/esp_websocket_client.h index 0f8c64e31..ae8cc8a4b 100644 --- a/components/esp_websocket_client/include/esp_websocket_client.h +++ b/components/esp_websocket_client/include/esp_websocket_client.h @@ -27,7 +27,7 @@ extern "C" { #endif -typedef struct esp_websocket_client* esp_websocket_client_handle_t; +typedef struct esp_websocket_client *esp_websocket_client_handle_t; ESP_EVENT_DECLARE_BASE(WEBSOCKET_EVENTS); // declaration of the task events family @@ -47,11 +47,13 @@ typedef enum { * @brief Websocket event data */ typedef struct { - const char *data_ptr; /*!< Data pointer */ - int data_len; /*!< Data length */ - uint8_t op_code; /*!< Received opcode */ - esp_websocket_client_handle_t client; /*!< esp_websocket_client_handle_t context */ - void *user_context; /*!< user_data context, from esp_websocket_client_config_t user_data */ + const char *data_ptr; /*!< Data pointer */ + int data_len; /*!< Data length */ + uint8_t op_code; /*!< Received opcode */ + esp_websocket_client_handle_t client; /*!< esp_websocket_client_handle_t context */ + void *user_context; /*!< user_data context, from esp_websocket_client_config_t user_data */ + int payload_len; /*!< Total payload length, payloads exceeding buffer will be posted through multiple events */ + int payload_offset; /*!< Actual offset for the data associated with this event */ } esp_websocket_event_data_t; /** @@ -205,7 +207,7 @@ bool esp_websocket_client_is_connected(esp_websocket_client_handle_t client); esp_err_t esp_websocket_register_events(esp_websocket_client_handle_t client, esp_websocket_event_id_t event, esp_event_handler_t event_handler, - void* event_handler_arg); + void *event_handler_arg); #ifdef __cplusplus } diff --git a/examples/protocols/websocket/example_test.py b/examples/protocols/websocket/example_test.py index 6308513c6..03063b116 100644 --- a/examples/protocols/websocket/example_test.py +++ b/examples/protocols/websocket/example_test.py @@ -3,10 +3,13 @@ from __future__ import unicode_literals import re import os import socket +import select import hashlib import base64 -from threading import Thread - +import queue +import random +import string +from threading import Thread, Event import ttfw_idf @@ -30,7 +33,10 @@ class Websocket: def __init__(self, port): self.port = port self.socket = socket.socket() + self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) self.socket.settimeout(10.0) + self.send_q = queue.Queue() + self.shutdown = Event() def __enter__(self): try: @@ -43,23 +49,27 @@ class Websocket: self.server_thread = Thread(target=self.run_server) self.server_thread.start() + return self + def __exit__(self, exc_type, exc_value, traceback): + self.shutdown.set() self.server_thread.join() self.socket.close() self.conn.close() def run_server(self): self.conn, address = self.socket.accept() # accept new connection - self.conn.settimeout(10.0) + self.socket.settimeout(10.0) + print("Connection from: {}".format(address)) self.establish_connection() - - # Echo data until client closes connection - self.echo_data() + print("WS established") + # Handle connection until client closes it, will echo any data received and send data from send_q queue + self.handle_conn() def establish_connection(self): - while True: + while not self.shutdown.is_set(): try: # receive data stream. it won't accept data packet greater than 1024 bytes data = self.conn.recv(1024).decode() @@ -70,6 +80,7 @@ class Websocket: if "Upgrade: websocket" in data and "Connection: Upgrade" in data: self.handshake(data) return + except socket.error as err: print("Unable to establish a websocket connection: {}, {}".format(err)) raise @@ -94,26 +105,46 @@ class Websocket: self.conn.send(resp.encode()) - def echo_data(self): - while(True): + def handle_conn(self): + while not self.shutdown.is_set(): + r,w,e = select.select([self.conn], [], [], 1) try: - header = bytearray(self.conn.recv(self.HEADER_LEN, socket.MSG_WAITALL)) - if not header: - # exit if data is not received - return + if self.conn in r: + self.echo_data() - # Remove mask bit - payload_len = ~(1 << 7) & header[1] + if not self.send_q.empty(): + self._send_data_(self.send_q.get()) - payload = bytearray(self.conn.recv(payload_len, socket.MSG_WAITALL)) - frame = header + payload - - decoded_payload = self.decode_frame(frame) - - echo_frame = self.encode_frame(decoded_payload) - self.conn.send(echo_frame) except socket.error as err: print("Stopped echoing data: {}".format(err)) + raise + + def echo_data(self): + header = bytearray(self.conn.recv(self.HEADER_LEN, socket.MSG_WAITALL)) + if not header: + # exit if socket closed by peer + return + + # Remove mask bit + payload_len = ~(1 << 7) & header[1] + + payload = bytearray(self.conn.recv(payload_len, socket.MSG_WAITALL)) + + if not payload: + # exit if socket closed by peer + return + frame = header + payload + + decoded_payload = self.decode_frame(frame) + print("Sending echo...") + self._send_data_(decoded_payload) + + def _send_data_(self, data): + frame = self.encode_frame(data) + self.conn.send(frame) + + def send_data(self, data): + self.send_q.put(data.encode()) def decode_frame(self, frame): # Mask out MASK bit from payload length, this len is only valid for short messages (<126) @@ -133,8 +164,18 @@ class Websocket: # Set FIN = 1 and OP_CODE = 1 (text) header = (1 << 7) | (1 << 0) - frame = bytearray(header) - frame.append(len(payload)) + frame = bytearray([header]) + payload_len = len(payload) + + # If payload len is longer than 125 then the next 16 bits are used to encode length + if payload_len > 125: + frame.append(126) + frame.append(payload_len >> 8) + frame.append(0xFF & payload_len) + + else: + frame.append(payload_len) + frame += payload return frame @@ -143,8 +184,27 @@ class Websocket: def test_echo(dut): dut.expect("WEBSOCKET_EVENT_CONNECTED") for i in range(0, 10): - dut.expect(re.compile(r"Received=hello (\d)")) - dut.expect("Websocket Stopped") + dut.expect(re.compile(r"Received=hello (\d)"), timeout=30) + print("All echos received") + + +def test_recv_long_msg(dut, websocket, msg_len, repeats): + send_msg = ''.join(random.choice(string.ascii_uppercase + string.ascii_lowercase + string.digits) for _ in range(msg_len)) + + for _ in range(repeats): + websocket.send_data(send_msg) + + recv_msg = '' + while len(recv_msg) < msg_len: + # Filter out color encoding + match = dut.expect(re.compile(r"Received=([a-zA-Z0-9]*).*\n"), timeout=30)[0] + recv_msg += match + + if recv_msg == send_msg: + print("Sent message and received message are equal") + else: + raise ValueError("DUT received string do not match sent string, \nexpected: {}\nwith length {}\ + \nreceived: {}\nwith length {}".format(send_msg, len(send_msg), recv_msg, len(recv_msg))) @ttfw_idf.idf_example_test(env_tag="Example_WIFI") @@ -178,12 +238,14 @@ def test_examples_protocol_websocket(env, extra_data): if uri_from_stdin: server_port = 4455 - with Websocket(server_port): + with Websocket(server_port) as ws: uri = "ws://{}:{}".format(get_my_ip(), server_port) print("DUT connecting to {}".format(uri)) dut1.expect("Please enter uri of websocket endpoint", timeout=30) dut1.write(uri) test_echo(dut1) + # Message length should exceed DUT's buffer size to test fragmentation, default is 1024 byte + test_recv_long_msg(dut1, ws, 2000, 3) else: print("DUT connecting to {}".format(uri)) diff --git a/examples/protocols/websocket/main/websocket_example.c b/examples/protocols/websocket/main/websocket_example.c index ffe49ff34..57381ebf2 100644 --- a/examples/protocols/websocket/main/websocket_example.c +++ b/examples/protocols/websocket/main/websocket_example.c @@ -17,15 +17,26 @@ #include "freertos/FreeRTOS.h" #include "freertos/task.h" +#include "freertos/semphr.h" #include "freertos/event_groups.h" - #include "esp_log.h" #include "esp_websocket_client.h" #include "esp_event.h" +#define NO_DATA_TIMEOUT_SEC 10 + static const char *TAG = "WEBSOCKET"; +static TimerHandle_t shutdown_signal_timer; +static SemaphoreHandle_t shutdown_sema; + +static void shutdown_signaler(TimerHandle_t xTimer) +{ + ESP_LOGI(TAG, "No data received for %d seconds, signaling shutdown", NO_DATA_TIMEOUT_SEC); + xSemaphoreGive(shutdown_sema); +} + #if CONFIG_WEBSOCKET_URI_FROM_STDIN static void get_string(char *line, size_t size) { @@ -49,20 +60,23 @@ static void websocket_event_handler(void *handler_args, esp_event_base_t base, i { esp_websocket_event_data_t *data = (esp_websocket_event_data_t *)event_data; switch (event_id) { - case WEBSOCKET_EVENT_CONNECTED: - ESP_LOGI(TAG, "WEBSOCKET_EVENT_CONNECTED"); - break; - case WEBSOCKET_EVENT_DISCONNECTED: - ESP_LOGI(TAG, "WEBSOCKET_EVENT_DISCONNECTED"); - break; - case WEBSOCKET_EVENT_DATA: - ESP_LOGI(TAG, "WEBSOCKET_EVENT_DATA"); - ESP_LOGI(TAG, "Received opcode=%d", data->op_code); - ESP_LOGW(TAG, "Received=%.*s\r\n", data->data_len, (char*)data->data_ptr); - break; - case WEBSOCKET_EVENT_ERROR: - ESP_LOGI(TAG, "WEBSOCKET_EVENT_ERROR"); - break; + case WEBSOCKET_EVENT_CONNECTED: + ESP_LOGI(TAG, "WEBSOCKET_EVENT_CONNECTED"); + break; + case WEBSOCKET_EVENT_DISCONNECTED: + ESP_LOGI(TAG, "WEBSOCKET_EVENT_DISCONNECTED"); + break; + case WEBSOCKET_EVENT_DATA: + ESP_LOGI(TAG, "WEBSOCKET_EVENT_DATA"); + ESP_LOGI(TAG, "Received opcode=%d", data->op_code); + ESP_LOGW(TAG, "Received=%.*s", data->data_len, (char *)data->data_ptr); + ESP_LOGW(TAG, "Total payload length=%d, data_len=%d, current payload offset=%d\r\n", data->payload_len, data->data_len, data->payload_offset); + + xTimerReset(shutdown_signal_timer, portMAX_DELAY); + break; + case WEBSOCKET_EVENT_ERROR: + ESP_LOGI(TAG, "WEBSOCKET_EVENT_ERROR"); + break; } } @@ -70,7 +84,11 @@ static void websocket_app_start(void) { esp_websocket_client_config_t websocket_cfg = {}; - #if CONFIG_WEBSOCKET_URI_FROM_STDIN + shutdown_signal_timer = xTimerCreate("Websocket shutdown timer", NO_DATA_TIMEOUT_SEC * 1000 / portTICK_PERIOD_MS, + pdFALSE, NULL, shutdown_signaler); + shutdown_sema = xSemaphoreCreateBinary(); + +#if CONFIG_WEBSOCKET_URI_FROM_STDIN char line[128]; ESP_LOGI(TAG, "Please enter uri of websocket endpoint"); @@ -79,10 +97,10 @@ static void websocket_app_start(void) websocket_cfg.uri = line; ESP_LOGI(TAG, "Endpoint uri: %s\n", line); - #else +#else websocket_cfg.uri = CONFIG_WEBSOCKET_URI; - #endif /* CONFIG_WEBSOCKET_URI_FROM_STDIN */ +#endif /* CONFIG_WEBSOCKET_URI_FROM_STDIN */ ESP_LOGI(TAG, "Connecting to %s...", websocket_cfg.uri); @@ -90,6 +108,7 @@ static void websocket_app_start(void) esp_websocket_register_events(client, WEBSOCKET_EVENT_ANY, websocket_event_handler, (void *)client); esp_websocket_client_start(client); + xTimerStart(shutdown_signal_timer, portMAX_DELAY); char data[32]; int i = 0; while (i < 10) { @@ -100,8 +119,8 @@ static void websocket_app_start(void) } vTaskDelay(1000 / portTICK_RATE_MS); } - // Give server some time to respond before closing - vTaskDelay(3000 / portTICK_RATE_MS); + + xSemaphoreTake(shutdown_sema, portMAX_DELAY); esp_websocket_client_stop(client); ESP_LOGI(TAG, "Websocket Stopped"); esp_websocket_client_destroy(client); From 17281a515edbe1ce0d9a70f3a14bae0006a5a447 Mon Sep 17 00:00:00 2001 From: Konstantin Kondrashov Date: Thu, 6 Feb 2020 14:00:18 +0800 Subject: [PATCH 24/65] esp32: add implementation of esp_timer based on TG0 LAC timer Closes: IDF-979 * Original commit: espressif/esp-idf@739eb05bb97736b70507e7ebcfee58e670672d23 --- components/esp_websocket_client/CMakeLists.txt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/components/esp_websocket_client/CMakeLists.txt b/components/esp_websocket_client/CMakeLists.txt index f366a278d..de17d6fdd 100644 --- a/components/esp_websocket_client/CMakeLists.txt +++ b/components/esp_websocket_client/CMakeLists.txt @@ -1,3 +1,4 @@ idf_component_register(SRCS "esp_websocket_client.c" INCLUDE_DIRS "include" - REQUIRES lwip esp-tls tcp_transport nghttp) + REQUIRES lwip esp-tls tcp_transport nghttp + PRIV_REQUIRES esp_timer) From fae2343b19cd93338ca60160cf94d8d05996b03d Mon Sep 17 00:00:00 2001 From: Angus Gratton Date: Wed, 13 Nov 2019 11:46:16 +0800 Subject: [PATCH 25/65] docs: add new top-level docs builder that builds docs for a single chip * Original commit: espressif/esp-idf@e6211c7864505f3a66687b402faeb06fa7377f25 --- docs/en/api-reference/protocols/esp_websocket_client.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/en/api-reference/protocols/esp_websocket_client.rst b/docs/en/api-reference/protocols/esp_websocket_client.rst index cd4db2413..240d0df59 100644 --- a/docs/en/api-reference/protocols/esp_websocket_client.rst +++ b/docs/en/api-reference/protocols/esp_websocket_client.rst @@ -66,5 +66,5 @@ Simple WebSocket example that uses esp_websocket_client to establish a websocket API Reference ------------- -.. include:: /_build/inc/esp_websocket_client.inc +.. include-build-file:: inc/esp_websocket_client.inc From 2b6022c85d9c2bbadb32fd544382dcd26d4d8ec7 Mon Sep 17 00:00:00 2001 From: David Cermak Date: Thu, 5 Mar 2020 16:26:26 +0100 Subject: [PATCH 26/65] examples: websocket example to send textual data with esp_websocket_client_send_text() Closes https://github.com/espressif/esp-idf/issues/4640 * Original commit: espressif/esp-idf@e5650d1ed8b20ec65fec3d3ac0809479632ab0fc --- examples/protocols/websocket/main/websocket_example.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/protocols/websocket/main/websocket_example.c b/examples/protocols/websocket/main/websocket_example.c index 57381ebf2..e424066d2 100644 --- a/examples/protocols/websocket/main/websocket_example.c +++ b/examples/protocols/websocket/main/websocket_example.c @@ -115,7 +115,7 @@ static void websocket_app_start(void) if (esp_websocket_client_is_connected(client)) { int len = sprintf(data, "hello %04d", i++); ESP_LOGI(TAG, "Sending %s", data); - esp_websocket_client_send(client, data, len, portMAX_DELAY); + esp_websocket_client_send_text(client, data, len, portMAX_DELAY); } vTaskDelay(1000 / portTICK_RATE_MS); } From 2b044f2434ff5010d0c80983f7b8eebaa88f6b55 Mon Sep 17 00:00:00 2001 From: Roland Dobai Date: Fri, 28 Feb 2020 16:12:11 +0100 Subject: [PATCH 27/65] Add multi-target support for performance tests * Original commit: espressif/esp-idf@15884eccf293dde1916dbc663d6a53758ff5dab0 --- examples/protocols/websocket/example_test.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/protocols/websocket/example_test.py b/examples/protocols/websocket/example_test.py index 03063b116..ae36bb0ac 100644 --- a/examples/protocols/websocket/example_test.py +++ b/examples/protocols/websocket/example_test.py @@ -220,7 +220,7 @@ def test_examples_protocol_websocket(env, extra_data): binary_file = os.path.join(dut1.app.binary_path, "websocket-example.bin") bin_size = os.path.getsize(binary_file) ttfw_idf.log_performance("websocket_bin_size", "{}KB".format(bin_size // 1024)) - ttfw_idf.check_performance("websocket_bin_size", bin_size // 1024) + ttfw_idf.check_performance("websocket_bin_size", bin_size // 1024, dut1.TARGET) try: if "CONFIG_WEBSOCKET_URI_FROM_STDIN" in dut1.app.get_sdkconfig(): From 42920d7fb5b673dbf204b3a30b3abebaef1c651e Mon Sep 17 00:00:00 2001 From: Elia Bieri Date: Thu, 2 Apr 2020 22:48:58 +0200 Subject: [PATCH 28/65] Fix format string in websocket example * Original commit: espressif/esp-idf@5288a797ef82428bfbbfb1d26db995001dc28ae6 --- examples/protocols/websocket/example_test.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/protocols/websocket/example_test.py b/examples/protocols/websocket/example_test.py index ae36bb0ac..6dfdccf5d 100644 --- a/examples/protocols/websocket/example_test.py +++ b/examples/protocols/websocket/example_test.py @@ -82,7 +82,7 @@ class Websocket: return except socket.error as err: - print("Unable to establish a websocket connection: {}, {}".format(err)) + print("Unable to establish a websocket connection: {}".format(err)) raise def handshake(self, data): From 7a5b2d5a7d816c1b2c9dcd31760a54d9eb1f1309 Mon Sep 17 00:00:00 2001 From: David Cermak Date: Wed, 25 Mar 2020 21:22:25 +0100 Subject: [PATCH 29/65] ws_client: fix fragmented send setting proper opcodes Previous implementation violated the RFC by having both the actual opcode and WS_FIN flag set for all fragments of a message. Fixed by setting the opcode only for the first fragment and WS_FIN for the last one Closes IDFGH-2938 Closes https://github.com/espressif/esp-idf/issues/4974 * Original commit: espressif/esp-idf@14992e62c5573d8b6076281f16b4fe11d6bc8f87 --- .../esp_websocket_client/esp_websocket_client.c | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/components/esp_websocket_client/esp_websocket_client.c b/components/esp_websocket_client/esp_websocket_client.c index d00bdbbe9..0e959971f 100644 --- a/components/esp_websocket_client/esp_websocket_client.c +++ b/components/esp_websocket_client/esp_websocket_client.c @@ -471,7 +471,7 @@ static esp_err_t esp_websocket_client_recv(esp_websocket_client_handle_t client) // if a PING message received -> send out the PONG, this will not work for PING messages with payload longer than buffer len if (client->last_opcode == WS_TRANSPORT_OPCODES_PING) { const char *data = (client->payload_len == 0) ? NULL : client->rx_buffer; - esp_transport_ws_send_raw(client->transport, WS_TRANSPORT_OPCODES_PONG, data, client->payload_len, + esp_transport_ws_send_raw(client->transport, WS_TRANSPORT_OPCODES_PONG | WS_TRANSPORT_OPCODES_FIN, data, client->payload_len, client->config->network_timeout_ms); } @@ -529,7 +529,7 @@ static void esp_websocket_client_task(void *pv) if (_tick_get_ms() - client->ping_tick_ms > WEBSOCKET_PING_TIMEOUT_MS) { client->ping_tick_ms = _tick_get_ms(); ESP_LOGD(TAG, "Sending PING..."); - esp_transport_ws_send_raw(client->transport, WS_TRANSPORT_OPCODES_PING, NULL, 0, client->config->network_timeout_ms); + esp_transport_ws_send_raw(client->transport, WS_TRANSPORT_OPCODES_PING | WS_TRANSPORT_OPCODES_FIN, NULL, 0, client->config->network_timeout_ms); } if (read_select == 0) { ESP_LOGV(TAG, "Read poll timeout: skipping esp_transport_read()..."); @@ -649,22 +649,26 @@ static int esp_websocket_client_send_with_opcode(esp_websocket_client_handle_t c ESP_LOGE(TAG, "Invalid transport"); goto unlock_and_return; } - + uint32_t current_opcode = opcode; while (widx < len) { if (need_write > client->buffer_size) { need_write = client->buffer_size; + } else { + current_opcode |= WS_TRANSPORT_OPCODES_FIN; } memcpy(client->tx_buffer, data + widx, need_write); // send with ws specific way and specific opcode - wlen = esp_transport_ws_send_raw(client->transport, opcode, (char *)client->tx_buffer, need_write, + wlen = esp_transport_ws_send_raw(client->transport, current_opcode, (char *)client->tx_buffer, need_write, (timeout==portMAX_DELAY)? -1 : timeout * portTICK_PERIOD_MS); if (wlen <= 0) { ret = wlen; ESP_LOGE(TAG, "Network error: esp_transport_write() returned %d, errno=%d", ret, errno); goto unlock_and_return; } + current_opcode = 0; widx += wlen; need_write = len - widx; + } ret = widx; unlock_and_return: From f8e3ba78134f6125947c09ee3d3da26f3e54c381 Mon Sep 17 00:00:00 2001 From: Marius Vikhammer Date: Mon, 25 May 2020 14:59:22 +0800 Subject: [PATCH 30/65] websocket client: the client now aborts the connection if send fails. Closes IDF-1744 * Original commit: espressif/esp-idf@6bebfc84f3ed9c96bcb331fd0d5b0bbb26ce07a4 --- components/esp_websocket_client/esp_websocket_client.c | 1 + 1 file changed, 1 insertion(+) diff --git a/components/esp_websocket_client/esp_websocket_client.c b/components/esp_websocket_client/esp_websocket_client.c index 0e959971f..6fea58c17 100644 --- a/components/esp_websocket_client/esp_websocket_client.c +++ b/components/esp_websocket_client/esp_websocket_client.c @@ -663,6 +663,7 @@ static int esp_websocket_client_send_with_opcode(esp_websocket_client_handle_t c if (wlen <= 0) { ret = wlen; ESP_LOGE(TAG, "Network error: esp_transport_write() returned %d, errno=%d", ret, errno); + esp_websocket_client_abort_connection(client); goto unlock_and_return; } current_opcode = 0; From b71c49c27775cfa6c8b418cbb1bef20d1e5fac45 Mon Sep 17 00:00:00 2001 From: Marius Vikhammer Date: Fri, 22 May 2020 20:34:20 +0800 Subject: [PATCH 31/65] websocket: add configurable timeout for PONG not received Closes IDF-1744 * Original commit: espressif/esp-idf@0049385850daebfe2222c8f0526b896ffaeacdd9 --- .../esp_websocket_client.c | 30 +++++++++++++++++++ .../include/esp_websocket_client.h | 5 +++- 2 files changed, 34 insertions(+), 1 deletion(-) diff --git a/components/esp_websocket_client/esp_websocket_client.c b/components/esp_websocket_client/esp_websocket_client.c index 6fea58c17..99cc65d8a 100644 --- a/components/esp_websocket_client/esp_websocket_client.c +++ b/components/esp_websocket_client/esp_websocket_client.c @@ -39,6 +39,7 @@ static const char *TAG = "WEBSOCKET_CLIENT"; #define WEBSOCKET_NETWORK_TIMEOUT_MS (10*1000) #define WEBSOCKET_PING_TIMEOUT_MS (10*1000) #define WEBSOCKET_EVENT_QUEUE_SIZE (1) +#define WEBSOCKET_PINGPONG_TIMEOUT_SEC (120) #define ESP_WS_CLIENT_MEM_CHECK(TAG, a, action) if (!(a)) { \ ESP_LOGE(TAG,"%s:%d (%s): %s", __FILE__, __LINE__, __FUNCTION__, "Memory exhausted"); \ @@ -65,6 +66,7 @@ typedef struct { char *subprotocol; char *user_agent; char *headers; + int pingpong_timeout_sec; } websocket_config_storage_t; typedef enum { @@ -84,9 +86,11 @@ struct esp_websocket_client { uint64_t keepalive_tick_ms; uint64_t reconnect_tick_ms; uint64_t ping_tick_ms; + uint64_t pingpong_tick_ms; int wait_timeout_ms; int auto_reconnect; bool run; + bool wait_for_pong_resp; EventGroupHandle_t status_bits; xSemaphoreHandle lock; char *rx_buffer; @@ -206,6 +210,13 @@ static esp_err_t esp_websocket_client_set_config(esp_websocket_client_handle_t c cfg->auto_reconnect = false; } + if (config->disable_pingpong_discon){ + cfg->pingpong_timeout_sec = 0; + } else if (config->pingpong_timeout_sec) { + cfg->pingpong_timeout_sec = config->pingpong_timeout_sec; + } else { + cfg->pingpong_timeout_sec = WEBSOCKET_PINGPONG_TIMEOUT_SEC; + } return ESP_OK; } @@ -335,6 +346,7 @@ esp_websocket_client_handle_t esp_websocket_client_init(const esp_websocket_clie client->keepalive_tick_ms = _tick_get_ms(); client->reconnect_tick_ms = _tick_get_ms(); client->ping_tick_ms = _tick_get_ms(); + client->wait_for_pong_resp = false; int buffer_size = config->buffer_size; if (buffer_size <= 0) { @@ -474,6 +486,9 @@ static esp_err_t esp_websocket_client_recv(esp_websocket_client_handle_t client) esp_transport_ws_send_raw(client->transport, WS_TRANSPORT_OPCODES_PONG | WS_TRANSPORT_OPCODES_FIN, data, client->payload_len, client->config->network_timeout_ms); } + else if (client->last_opcode == WS_TRANSPORT_OPCODES_PONG) { + client->wait_for_pong_resp = false; + } return ESP_OK; } @@ -522,6 +537,7 @@ static void esp_websocket_client_task(void *pv) ESP_LOGD(TAG, "Transport connected to %s://%s:%d", client->config->scheme, client->config->host, client->config->port); client->state = WEBSOCKET_STATE_CONNECTED; + client->wait_for_pong_resp = false; esp_websocket_client_dispatch_event(client, WEBSOCKET_EVENT_CONNECTED, NULL, 0); break; @@ -530,7 +546,21 @@ static void esp_websocket_client_task(void *pv) client->ping_tick_ms = _tick_get_ms(); ESP_LOGD(TAG, "Sending PING..."); esp_transport_ws_send_raw(client->transport, WS_TRANSPORT_OPCODES_PING | WS_TRANSPORT_OPCODES_FIN, NULL, 0, client->config->network_timeout_ms); + + if (!client->wait_for_pong_resp && client->config->pingpong_timeout_sec) { + client->pingpong_tick_ms = _tick_get_ms(); + client->wait_for_pong_resp = true; + } } + + if ( _tick_get_ms() - client->pingpong_tick_ms > client->config->pingpong_timeout_sec*1000 ) { + if (client->wait_for_pong_resp) { + ESP_LOGE(TAG, "Error, no PONG received for more than %d seconds after PING", client->config->pingpong_timeout_sec); + esp_websocket_client_abort_connection(client); + break; + } + } + if (read_select == 0) { ESP_LOGV(TAG, "Read poll timeout: skipping esp_transport_read()..."); break; diff --git a/components/esp_websocket_client/include/esp_websocket_client.h b/components/esp_websocket_client/include/esp_websocket_client.h index ae8cc8a4b..5a0e52e0a 100644 --- a/components/esp_websocket_client/include/esp_websocket_client.h +++ b/components/esp_websocket_client/include/esp_websocket_client.h @@ -85,6 +85,9 @@ typedef struct { char *subprotocol; /*!< Websocket subprotocol */ char *user_agent; /*!< Websocket user-agent */ char *headers; /*!< Websocket additional headers */ + int pingpong_timeout_sec; /*!< Period before connection is aborted due to no PONGs received */ + bool disable_pingpong_discon; /*!< Disable auto-disconnect due to no PONG received within pingpong_timeout_sec */ + } esp_websocket_client_config_t; /** @@ -185,7 +188,7 @@ int esp_websocket_client_send_bin(esp_websocket_client_handle_t client, const ch int esp_websocket_client_send_text(esp_websocket_client_handle_t client, const char *data, int len, TickType_t timeout); /** - * @brief Check the WebSocket connection status + * @brief Check the WebSocket client connection state * * @param[in] client The client handle * From 5f2a50f09f31ed324102abfc66b5bb1a9993c459 Mon Sep 17 00:00:00 2001 From: Marius Vikhammer Date: Tue, 7 Apr 2020 12:58:07 +0800 Subject: [PATCH 32/65] doc/websocket: updates the API reference for ESP WebSocket Client * Original commit: espressif/esp-idf@09f240c1e121b9a2f231560798d9fccd89e0c17b --- .../protocols/esp_websocket_client.rst | 115 +++++++++++++----- 1 file changed, 86 insertions(+), 29 deletions(-) diff --git a/docs/en/api-reference/protocols/esp_websocket_client.rst b/docs/en/api-reference/protocols/esp_websocket_client.rst index 240d0df59..f54a9ab02 100644 --- a/docs/en/api-reference/protocols/esp_websocket_client.rst +++ b/docs/en/api-reference/protocols/esp_websocket_client.rst @@ -3,11 +3,11 @@ ESP WebSocket Client Overview -------- -The ESP WebSocket client is an implementation of `WebSocket protocol client `_ for ESP32 +The ESP WebSocket client is an implementation of `WebSocket protocol client `_ for {IDF_TARGET_NAME} Features -------- - * supports WebSocket over TCP, SSL with mbedtls + * Supports WebSocket over TCP, TLS with mbedtls * Easy to setup with URI * Multiple instances (Multiple clients in one application) @@ -19,48 +19,105 @@ URI - Supports ``ws``, ``wss`` schemes - WebSocket samples: - - ``ws://websocket.org``: WebSocket over TCP, default port 80 - - ``wss://websocket.org``: WebSocket over SSL, default port 443 - -- Minimal configurations: + - ``ws://echo.websocket.org``: WebSocket over TCP, default port 80 + - ``wss://echo.websocket.org``: WebSocket over SSL, default port 443 + +Minimal configurations: + +.. code:: c + + const esp_websocket_client_config_t ws_cfg = { + .uri = "ws://echo.websocket.org", + }; + +The WebSocket client supports the use of both path and query in the URI. Sample: + +.. code:: c + + const esp_websocket_client_config_t ws_cfg = { + .uri = "ws://echo.websocket.org/connectionhandler?id=104", + }; + + +If there are any options related to the URI in +:cpp:type:`esp_websocket_client_config_t`, the option defined by the URI will be +overridden. Sample: + +.. code:: c + + const esp_websocket_client_config_t ws_cfg = { + .uri = "ws://echo.websocket.org:123", + .port = 4567, + }; + //WebSocket client will connect to websocket.org using port 4567 + +TLS +^^^ + +Configuration: + +.. code:: c + + const esp_websocket_client_config_t ws_cfg = { + .uri = "wss://echo.websocket.org", + .cert_pem = (const char *)websocket_org_pem_start, + }; + +.. note:: If you want to verify the server, then you need to provide a certificate in PEM format, and provide to ``cert_pem`` in :cpp:type:`websocket_client_config_t`. If no certficate is provided then the TLS connection will to default not requiring verification. + +PEM certificate for this example could be extracted from an openssl `s_client` command connecting to websocket.org. +In case a host operating system has `openssl` and `sed` packages installed, one could execute the following command to download and save the root or intermediate root certificate to a file (Note for Windows users: Both Linux like environment or Windows native packages may be used). +``` +echo "" | openssl s_client -showcerts -connect websocket.org:443 | sed -n "1,/Root/d; /BEGIN/,/END/p" | openssl x509 -outform PEM >websocket_org.pem +``` + +This command will extract the second certificate in the chain and save it as a pem-file. + +Subprotocol +^^^^^^^^^^^ + +The subprotocol field in the config struct can be used to request a subprotocol .. code:: c const esp_websocket_client_config_t ws_cfg = { .uri = "ws://websocket.org", + .subprotocol = "soap", }; -- If there are any options related to the URI in - ``esp_websocket_client_config_t``, the option defined by the URI will be - overridden. Sample: +.. note:: The client is indifferent to the subprotocol field in the server response and will accept the connection no matter what the server replies. + +For more options on :cpp:type:`esp_websocket_client_config_t`, please refer to API reference below + +Events +------ +* `WEBSOCKET_EVENT_CONNECTED`: The client has successfully established a connection to the server. The client is now ready to send and receive data. Contains no event data. +* `WEBSOCKET_EVENT_DISCONNECTED`: The client has aborted the connection due to the transport layer failing to read data, e.g. because the server is unavailable. Contains no event data. +* `WEBSOCKET_EVENT_DATA`: The client has successfully received and parsed a WebSocket frame. The event data contains a pointer to the payload data, the length of the payload data as well as the opcode of the received frame. A message may be fragmented into multiple events if the length exceeds the buffer size. This event will also be posted for non-payload frames, e.g. pong or connection close frames. +* `WEBSOCKET_EVENT_ERROR`: Not used in the current implementation of the client. + +If the client handle is needed in the event handler it can be accessed through the pointer passed to the event handler: .. code:: c - const esp_websocket_client_config_t ws_cfg = { - .uri = "ws://websocket.org:123", - .port = 4567, - }; - //WebSocket client will connect to websocket.org using port 4567 + esp_websocket_client_handle_t client = (esp_websocket_client_handle_t)handler_args; -SSL -^^^ -- Get certificate from server, example: ``websocket.org`` - ``openssl s_client -showcerts -connect websocket.org:443 /dev/null|openssl x509 -outform PEM >websocket_org.pem`` -- Configuration: - -.. code:: cpp - - const esp_websocket_client_config_t ws_cfg = { - .uri = "wss://websocket.org", - .cert_pem = (const char *)websocket_org_pem_start, - }; - -For more options on ``esp_websocket_client_config_t``, please refer to API reference below +Limitations and Known Issues +---------------------------- +* The client is able to request the use of a subprotocol from the server during the handshake, but does not do any subprotocol related checks on the response from the server. Application Example ------------------- -Simple WebSocket example that uses esp_websocket_client to establish a websocket connection and send/receive data with the `websocket.org `_ Server: :example:`protocols/websocket`. +A simple WebSocket example that uses esp_websocket_client to establish a websocket connection and send/receive data with the `websocket.org `_ server can be found here: :example:`protocols/websocket`. + +Sending Text Data +^^^^^^^^^^^^^^^^^ +The WebSocket client supports sending data as a text data frame, which informs the application layer that the payload data is text data encoded as UTF-8. Example: + +.. code:: cpp + + esp_websocket_client_send_text(client, data, len, portMAX_DELAY); API Reference From 6ab0aea8417a5e21a8368b7025f97efed3aa60df Mon Sep 17 00:00:00 2001 From: xutao Date: Wed, 15 Jul 2020 21:13:21 +0800 Subject: [PATCH 33/65] websocket_client : fix some issues for websocket client 1. will post twice disconnect event when read error 2. will block `timeout` times when set disable_auto_connect 3. When `esp_websocket_client_stop` before `esp_websocket_client_send*`, if the `esp_websocket_client_send*` fails, the status will change to 'WEBSOCKET_STATE_WAIT_TIMEOUT', and the next `esp_websocket_client_start` will fail forever * Original commit: espressif/esp-idf@341e48057349d92c3b8afe5f9c0fcd0aa47500b0 --- .../esp_websocket_client/esp_websocket_client.c | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/components/esp_websocket_client/esp_websocket_client.c b/components/esp_websocket_client/esp_websocket_client.c index 99cc65d8a..82520c624 100644 --- a/components/esp_websocket_client/esp_websocket_client.c +++ b/components/esp_websocket_client/esp_websocket_client.c @@ -46,6 +46,11 @@ static const char *TAG = "WEBSOCKET_CLIENT"; action; \ } +#define ESP_WS_CLIENT_STATE_CHECK(TAG, a, action) if ((a->state) < WEBSOCKET_STATE_INIT) { \ + ESP_LOGE(TAG,"%s:%d (%s): %s", __FILE__, __LINE__, __FUNCTION__, "Websocket already stop"); \ + action; \ + } + const static int STOPPED_BIT = BIT0; ESP_EVENT_DEFINE_BASE(WEBSOCKET_EVENTS); @@ -134,11 +139,15 @@ static esp_err_t esp_websocket_client_dispatch_event(esp_websocket_client_handle static esp_err_t esp_websocket_client_abort_connection(esp_websocket_client_handle_t client) { + ESP_WS_CLIENT_STATE_CHECK(TAG, client, return ESP_FAIL); esp_transport_close(client->transport); - client->wait_timeout_ms = WEBSOCKET_RECONNECT_TIMEOUT_MS; - client->reconnect_tick_ms = _tick_get_ms(); + + if (client->config->auto_reconnect) { + client->wait_timeout_ms = WEBSOCKET_RECONNECT_TIMEOUT_MS; + client->reconnect_tick_ms = _tick_get_ms(); + ESP_LOGI(TAG, "Reconnect after %d ms", client->wait_timeout_ms); + } client->state = WEBSOCKET_STATE_WAIT_TIMEOUT; - ESP_LOGI(TAG, "Reconnect after %d ms", client->wait_timeout_ms); esp_websocket_client_dispatch_event(client, WEBSOCKET_EVENT_DISCONNECTED, NULL, 0); return ESP_OK; } @@ -469,7 +478,6 @@ static esp_err_t esp_websocket_client_recv(esp_websocket_client_handle_t client) rlen = esp_transport_read(client->transport, client->rx_buffer, client->buffer_size, client->config->network_timeout_ms); if (rlen < 0) { ESP_LOGE(TAG, "Error read data"); - esp_websocket_client_abort_connection(client); return ESP_FAIL; } client->payload_len = esp_transport_ws_get_read_payload_len(client->transport); From 01b4f640d96f77c2bda2d2b4c17d7e011facf3be Mon Sep 17 00:00:00 2001 From: Chen Yi Qun Date: Wed, 29 Jul 2020 20:46:37 +0800 Subject: [PATCH 34/65] driver, http_client, web_socket, tcp_transport: remove __FILE__ from log messages __FILE__ macro in the error messages adds full paths to the production binarys, remove __FILE__ from the ESP_LOGE. Closes https://github.com/espressif/esp-idf/issues/5637 Merges https://github.com/espressif/esp-idf/pull/5638 * Original commit: espressif/esp-idf@caaf62bdad965e6b58bba74171986414057f6757 --- components/esp_websocket_client/esp_websocket_client.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/esp_websocket_client/esp_websocket_client.c b/components/esp_websocket_client/esp_websocket_client.c index 82520c624..5059e36dd 100644 --- a/components/esp_websocket_client/esp_websocket_client.c +++ b/components/esp_websocket_client/esp_websocket_client.c @@ -42,7 +42,7 @@ static const char *TAG = "WEBSOCKET_CLIENT"; #define WEBSOCKET_PINGPONG_TIMEOUT_SEC (120) #define ESP_WS_CLIENT_MEM_CHECK(TAG, a, action) if (!(a)) { \ - ESP_LOGE(TAG,"%s:%d (%s): %s", __FILE__, __LINE__, __FUNCTION__, "Memory exhausted"); \ + ESP_LOGE(TAG,"%s(%d): %s", __FUNCTION__, __LINE__, "Memory exhausted"); \ action; \ } From 1455bc03051d750f1dc621e69f6415fa7e9086d3 Mon Sep 17 00:00:00 2001 From: David Cermak Date: Fri, 17 Jul 2020 17:59:05 +0200 Subject: [PATCH 35/65] ws_client: Added support for close frame, closing connection gracefully * Original commit: espressif/esp-idf@b213f2c6d3d78ba3a95005e3206d4ce370b8a649 --- .../esp_websocket_client.c | 92 ++++++++++++++++--- .../include/esp_websocket_client.h | 30 ++++++ .../websocket/main/websocket_example.c | 8 +- 3 files changed, 116 insertions(+), 14 deletions(-) diff --git a/components/esp_websocket_client/esp_websocket_client.c b/components/esp_websocket_client/esp_websocket_client.c index 5059e36dd..23bdd3c6a 100644 --- a/components/esp_websocket_client/esp_websocket_client.c +++ b/components/esp_websocket_client/esp_websocket_client.c @@ -52,6 +52,8 @@ static const char *TAG = "WEBSOCKET_CLIENT"; } const static int STOPPED_BIT = BIT0; +const static int CLOSING_BIT = BIT1; // Indicates that a close frame received from server + // and we are waiting for the "Reset by Peer" from the server ESP_EVENT_DEFINE_BASE(WEBSOCKET_EVENTS); @@ -477,6 +479,11 @@ static esp_err_t esp_websocket_client_recv(esp_websocket_client_handle_t client) do { rlen = esp_transport_read(client->transport, client->rx_buffer, client->buffer_size, client->config->network_timeout_ms); if (rlen < 0) { + if (CLOSING_BIT & xEventGroupGetBits(client->status_bits)) { + client->run = false; + client->state = WEBSOCKET_STATE_UNKNOW; + return ESP_OK; + } ESP_LOGE(TAG, "Error read data"); return ESP_FAIL; } @@ -493,9 +500,10 @@ static esp_err_t esp_websocket_client_recv(esp_websocket_client_handle_t client) const char *data = (client->payload_len == 0) ? NULL : client->rx_buffer; esp_transport_ws_send_raw(client->transport, WS_TRANSPORT_OPCODES_PONG | WS_TRANSPORT_OPCODES_FIN, data, client->payload_len, client->config->network_timeout_ms); - } - else if (client->last_opcode == WS_TRANSPORT_OPCODES_PONG) { + } else if (client->last_opcode == WS_TRANSPORT_OPCODES_PONG) { client->wait_for_pong_resp = false; + } else if (client->last_opcode == WS_TRANSPORT_OPCODES_CLOSE) { + xEventGroupSetBits(client->status_bits, CLOSING_BIT); } return ESP_OK; @@ -520,7 +528,7 @@ static void esp_websocket_client_task(void *pv) } client->state = WEBSOCKET_STATE_INIT; - xEventGroupClearBits(client->status_bits, STOPPED_BIT); + xEventGroupClearBits(client->status_bits, STOPPED_BIT | CLOSING_BIT); int read_select = 0; while (client->run) { if (xSemaphoreTakeRecursive(client->lock, lock_timeout) != pdPASS) { @@ -598,6 +606,11 @@ static void esp_websocket_client_task(void *pv) if (WEBSOCKET_STATE_CONNECTED == client->state) { read_select = esp_transport_poll_read(client->transport, 1000); //Poll every 1000ms if (read_select < 0) { + if (CLOSING_BIT & xEventGroupGetBits(client->status_bits)) { + client->run = false; + client->state = WEBSOCKET_STATE_UNKNOW; + break; + } ESP_LOGE(TAG, "Network error: esp_transport_poll_read() returned %d, errno=%d", read_select, errno); esp_websocket_client_abort_connection(client); } @@ -626,7 +639,7 @@ esp_err_t esp_websocket_client_start(esp_websocket_client_handle_t client) ESP_LOGE(TAG, "Error create websocket task"); return ESP_FAIL; } - xEventGroupClearBits(client->status_bits, STOPPED_BIT); + xEventGroupClearBits(client->status_bits, STOPPED_BIT | CLOSING_BIT); return ESP_OK; } @@ -645,30 +658,85 @@ esp_err_t esp_websocket_client_stop(esp_websocket_client_handle_t client) return ESP_OK; } -static int esp_websocket_client_send_with_opcode(esp_websocket_client_handle_t client, ws_transport_opcodes_t opcode, const char *data, int len, TickType_t timeout); +static int esp_websocket_client_send_with_opcode(esp_websocket_client_handle_t client, ws_transport_opcodes_t opcode, uint8_t *data, int len, TickType_t timeout); + +int esp_websocket_client_send_close(esp_websocket_client_handle_t client, int code, const char *additional_data, int total_len, TickType_t timeout) +{ + uint8_t *close_status_data = NULL; + // RFC6455#section-5.5.1: The Close frame MAY contain a body (indicated by total_len >= 2) + if (total_len >= 2) { + close_status_data = calloc(1, total_len); + // RFC6455#section-5.5.1: The first two bytes of the body MUST be a 2-byte representing a status + uint16_t *code_network_order = (uint16_t *) close_status_data; + *code_network_order = htons(code); + memcpy(close_status_data + 2, additional_data, total_len - 2); + } + int ret = esp_websocket_client_send_with_opcode(client, WS_TRANSPORT_OPCODES_CLOSE, close_status_data, total_len, timeout); + free(close_status_data); + return ret; +} + + +static esp_err_t esp_websocket_client_close_with_optional_body(esp_websocket_client_handle_t client, bool send_body, int code, const char *data, int len, TickType_t timeout) +{ + if (client == NULL) { + return ESP_ERR_INVALID_ARG; + } + if (!client->run) { + ESP_LOGW(TAG, "Client was not started"); + return ESP_FAIL; + } + + if (send_body) { + esp_websocket_client_send_close(client, code, data, len + 2, portMAX_DELAY); // len + 2 -> always sending the code + } else { + esp_websocket_client_send_close(client, 0, NULL, 0, portMAX_DELAY); // only opcode frame + } + + if (STOPPED_BIT & xEventGroupWaitBits(client->status_bits, STOPPED_BIT, false, true, timeout)) { + return ESP_OK; + } + + // If could not close gracefully within timeout, stop the client and disconnect + client->run = false; + xEventGroupWaitBits(client->status_bits, STOPPED_BIT, false, true, portMAX_DELAY); + client->state = WEBSOCKET_STATE_UNKNOW; + return ESP_OK; +} + +esp_err_t esp_websocket_client_close_with_code(esp_websocket_client_handle_t client, int code, const char *data, int len, TickType_t timeout) +{ + return esp_websocket_client_close_with_optional_body(client, true, code, data, len, timeout); +} + +esp_err_t esp_websocket_client_close(esp_websocket_client_handle_t client, TickType_t timeout) +{ + return esp_websocket_client_close_with_optional_body(client, false, 0, NULL, 0, timeout); +} int esp_websocket_client_send_text(esp_websocket_client_handle_t client, const char *data, int len, TickType_t timeout) { - return esp_websocket_client_send_with_opcode(client, WS_TRANSPORT_OPCODES_TEXT, data, len, timeout); + return esp_websocket_client_send_with_opcode(client, WS_TRANSPORT_OPCODES_TEXT, (uint8_t *)data, len, timeout); } int esp_websocket_client_send(esp_websocket_client_handle_t client, const char *data, int len, TickType_t timeout) { - return esp_websocket_client_send_with_opcode(client, WS_TRANSPORT_OPCODES_BINARY, data, len, timeout); + return esp_websocket_client_send_with_opcode(client, WS_TRANSPORT_OPCODES_BINARY, (uint8_t *)data, len, timeout); } int esp_websocket_client_send_bin(esp_websocket_client_handle_t client, const char *data, int len, TickType_t timeout) { - return esp_websocket_client_send_with_opcode(client, WS_TRANSPORT_OPCODES_BINARY, data, len, timeout); + return esp_websocket_client_send_with_opcode(client, WS_TRANSPORT_OPCODES_BINARY, (uint8_t *)data, len, timeout); } -static int esp_websocket_client_send_with_opcode(esp_websocket_client_handle_t client, ws_transport_opcodes_t opcode, const char *data, int len, TickType_t timeout) +static int esp_websocket_client_send_with_opcode(esp_websocket_client_handle_t client, ws_transport_opcodes_t opcode, uint8_t *data, int len, TickType_t timeout) { int need_write = len; int wlen = 0, widx = 0; int ret = ESP_FAIL; - if (client == NULL || data == NULL || len <= 0) { + if (client == NULL || len < 0 || + (opcode != WS_TRANSPORT_OPCODES_CLOSE && (data == NULL || len <= 0))) { ESP_LOGE(TAG, "Invalid arguments"); return ESP_FAIL; } @@ -688,7 +756,7 @@ static int esp_websocket_client_send_with_opcode(esp_websocket_client_handle_t c goto unlock_and_return; } uint32_t current_opcode = opcode; - while (widx < len) { + while (widx < len || current_opcode) { // allow for sending "current_opcode" only massage with len==0 if (need_write > client->buffer_size) { need_write = client->buffer_size; } else { @@ -698,7 +766,7 @@ static int esp_websocket_client_send_with_opcode(esp_websocket_client_handle_t c // send with ws specific way and specific opcode wlen = esp_transport_ws_send_raw(client->transport, current_opcode, (char *)client->tx_buffer, need_write, (timeout==portMAX_DELAY)? -1 : timeout * portTICK_PERIOD_MS); - if (wlen <= 0) { + if (wlen < 0 || (wlen == 0 && need_write != 0)) { ret = wlen; ESP_LOGE(TAG, "Network error: esp_transport_write() returned %d, errno=%d", ret, errno); esp_websocket_client_abort_connection(client); diff --git a/components/esp_websocket_client/include/esp_websocket_client.h b/components/esp_websocket_client/include/esp_websocket_client.h index 5a0e52e0a..68946c61c 100644 --- a/components/esp_websocket_client/include/esp_websocket_client.h +++ b/components/esp_websocket_client/include/esp_websocket_client.h @@ -187,6 +187,36 @@ int esp_websocket_client_send_bin(esp_websocket_client_handle_t client, const ch */ int esp_websocket_client_send_text(esp_websocket_client_handle_t client, const char *data, int len, TickType_t timeout); +/** + * @brief Close the WebSocket connection in a clean way + * + * Sequence of clean close initiated by client: + * * Client sends CLOSE frame + * * Client waits until server echos the CLOSE frame + * * Client waits until server closes the connection + * * Client is stopped the same way as by the `esp_websocket_client_stop()` + * + * @param[in] client The client + * @param[in] timeout Timeout in RTOS ticks for waiting + * + * @return esp_err_t + */ +esp_err_t esp_websocket_client_close(esp_websocket_client_handle_t client, TickType_t timeout); + +/** + * @brief Close the WebSocket connection in a clean way with custom code/data + * Closing sequence is the same as for esp_websocket_client_close() + * + * @param[in] client The client + * @param[in] code Close status code as defined in RFC6455 section-7.4 + * @param[in] data Additional data to closing message + * @param[in] len The length of the additional data + * @param[in] timeout Timeout in RTOS ticks for waiting + * + * @return esp_err_t + */ +esp_err_t esp_websocket_client_close_with_code(esp_websocket_client_handle_t client, int code, const char *data, int len, TickType_t timeout); + /** * @brief Check the WebSocket client connection state * diff --git a/examples/protocols/websocket/main/websocket_example.c b/examples/protocols/websocket/main/websocket_example.c index e424066d2..eb3db80ce 100644 --- a/examples/protocols/websocket/main/websocket_example.c +++ b/examples/protocols/websocket/main/websocket_example.c @@ -69,7 +69,11 @@ static void websocket_event_handler(void *handler_args, esp_event_base_t base, i case WEBSOCKET_EVENT_DATA: ESP_LOGI(TAG, "WEBSOCKET_EVENT_DATA"); ESP_LOGI(TAG, "Received opcode=%d", data->op_code); - ESP_LOGW(TAG, "Received=%.*s", data->data_len, (char *)data->data_ptr); + if (data->op_code == 0x08 && data->data_len == 2) { + ESP_LOGW(TAG, "Received closed message with code=%d", 256*data->data_ptr[0] + data->data_ptr[1]); + } else { + ESP_LOGW(TAG, "Received=%.*s", data->data_len, (char *)data->data_ptr); + } ESP_LOGW(TAG, "Total payload length=%d, data_len=%d, current payload offset=%d\r\n", data->payload_len, data->data_len, data->payload_offset); xTimerReset(shutdown_signal_timer, portMAX_DELAY); @@ -121,7 +125,7 @@ static void websocket_app_start(void) } xSemaphoreTake(shutdown_sema, portMAX_DELAY); - esp_websocket_client_stop(client); + esp_websocket_client_close(client, portMAX_DELAY); ESP_LOGI(TAG, "Websocket Stopped"); esp_websocket_client_destroy(client); } From 6d12d06605f06cd6e6fd7c8757ce121cacc829cc Mon Sep 17 00:00:00 2001 From: David Cermak Date: Fri, 17 Jul 2020 17:59:05 +0200 Subject: [PATCH 36/65] tcp_transport: Added internal API for underlying socket, used for custom select on connection end for WS Internal tcp_transport functions could now use custom socket operations. This is used for WebSocket transport, when we typically wait for clean connection closure, i.e. selecting for read/error with expected errno or recv size=0 while socket readable (=connection terminated by FIN flag) * Original commit: espressif/esp-idf@5e9f8b52e7a87371370205a387b2d94e5ac6cbf9 --- .../esp_websocket_client.c | 99 ++++++++++++------- .../include/esp_websocket_client.h | 7 +- 2 files changed, 69 insertions(+), 37 deletions(-) diff --git a/components/esp_websocket_client/esp_websocket_client.c b/components/esp_websocket_client/esp_websocket_client.c index 23bdd3c6a..bd670d93e 100644 --- a/components/esp_websocket_client/esp_websocket_client.c +++ b/components/esp_websocket_client/esp_websocket_client.c @@ -52,8 +52,8 @@ static const char *TAG = "WEBSOCKET_CLIENT"; } const static int STOPPED_BIT = BIT0; -const static int CLOSING_BIT = BIT1; // Indicates that a close frame received from server - // and we are waiting for the "Reset by Peer" from the server +const static int CLOSE_FRAME_SENT_BIT = BIT1; // Indicates that a close frame was sent by the client + // and we are waiting for the server to continue with clean close ESP_EVENT_DEFINE_BASE(WEBSOCKET_EVENTS); @@ -82,6 +82,7 @@ typedef enum { WEBSOCKET_STATE_INIT, WEBSOCKET_STATE_CONNECTED, WEBSOCKET_STATE_WAIT_TIMEOUT, + WEBSOCKET_STATE_CLOSING, } websocket_client_state_t; struct esp_websocket_client { @@ -479,11 +480,6 @@ static esp_err_t esp_websocket_client_recv(esp_websocket_client_handle_t client) do { rlen = esp_transport_read(client->transport, client->rx_buffer, client->buffer_size, client->config->network_timeout_ms); if (rlen < 0) { - if (CLOSING_BIT & xEventGroupGetBits(client->status_bits)) { - client->run = false; - client->state = WEBSOCKET_STATE_UNKNOW; - return ESP_OK; - } ESP_LOGE(TAG, "Error read data"); return ESP_FAIL; } @@ -503,12 +499,17 @@ static esp_err_t esp_websocket_client_recv(esp_websocket_client_handle_t client) } else if (client->last_opcode == WS_TRANSPORT_OPCODES_PONG) { client->wait_for_pong_resp = false; } else if (client->last_opcode == WS_TRANSPORT_OPCODES_CLOSE) { - xEventGroupSetBits(client->status_bits, CLOSING_BIT); + ESP_LOGD(TAG, "Received close frame"); + client->state = WEBSOCKET_STATE_CLOSING; } return ESP_OK; } +static int esp_websocket_client_send_with_opcode(esp_websocket_client_handle_t client, ws_transport_opcodes_t opcode, const uint8_t *data, int len, TickType_t timeout); + +static int esp_websocket_client_send_close(esp_websocket_client_handle_t client, int code, const char *additional_data, int total_len, TickType_t timeout); + static void esp_websocket_client_task(void *pv) { const int lock_timeout = portMAX_DELAY; @@ -528,7 +529,7 @@ static void esp_websocket_client_task(void *pv) } client->state = WEBSOCKET_STATE_INIT; - xEventGroupClearBits(client->status_bits, STOPPED_BIT | CLOSING_BIT); + xEventGroupClearBits(client->status_bits, STOPPED_BIT | CLOSE_FRAME_SENT_BIT); int read_select = 0; while (client->run) { if (xSemaphoreTakeRecursive(client->lock, lock_timeout) != pdPASS) { @@ -558,22 +559,25 @@ static void esp_websocket_client_task(void *pv) break; case WEBSOCKET_STATE_CONNECTED: - if (_tick_get_ms() - client->ping_tick_ms > WEBSOCKET_PING_TIMEOUT_MS) { - client->ping_tick_ms = _tick_get_ms(); - ESP_LOGD(TAG, "Sending PING..."); - esp_transport_ws_send_raw(client->transport, WS_TRANSPORT_OPCODES_PING | WS_TRANSPORT_OPCODES_FIN, NULL, 0, client->config->network_timeout_ms); + if ((CLOSE_FRAME_SENT_BIT & xEventGroupGetBits(client->status_bits)) == 0) { // only send and check for PING + // if closing hasn't been initiated + if (_tick_get_ms() - client->ping_tick_ms > WEBSOCKET_PING_TIMEOUT_MS) { + client->ping_tick_ms = _tick_get_ms(); + ESP_LOGD(TAG, "Sending PING..."); + esp_transport_ws_send_raw(client->transport, WS_TRANSPORT_OPCODES_PING | WS_TRANSPORT_OPCODES_FIN, NULL, 0, client->config->network_timeout_ms); - if (!client->wait_for_pong_resp && client->config->pingpong_timeout_sec) { - client->pingpong_tick_ms = _tick_get_ms(); - client->wait_for_pong_resp = true; + if (!client->wait_for_pong_resp && client->config->pingpong_timeout_sec) { + client->pingpong_tick_ms = _tick_get_ms(); + client->wait_for_pong_resp = true; + } } - } - if ( _tick_get_ms() - client->pingpong_tick_ms > client->config->pingpong_timeout_sec*1000 ) { - if (client->wait_for_pong_resp) { - ESP_LOGE(TAG, "Error, no PONG received for more than %d seconds after PING", client->config->pingpong_timeout_sec); - esp_websocket_client_abort_connection(client); - break; + if ( _tick_get_ms() - client->pingpong_tick_ms > client->config->pingpong_timeout_sec*1000 ) { + if (client->wait_for_pong_resp) { + ESP_LOGE(TAG, "Error, no PONG received for more than %d seconds after PING", client->config->pingpong_timeout_sec); + esp_websocket_client_abort_connection(client); + break; + } } } @@ -601,22 +605,43 @@ static void esp_websocket_client_task(void *pv) ESP_LOGD(TAG, "Reconnecting..."); } break; + case WEBSOCKET_STATE_CLOSING: + // if closing not initiated by the client echo the close message back + if ((CLOSE_FRAME_SENT_BIT & xEventGroupGetBits(client->status_bits)) == 0) { + ESP_LOGD(TAG, "Closing initiated by the server, sending close frame"); + esp_transport_ws_send_raw(client->transport, WS_TRANSPORT_OPCODES_CLOSE | WS_TRANSPORT_OPCODES_FIN, NULL, 0, client->config->network_timeout_ms); + xEventGroupSetBits(client->status_bits, CLOSE_FRAME_SENT_BIT); + } + break; + default: + ESP_LOGD(TAG, "Client run iteration in a default state: %d", client->state); + break; } xSemaphoreGiveRecursive(client->lock); if (WEBSOCKET_STATE_CONNECTED == client->state) { read_select = esp_transport_poll_read(client->transport, 1000); //Poll every 1000ms if (read_select < 0) { - if (CLOSING_BIT & xEventGroupGetBits(client->status_bits)) { - client->run = false; - client->state = WEBSOCKET_STATE_UNKNOW; - break; - } ESP_LOGE(TAG, "Network error: esp_transport_poll_read() returned %d, errno=%d", read_select, errno); esp_websocket_client_abort_connection(client); } } else if (WEBSOCKET_STATE_WAIT_TIMEOUT == client->state) { // waiting for reconnecting... vTaskDelay(client->wait_timeout_ms / 2 / portTICK_RATE_MS); + } else if (WEBSOCKET_STATE_CLOSING == client->state && + (CLOSE_FRAME_SENT_BIT & xEventGroupGetBits(client->status_bits))) { + ESP_LOGD(TAG, " Waiting for TCP connection to be closed by the server"); + int ret = esp_transport_ws_poll_connection_closed(client->transport, 1000); + if (ret == 0) { + // still waiting + break; + } + if (ret < 0) { + ESP_LOGW(TAG, "Connection terminated while waiting for clean TCP close"); + } + client->run = false; + client->state = WEBSOCKET_STATE_UNKNOW; + esp_websocket_client_dispatch_event(client, WEBSOCKET_EVENT_CLOSED, NULL, 0); + break; } } @@ -639,7 +664,7 @@ esp_err_t esp_websocket_client_start(esp_websocket_client_handle_t client) ESP_LOGE(TAG, "Error create websocket task"); return ESP_FAIL; } - xEventGroupClearBits(client->status_bits, STOPPED_BIT | CLOSING_BIT); + xEventGroupClearBits(client->status_bits, STOPPED_BIT | CLOSE_FRAME_SENT_BIT); return ESP_OK; } @@ -658,14 +683,13 @@ esp_err_t esp_websocket_client_stop(esp_websocket_client_handle_t client) return ESP_OK; } -static int esp_websocket_client_send_with_opcode(esp_websocket_client_handle_t client, ws_transport_opcodes_t opcode, uint8_t *data, int len, TickType_t timeout); - -int esp_websocket_client_send_close(esp_websocket_client_handle_t client, int code, const char *additional_data, int total_len, TickType_t timeout) +static int esp_websocket_client_send_close(esp_websocket_client_handle_t client, int code, const char *additional_data, int total_len, TickType_t timeout) { uint8_t *close_status_data = NULL; // RFC6455#section-5.5.1: The Close frame MAY contain a body (indicated by total_len >= 2) if (total_len >= 2) { close_status_data = calloc(1, total_len); + ESP_WS_CLIENT_MEM_CHECK(TAG, close_status_data, return -1); // RFC6455#section-5.5.1: The first two bytes of the body MUST be a 2-byte representing a status uint16_t *code_network_order = (uint16_t *) close_status_data; *code_network_order = htons(code); @@ -693,6 +717,9 @@ static esp_err_t esp_websocket_client_close_with_optional_body(esp_websocket_cli esp_websocket_client_send_close(client, 0, NULL, 0, portMAX_DELAY); // only opcode frame } + // Set closing bit to prevent from sending PING frames while connected + xEventGroupSetBits(client->status_bits, CLOSE_FRAME_SENT_BIT); + if (STOPPED_BIT & xEventGroupWaitBits(client->status_bits, STOPPED_BIT, false, true, timeout)) { return ESP_OK; } @@ -716,20 +743,20 @@ esp_err_t esp_websocket_client_close(esp_websocket_client_handle_t client, TickT int esp_websocket_client_send_text(esp_websocket_client_handle_t client, const char *data, int len, TickType_t timeout) { - return esp_websocket_client_send_with_opcode(client, WS_TRANSPORT_OPCODES_TEXT, (uint8_t *)data, len, timeout); + return esp_websocket_client_send_with_opcode(client, WS_TRANSPORT_OPCODES_TEXT, (const uint8_t *)data, len, timeout); } int esp_websocket_client_send(esp_websocket_client_handle_t client, const char *data, int len, TickType_t timeout) { - return esp_websocket_client_send_with_opcode(client, WS_TRANSPORT_OPCODES_BINARY, (uint8_t *)data, len, timeout); + return esp_websocket_client_send_with_opcode(client, WS_TRANSPORT_OPCODES_BINARY, (const uint8_t *)data, len, timeout); } int esp_websocket_client_send_bin(esp_websocket_client_handle_t client, const char *data, int len, TickType_t timeout) { - return esp_websocket_client_send_with_opcode(client, WS_TRANSPORT_OPCODES_BINARY, (uint8_t *)data, len, timeout); + return esp_websocket_client_send_with_opcode(client, WS_TRANSPORT_OPCODES_BINARY, (const uint8_t *)data, len, timeout); } -static int esp_websocket_client_send_with_opcode(esp_websocket_client_handle_t client, ws_transport_opcodes_t opcode, uint8_t *data, int len, TickType_t timeout) +static int esp_websocket_client_send_with_opcode(esp_websocket_client_handle_t client, ws_transport_opcodes_t opcode, const uint8_t *data, int len, TickType_t timeout) { int need_write = len; int wlen = 0, widx = 0; @@ -756,7 +783,7 @@ static int esp_websocket_client_send_with_opcode(esp_websocket_client_handle_t c goto unlock_and_return; } uint32_t current_opcode = opcode; - while (widx < len || current_opcode) { // allow for sending "current_opcode" only massage with len==0 + while (widx < len || current_opcode) { // allow for sending "current_opcode" only message with len==0 if (need_write > client->buffer_size) { need_write = client->buffer_size; } else { diff --git a/components/esp_websocket_client/include/esp_websocket_client.h b/components/esp_websocket_client/include/esp_websocket_client.h index 68946c61c..44488cdb0 100644 --- a/components/esp_websocket_client/include/esp_websocket_client.h +++ b/components/esp_websocket_client/include/esp_websocket_client.h @@ -40,6 +40,7 @@ typedef enum { WEBSOCKET_EVENT_CONNECTED, /*!< Once the Websocket has been connected to the server, no data exchange has been performed */ WEBSOCKET_EVENT_DISCONNECTED, /*!< The connection has been disconnected */ WEBSOCKET_EVENT_DATA, /*!< When receiving data from the server, possibly multiple portions of the packet */ + WEBSOCKET_EVENT_CLOSED, /*!< The connection has been closed cleanly */ WEBSOCKET_EVENT_MAX } esp_websocket_event_id_t; @@ -125,7 +126,11 @@ esp_err_t esp_websocket_client_set_uri(esp_websocket_client_handle_t client, con esp_err_t esp_websocket_client_start(esp_websocket_client_handle_t client); /** - * @brief Close the WebSocket connection + * @brief Stops the WebSocket connection without websocket closing handshake + * + * This API stops ws client and closes TCP connection directly without sending + * close frames. It is a good practice to close the connection in a clean way + * using esp_websocket_client_close(). * * @param[in] client The client * From fda070ba39d4c634b9bfb00553d648da731a2e88 Mon Sep 17 00:00:00 2001 From: David Cermak Date: Tue, 21 Jul 2020 16:04:25 +0200 Subject: [PATCH 37/65] ws_client tests: Updated example test to use WebsSocket package Added a new test for closing connection with close frames * Original commit: espressif/esp-idf@44c553fd1479bf990cf0d96dfce894c50900324e --- examples/protocols/websocket/example_test.py | 186 ++++--------------- 1 file changed, 38 insertions(+), 148 deletions(-) diff --git a/examples/protocols/websocket/example_test.py b/examples/protocols/websocket/example_test.py index 6dfdccf5d..0bb085d26 100644 --- a/examples/protocols/websocket/example_test.py +++ b/examples/protocols/websocket/example_test.py @@ -3,12 +3,10 @@ from __future__ import unicode_literals import re import os import socket -import select -import hashlib -import base64 -import queue import random import string +from SimpleWebSocketServer import SimpleWebSocketServer, WebSocket +from tiny_test_fw import Utility from threading import Thread, Event import ttfw_idf @@ -26,159 +24,45 @@ def get_my_ip(): return IP +class TestEcho(WebSocket): + + def handleMessage(self): + self.sendMessage(self.data) + print('Server sent: {}'.format(self.data)) + + def handleConnected(self): + print('Connection from: {}'.format(self.address)) + + def handleClose(self): + print('{} closed the connection'.format(self.address)) + + # Simple Websocket server for testing purposes -class Websocket: - HEADER_LEN = 6 +class Websocket(object): + + def send_data(self, data): + for nr, conn in self.server.connections.items(): + conn.sendMessage(data) + + def run(self): + self.server = SimpleWebSocketServer('', self.port, TestEcho) + while not self.exit_event.is_set(): + self.server.serveonce() def __init__(self, port): self.port = port - self.socket = socket.socket() - self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) - self.socket.settimeout(10.0) - self.send_q = queue.Queue() - self.shutdown = Event() + self.exit_event = Event() + self.thread = Thread(target=self.run) + self.thread.start() def __enter__(self): - try: - self.socket.bind(('', self.port)) - except socket.error as e: - print("Bind failed:{}".format(e)) - raise - - self.socket.listen(1) - self.server_thread = Thread(target=self.run_server) - self.server_thread.start() - return self def __exit__(self, exc_type, exc_value, traceback): - self.shutdown.set() - self.server_thread.join() - self.socket.close() - self.conn.close() - - def run_server(self): - self.conn, address = self.socket.accept() # accept new connection - self.socket.settimeout(10.0) - - print("Connection from: {}".format(address)) - - self.establish_connection() - print("WS established") - # Handle connection until client closes it, will echo any data received and send data from send_q queue - self.handle_conn() - - def establish_connection(self): - while not self.shutdown.is_set(): - try: - # receive data stream. it won't accept data packet greater than 1024 bytes - data = self.conn.recv(1024).decode() - if not data: - # exit if data is not received - raise - - if "Upgrade: websocket" in data and "Connection: Upgrade" in data: - self.handshake(data) - return - - except socket.error as err: - print("Unable to establish a websocket connection: {}".format(err)) - raise - - def handshake(self, data): - # Magic string from RFC - MAGIC_STRING = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11" - headers = data.split("\r\n") - - for header in headers: - if "Sec-WebSocket-Key" in header: - client_key = header.split()[1] - - if client_key: - resp_key = client_key + MAGIC_STRING - resp_key = base64.standard_b64encode(hashlib.sha1(resp_key.encode()).digest()) - - resp = "HTTP/1.1 101 Switching Protocols\r\n" + \ - "Upgrade: websocket\r\n" + \ - "Connection: Upgrade\r\n" + \ - "Sec-WebSocket-Accept: {}\r\n\r\n".format(resp_key.decode()) - - self.conn.send(resp.encode()) - - def handle_conn(self): - while not self.shutdown.is_set(): - r,w,e = select.select([self.conn], [], [], 1) - try: - if self.conn in r: - self.echo_data() - - if not self.send_q.empty(): - self._send_data_(self.send_q.get()) - - except socket.error as err: - print("Stopped echoing data: {}".format(err)) - raise - - def echo_data(self): - header = bytearray(self.conn.recv(self.HEADER_LEN, socket.MSG_WAITALL)) - if not header: - # exit if socket closed by peer - return - - # Remove mask bit - payload_len = ~(1 << 7) & header[1] - - payload = bytearray(self.conn.recv(payload_len, socket.MSG_WAITALL)) - - if not payload: - # exit if socket closed by peer - return - frame = header + payload - - decoded_payload = self.decode_frame(frame) - print("Sending echo...") - self._send_data_(decoded_payload) - - def _send_data_(self, data): - frame = self.encode_frame(data) - self.conn.send(frame) - - def send_data(self, data): - self.send_q.put(data.encode()) - - def decode_frame(self, frame): - # Mask out MASK bit from payload length, this len is only valid for short messages (<126) - payload_len = ~(1 << 7) & frame[1] - - mask = frame[2:self.HEADER_LEN] - - encrypted_payload = frame[self.HEADER_LEN:self.HEADER_LEN + payload_len] - payload = bytearray() - - for i in range(payload_len): - payload.append(encrypted_payload[i] ^ mask[i % 4]) - - return payload - - def encode_frame(self, payload): - # Set FIN = 1 and OP_CODE = 1 (text) - header = (1 << 7) | (1 << 0) - - frame = bytearray([header]) - payload_len = len(payload) - - # If payload len is longer than 125 then the next 16 bits are used to encode length - if payload_len > 125: - frame.append(126) - frame.append(payload_len >> 8) - frame.append(0xFF & payload_len) - - else: - frame.append(payload_len) - - frame += payload - - return frame + self.exit_event.set() + self.thread.join(10) + if self.thread.is_alive(): + Utility.console_log('Thread cannot be joined', 'orange') def test_echo(dut): @@ -188,6 +72,11 @@ def test_echo(dut): print("All echos received") +def test_close(dut): + code = dut.expect(re.compile(r"WEBSOCKET: Received closed message with code=(\d*)"), timeout=60)[0] + print("Received close frame with code {}".format(code)) + + def test_recv_long_msg(dut, websocket, msg_len, repeats): send_msg = ''.join(random.choice(string.ascii_uppercase + string.ascii_lowercase + string.digits) for _ in range(msg_len)) @@ -246,6 +135,7 @@ def test_examples_protocol_websocket(env, extra_data): test_echo(dut1) # Message length should exceed DUT's buffer size to test fragmentation, default is 1024 byte test_recv_long_msg(dut1, ws, 2000, 3) + test_close(dut1) else: print("DUT connecting to {}".format(uri)) From e90272c812e78241b38338f495509430afa1d022 Mon Sep 17 00:00:00 2001 From: Marius Vikhammer Date: Thu, 6 Aug 2020 14:48:13 +0800 Subject: [PATCH 38/65] Websocket client: avoid deadlock if stop called from event handler * Original commit: espressif/esp-idf@c2bb0762bb5c24cb170bc9c96fdadb86ae2f06e7 --- .../esp_websocket_client.c | 19 ++++++++++++++++++- .../include/esp_websocket_client.h | 12 ++++++++++++ 2 files changed, 30 insertions(+), 1 deletion(-) diff --git a/components/esp_websocket_client/esp_websocket_client.c b/components/esp_websocket_client/esp_websocket_client.c index bd670d93e..f0e5c8a66 100644 --- a/components/esp_websocket_client/esp_websocket_client.c +++ b/components/esp_websocket_client/esp_websocket_client.c @@ -87,6 +87,7 @@ typedef enum { struct esp_websocket_client { esp_event_loop_handle_t event_handle; + TaskHandle_t task_handle; esp_transport_list_handle_t transport_list; esp_transport_handle_t transport; websocket_config_storage_t *config; @@ -660,7 +661,7 @@ esp_err_t esp_websocket_client_start(esp_websocket_client_handle_t client) ESP_LOGE(TAG, "The client has started"); return ESP_FAIL; } - if (xTaskCreate(esp_websocket_client_task, "websocket_task", client->config->task_stack, client, client->config->task_prio, NULL) != pdTRUE) { + if (xTaskCreate(esp_websocket_client_task, "websocket_task", client->config->task_stack, client, client->config->task_prio, &client->task_handle) != pdTRUE) { ESP_LOGE(TAG, "Error create websocket task"); return ESP_FAIL; } @@ -677,6 +678,15 @@ esp_err_t esp_websocket_client_stop(esp_websocket_client_handle_t client) ESP_LOGW(TAG, "Client was not started"); return ESP_FAIL; } + + /* A running client cannot be stopped from the websocket task/event handler */ + TaskHandle_t running_task = xTaskGetCurrentTaskHandle(); + if (running_task == client->task_handle) { + ESP_LOGE(TAG, "Client cannot be stopped from websocket task"); + return ESP_FAIL; + } + + client->run = false; xEventGroupWaitBits(client->status_bits, STOPPED_BIT, false, true, portMAX_DELAY); client->state = WEBSOCKET_STATE_UNKNOW; @@ -711,6 +721,13 @@ static esp_err_t esp_websocket_client_close_with_optional_body(esp_websocket_cli return ESP_FAIL; } + /* A running client cannot be stopped from the websocket task/event handler */ + TaskHandle_t running_task = xTaskGetCurrentTaskHandle(); + if (running_task == client->task_handle) { + ESP_LOGE(TAG, "Client cannot be stopped from websocket task"); + return ESP_FAIL; + } + if (send_body) { esp_websocket_client_send_close(client, code, data, len + 2, portMAX_DELAY); // len + 2 -> always sending the code } else { diff --git a/components/esp_websocket_client/include/esp_websocket_client.h b/components/esp_websocket_client/include/esp_websocket_client.h index 44488cdb0..8bfb2ce24 100644 --- a/components/esp_websocket_client/include/esp_websocket_client.h +++ b/components/esp_websocket_client/include/esp_websocket_client.h @@ -132,6 +132,9 @@ esp_err_t esp_websocket_client_start(esp_websocket_client_handle_t client); * close frames. It is a good practice to close the connection in a clean way * using esp_websocket_client_close(). * + * Notes: + * - Cannot be called from the websocket event handler + * * @param[in] client The client * * @return esp_err_t @@ -144,6 +147,9 @@ esp_err_t esp_websocket_client_stop(esp_websocket_client_handle_t client); * It is the opposite of the esp_websocket_client_init function and must be called with the same handle as input that a esp_websocket_client_init call returned. * This might close all connections this handle has used. * + * Notes: + * - Cannot be called from the websocket event handler + * * @param[in] client The client * * @return esp_err_t @@ -201,6 +207,9 @@ int esp_websocket_client_send_text(esp_websocket_client_handle_t client, const c * * Client waits until server closes the connection * * Client is stopped the same way as by the `esp_websocket_client_stop()` * + * Notes: + * - Cannot be called from the websocket event handler + * * @param[in] client The client * @param[in] timeout Timeout in RTOS ticks for waiting * @@ -212,6 +221,9 @@ esp_err_t esp_websocket_client_close(esp_websocket_client_handle_t client, TickT * @brief Close the WebSocket connection in a clean way with custom code/data * Closing sequence is the same as for esp_websocket_client_close() * + * Notes: + * - Cannot be called from the websocket event handler + * * @param[in] client The client * @param[in] code Close status code as defined in RFC6455 section-7.4 * @param[in] data Additional data to closing message From d3764807660ae68aeff66d88276ead2f9c26f297 Mon Sep 17 00:00:00 2001 From: Angus Gratton Date: Tue, 10 Nov 2020 18:40:01 +1100 Subject: [PATCH 39/65] Whitespace: Automated whitespace fixes (large commit) Apply the pre-commit hook whitespace fixes to all files in the repo. (Line endings, blank lines at end of file, trailing whitespace) * Original commit: espressif/esp-idf@66fb5a29bbdc2482d67c52e6f66b303378c9b789 --- .../esp_websocket_client/include/esp_websocket_client.h | 8 ++++---- examples/protocols/websocket/Makefile | 1 - examples/protocols/websocket/sdkconfig.ci | 1 - 3 files changed, 4 insertions(+), 6 deletions(-) diff --git a/components/esp_websocket_client/include/esp_websocket_client.h b/components/esp_websocket_client/include/esp_websocket_client.h index 8bfb2ce24..55a580a9e 100644 --- a/components/esp_websocket_client/include/esp_websocket_client.h +++ b/components/esp_websocket_client/include/esp_websocket_client.h @@ -133,7 +133,7 @@ esp_err_t esp_websocket_client_start(esp_websocket_client_handle_t client); * using esp_websocket_client_close(). * * Notes: - * - Cannot be called from the websocket event handler + * - Cannot be called from the websocket event handler * * @param[in] client The client * @@ -149,7 +149,7 @@ esp_err_t esp_websocket_client_stop(esp_websocket_client_handle_t client); * * Notes: * - Cannot be called from the websocket event handler - * + * * @param[in] client The client * * @return esp_err_t @@ -208,7 +208,7 @@ int esp_websocket_client_send_text(esp_websocket_client_handle_t client, const c * * Client is stopped the same way as by the `esp_websocket_client_stop()` * * Notes: - * - Cannot be called from the websocket event handler + * - Cannot be called from the websocket event handler * * @param[in] client The client * @param[in] timeout Timeout in RTOS ticks for waiting @@ -222,7 +222,7 @@ esp_err_t esp_websocket_client_close(esp_websocket_client_handle_t client, TickT * Closing sequence is the same as for esp_websocket_client_close() * * Notes: - * - Cannot be called from the websocket event handler + * - Cannot be called from the websocket event handler * * @param[in] client The client * @param[in] code Close status code as defined in RFC6455 section-7.4 diff --git a/examples/protocols/websocket/Makefile b/examples/protocols/websocket/Makefile index 3b37d3206..063a0a099 100644 --- a/examples/protocols/websocket/Makefile +++ b/examples/protocols/websocket/Makefile @@ -7,4 +7,3 @@ PROJECT_NAME := websocket-example EXTRA_COMPONENT_DIRS = $(IDF_PATH)/examples/common_components/protocol_examples_common include $(IDF_PATH)/make/project.mk - diff --git a/examples/protocols/websocket/sdkconfig.ci b/examples/protocols/websocket/sdkconfig.ci index a0b7712a2..3ce203d46 100644 --- a/examples/protocols/websocket/sdkconfig.ci +++ b/examples/protocols/websocket/sdkconfig.ci @@ -1,3 +1,2 @@ CONFIG_WEBSOCKET_URI_FROM_STDIN=y CONFIG_WEBSOCKET_URI_FROM_STRING=n - From 36167db336690d522104bc6f25931efa1cd790bb Mon Sep 17 00:00:00 2001 From: He Yin Ling Date: Tue, 24 Nov 2020 16:40:15 +0800 Subject: [PATCH 40/65] test: remove fake binary size check in example test: the binary size check in example test was removed long time ago. Now we have updated ttfw_idf to raise exception when performance standard is not found. These fake performance check will be regarded as error. Remove them now. * Original commit: espressif/esp-idf@a908174c06fd493dfada08606eb9d8ff314f7939 --- examples/protocols/websocket/example_test.py | 1 - 1 file changed, 1 deletion(-) diff --git a/examples/protocols/websocket/example_test.py b/examples/protocols/websocket/example_test.py index 0bb085d26..c436fb179 100644 --- a/examples/protocols/websocket/example_test.py +++ b/examples/protocols/websocket/example_test.py @@ -109,7 +109,6 @@ def test_examples_protocol_websocket(env, extra_data): binary_file = os.path.join(dut1.app.binary_path, "websocket-example.bin") bin_size = os.path.getsize(binary_file) ttfw_idf.log_performance("websocket_bin_size", "{}KB".format(bin_size // 1024)) - ttfw_idf.check_performance("websocket_bin_size", bin_size // 1024, dut1.TARGET) try: if "CONFIG_WEBSOCKET_URI_FROM_STDIN" in dut1.app.get_sdkconfig(): From d1dd6ece38115422bae5898c530fec59e40d8551 Mon Sep 17 00:00:00 2001 From: yuanjm Date: Mon, 4 Jan 2021 10:15:15 +0800 Subject: [PATCH 41/65] websocket: support mutual tls for websocket Closes https://github.com/espressif/esp-idf/issues/6059 * Original commit: espressif/esp-idf@5ab774f9d8e119fff56b566fa2f9bdad853bf701 --- .../esp_websocket_client.c | 27 +++++++++++++++++-- .../include/esp_websocket_client.h | 10 +++++-- 2 files changed, 33 insertions(+), 4 deletions(-) diff --git a/components/esp_websocket_client/esp_websocket_client.c b/components/esp_websocket_client/esp_websocket_client.c index f0e5c8a66..e396c3fb0 100644 --- a/components/esp_websocket_client/esp_websocket_client.c +++ b/components/esp_websocket_client/esp_websocket_client.c @@ -320,8 +320,31 @@ esp_websocket_client_handle_t esp_websocket_client_init(const esp_websocket_clie ESP_WS_CLIENT_MEM_CHECK(TAG, ssl, goto _websocket_init_fail); esp_transport_set_default_port(ssl, WEBSOCKET_SSL_DEFAULT_PORT); - if (config->cert_pem) { - esp_transport_ssl_set_cert_data(ssl, config->cert_pem, strlen(config->cert_pem)); + if (config->use_global_ca_store == true) { + esp_transport_ssl_enable_global_ca_store(ssl); + } else if (config->cert_pem) { + if (!config->cert_len) { + esp_transport_ssl_set_cert_data(ssl, config->cert_pem, strlen(config->cert_pem)); + } else { + esp_transport_ssl_set_cert_data_der(ssl, config->cert_pem, config->cert_len); + } + } + if (config->client_cert) { + if (!config->client_cert_len) { + esp_transport_ssl_set_client_cert_data(ssl, config->client_cert, strlen(config->client_cert)); + } else { + esp_transport_ssl_set_client_cert_data_der(ssl, config->client_cert, config->client_cert_len); + } + } + if (config->client_key) { + if (!config->client_key_len) { + esp_transport_ssl_set_client_key_data(ssl, config->client_key, strlen(config->client_key)); + } else { + esp_transport_ssl_set_client_key_data_der(ssl, config->client_key, config->client_key_len); + } + } + if (config->skip_cert_common_name_check) { + esp_transport_ssl_skip_common_name_check(ssl); } esp_transport_list_add(client->transport_list, ssl, "_ssl"); // need to save to transport list, for cleanup diff --git a/components/esp_websocket_client/include/esp_websocket_client.h b/components/esp_websocket_client/include/esp_websocket_client.h index 55a580a9e..62a0d53f0 100644 --- a/components/esp_websocket_client/include/esp_websocket_client.h +++ b/components/esp_websocket_client/include/esp_websocket_client.h @@ -81,14 +81,20 @@ typedef struct { int task_prio; /*!< Websocket task priority */ int task_stack; /*!< Websocket task stack */ int buffer_size; /*!< Websocket buffer size */ - const char *cert_pem; /*!< SSL Certification, PEM format as string, if the client requires to verify server */ + const char *cert_pem; /*!< Pointer to certificate data in PEM or DER format for server verify (with SSL), default is NULL, not required to verify the server. PEM-format must have a terminating NULL-character. DER-format requires the length to be passed in cert_len. */ + size_t cert_len; /*!< Length of the buffer pointed to by cert_pem. May be 0 for null-terminated pem */ + const char *client_cert; /*!< Pointer to certificate data in PEM or DER format for SSL mutual authentication, default is NULL, not required if mutual authentication is not needed. If it is not NULL, also `client_key` has to be provided. PEM-format must have a terminating NULL-character. DER-format requires the length to be passed in client_cert_len. */ + size_t client_cert_len; /*!< Length of the buffer pointed to by client_cert. May be 0 for null-terminated pem */ + const char *client_key; /*!< Pointer to private key data in PEM or DER format for SSL mutual authentication, default is NULL, not required if mutual authentication is not needed. If it is not NULL, also `client_cert` has to be provided. PEM-format must have a terminating NULL-character. DER-format requires the length to be passed in client_key_len */ + size_t client_key_len; /*!< Length of the buffer pointed to by client_key_pem. May be 0 for null-terminated pem */ esp_websocket_transport_t transport; /*!< Websocket transport type, see `esp_websocket_transport_t */ char *subprotocol; /*!< Websocket subprotocol */ char *user_agent; /*!< Websocket user-agent */ char *headers; /*!< Websocket additional headers */ int pingpong_timeout_sec; /*!< Period before connection is aborted due to no PONGs received */ bool disable_pingpong_discon; /*!< Disable auto-disconnect due to no PONG received within pingpong_timeout_sec */ - + bool use_global_ca_store; /*!< Use a global ca_store for all the connections in which this bool is set. */ + bool skip_cert_common_name_check;/*!< Skip any validation of server certificate CN field */ } esp_websocket_client_config_t; /** From 8a6c320a295c377c0672742a53630368e6dd5f87 Mon Sep 17 00:00:00 2001 From: Shubham Kulkarni Date: Mon, 11 Jan 2021 16:24:48 +0530 Subject: [PATCH 42/65] Add options for esp_http_client and esp_websocket_client to support keepalive * Original commit: espressif/esp-idf@b53e46a68e8671c73e8aafe2602de5ff5a77e3db --- .../esp_websocket_client/esp_websocket_client.c | 13 +++++++++++++ .../include/esp_websocket_client.h | 4 ++++ 2 files changed, 17 insertions(+) diff --git a/components/esp_websocket_client/esp_websocket_client.c b/components/esp_websocket_client/esp_websocket_client.c index e396c3fb0..1e4aa2a14 100644 --- a/components/esp_websocket_client/esp_websocket_client.c +++ b/components/esp_websocket_client/esp_websocket_client.c @@ -40,6 +40,9 @@ static const char *TAG = "WEBSOCKET_CLIENT"; #define WEBSOCKET_PING_TIMEOUT_MS (10*1000) #define WEBSOCKET_EVENT_QUEUE_SIZE (1) #define WEBSOCKET_PINGPONG_TIMEOUT_SEC (120) +#define WEBSOCKET_KEEP_ALIVE_IDLE (5) +#define WEBSOCKET_KEEP_ALIVE_INTERVAL (5) +#define WEBSOCKET_KEEP_ALIVE_COUNT (3) #define ESP_WS_CLIENT_MEM_CHECK(TAG, a, action) if (!(a)) { \ ESP_LOGE(TAG,"%s(%d): %s", __FUNCTION__, __LINE__, "Memory exhausted"); \ @@ -108,6 +111,7 @@ struct esp_websocket_client { ws_transport_opcodes_t last_opcode; int payload_len; int payload_offset; + esp_transport_keep_alive_t keep_alive_cfg; }; static uint64_t _tick_get_ms(void) @@ -290,6 +294,13 @@ esp_websocket_client_handle_t esp_websocket_client_init(const esp_websocket_clie return NULL; } + if (config->keep_alive_enable == true) { + client->keep_alive_cfg.keep_alive_enable = true; + client->keep_alive_cfg.keep_alive_idle = (config->keep_alive_idle == 0) ? WEBSOCKET_KEEP_ALIVE_IDLE : config->keep_alive_idle; + client->keep_alive_cfg.keep_alive_interval = (config->keep_alive_interval == 0) ? WEBSOCKET_KEEP_ALIVE_INTERVAL : config->keep_alive_interval; + client->keep_alive_cfg.keep_alive_count = (config->keep_alive_count == 0) ? WEBSOCKET_KEEP_ALIVE_COUNT : config->keep_alive_count; + } + client->lock = xSemaphoreCreateRecursiveMutex(); ESP_WS_CLIENT_MEM_CHECK(TAG, client->lock, goto _websocket_init_fail); @@ -303,6 +314,7 @@ esp_websocket_client_handle_t esp_websocket_client_init(const esp_websocket_clie ESP_WS_CLIENT_MEM_CHECK(TAG, tcp, goto _websocket_init_fail); esp_transport_set_default_port(tcp, WEBSOCKET_TCP_DEFAULT_PORT); + esp_transport_tcp_set_keep_alive(tcp, &client->keep_alive_cfg); esp_transport_list_add(client->transport_list, tcp, "_tcp"); // need to save to transport list, for cleanup @@ -346,6 +358,7 @@ esp_websocket_client_handle_t esp_websocket_client_init(const esp_websocket_clie if (config->skip_cert_common_name_check) { esp_transport_ssl_skip_common_name_check(ssl); } + esp_transport_ssl_set_keep_alive(ssl, &client->keep_alive_cfg); esp_transport_list_add(client->transport_list, ssl, "_ssl"); // need to save to transport list, for cleanup esp_transport_handle_t wss = esp_transport_ws_init(ssl); diff --git a/components/esp_websocket_client/include/esp_websocket_client.h b/components/esp_websocket_client/include/esp_websocket_client.h index 62a0d53f0..c50184565 100644 --- a/components/esp_websocket_client/include/esp_websocket_client.h +++ b/components/esp_websocket_client/include/esp_websocket_client.h @@ -95,6 +95,10 @@ typedef struct { bool disable_pingpong_discon; /*!< Disable auto-disconnect due to no PONG received within pingpong_timeout_sec */ bool use_global_ca_store; /*!< Use a global ca_store for all the connections in which this bool is set. */ bool skip_cert_common_name_check;/*!< Skip any validation of server certificate CN field */ + bool keep_alive_enable; /*!< Enable keep-alive timeout */ + int keep_alive_idle; /*!< Keep-alive idle time. Default is 5 (second) */ + int keep_alive_interval; /*!< Keep-alive interval time. Default is 5 (second) */ + int keep_alive_count; /*!< Keep-alive packet retry send count. Default is 3 counts */ } esp_websocket_client_config_t; /** From 95cf983502ef64a0b4a129725b1c82981d850ec0 Mon Sep 17 00:00:00 2001 From: David Cermak Date: Mon, 11 Jan 2021 18:15:13 +0100 Subject: [PATCH 43/65] ws_transport: Add option to propagate control packets to the app Client could choose if they want to receive control packets and handle them. * If disabled (default) the transport itself tries to handle PING and CLOSE frames automatically during read operation. If handled correctly, read outputs 0 indicating no (actual app) data received. * if enabled, all control frames are passed to the application to be processed there. Closes https://github.com/espressif/esp-idf/issues/6307 * Original commit: espressif/esp-idf@acc7bd2ca45c21033cbd02220a27c3c1ecdd5ad0 --- .../esp_websocket_client.c | 44 ++++++++++++------- .../websocket/main/websocket_example.c | 1 + 2 files changed, 30 insertions(+), 15 deletions(-) diff --git a/components/esp_websocket_client/esp_websocket_client.c b/components/esp_websocket_client/esp_websocket_client.c index 1e4aa2a14..8a578c931 100644 --- a/components/esp_websocket_client/esp_websocket_client.c +++ b/components/esp_websocket_client/esp_websocket_client.c @@ -49,6 +49,14 @@ static const char *TAG = "WEBSOCKET_CLIENT"; action; \ } +#define ESP_WS_CLIENT_ERR_OK_CHECK(TAG, err, action) { \ + esp_err_t _esp_ws_err_to_check = err; \ + if (_esp_ws_err_to_check != ESP_OK) { \ + ESP_LOGE(TAG,"%s(%d): Expected ESP_OK; reported: %d", __FUNCTION__, __LINE__, _esp_ws_err_to_check); \ + action; \ + } \ + } + #define ESP_WS_CLIENT_STATE_CHECK(TAG, a, action) if ((a->state) < WEBSOCKET_STATE_INIT) { \ ESP_LOGE(TAG,"%s:%d (%s): %s", __FILE__, __LINE__, __FUNCTION__, "Websocket already stop"); \ action; \ @@ -262,20 +270,20 @@ static esp_err_t esp_websocket_client_destroy_config(esp_websocket_client_handle return ESP_OK; } -static void set_websocket_transport_optional_settings(esp_websocket_client_handle_t client, esp_transport_handle_t trans) +static esp_err_t set_websocket_transport_optional_settings(esp_websocket_client_handle_t client, const char *scheme) { - if (trans && client->config->path) { - esp_transport_ws_set_path(trans, client->config->path); - } - if (trans && client->config->subprotocol) { - esp_transport_ws_set_subprotocol(trans, client->config->subprotocol); - } - if (trans && client->config->user_agent) { - esp_transport_ws_set_user_agent(trans, client->config->user_agent); - } - if (trans && client->config->headers) { - esp_transport_ws_set_headers(trans, client->config->headers); + esp_transport_handle_t trans = esp_transport_list_get_transport(client->transport_list, scheme); + if (trans) { + const esp_transport_ws_config_t config = { + .ws_path = client->config->path, + .sub_protocol = client->config->subprotocol, + .user_agent = client->config->user_agent, + .headers = client->config->headers, + .propagate_control_frames = true + }; + return esp_transport_ws_set_config(trans, &config); } + return ESP_ERR_INVALID_ARG; } esp_websocket_client_handle_t esp_websocket_client_init(const esp_websocket_client_config_t *config) @@ -389,8 +397,8 @@ esp_websocket_client_handle_t esp_websocket_client_init(const esp_websocket_clie ESP_WS_CLIENT_MEM_CHECK(TAG, client->config->scheme, goto _websocket_init_fail); } - set_websocket_transport_optional_settings(client, esp_transport_list_get_transport(client->transport_list, "ws")); - set_websocket_transport_optional_settings(client, esp_transport_list_get_transport(client->transport_list, "wss")); + ESP_WS_CLIENT_ERR_OK_CHECK(TAG, set_websocket_transport_optional_settings(client, "ws"), goto _websocket_init_fail;) + ESP_WS_CLIENT_ERR_OK_CHECK(TAG, set_websocket_transport_optional_settings(client, "wss"), goto _websocket_init_fail;) client->keepalive_tick_ms = _tick_get_ms(); client->reconnect_tick_ms = _tick_get_ms(); @@ -523,6 +531,11 @@ static esp_err_t esp_websocket_client_recv(esp_websocket_client_handle_t client) client->payload_len = esp_transport_ws_get_read_payload_len(client->transport); client->last_opcode = esp_transport_ws_get_read_opcode(client->transport); + if (rlen == 0 && client->last_opcode == WS_TRANSPORT_OPCODES_NONE ) { + ESP_LOGV(TAG, "esp_transport_read timeouts"); + return ESP_OK; + } + esp_websocket_client_dispatch_event(client, WEBSOCKET_EVENT_DATA, client->rx_buffer, rlen); client->payload_offset += rlen; @@ -531,6 +544,7 @@ static esp_err_t esp_websocket_client_recv(esp_websocket_client_handle_t client) // if a PING message received -> send out the PONG, this will not work for PING messages with payload longer than buffer len if (client->last_opcode == WS_TRANSPORT_OPCODES_PING) { const char *data = (client->payload_len == 0) ? NULL : client->rx_buffer; + ESP_LOGD(TAG, "Sending PONG with payload len=%d", client->payload_len); esp_transport_ws_send_raw(client->transport, WS_TRANSPORT_OPCODES_PONG | WS_TRANSPORT_OPCODES_FIN, data, client->payload_len, client->config->network_timeout_ms); } else if (client->last_opcode == WS_TRANSPORT_OPCODES_PONG) { @@ -570,7 +584,7 @@ static void esp_websocket_client_task(void *pv) int read_select = 0; while (client->run) { if (xSemaphoreTakeRecursive(client->lock, lock_timeout) != pdPASS) { - ESP_LOGE(TAG, "Failed to lock ws-client tasks, exitting the task..."); + ESP_LOGE(TAG, "Failed to lock ws-client tasks, exiting the task..."); break; } switch ((int)client->state) { diff --git a/examples/protocols/websocket/main/websocket_example.c b/examples/protocols/websocket/main/websocket_example.c index eb3db80ce..a1cb08c56 100644 --- a/examples/protocols/websocket/main/websocket_example.c +++ b/examples/protocols/websocket/main/websocket_example.c @@ -137,6 +137,7 @@ void app_main(void) ESP_LOGI(TAG, "[APP] IDF version: %s", esp_get_idf_version()); esp_log_level_set("*", ESP_LOG_INFO); esp_log_level_set("WEBSOCKET_CLIENT", ESP_LOG_DEBUG); + esp_log_level_set("TRANSPORT_WS", ESP_LOG_DEBUG); esp_log_level_set("TRANS_TCP", ESP_LOG_DEBUG); ESP_ERROR_CHECK(nvs_flash_init()); From cf697a1a1b0318853d3a3a3caaddadddde312bf7 Mon Sep 17 00:00:00 2001 From: Fu Hanxi Date: Tue, 26 Jan 2021 10:49:01 +0800 Subject: [PATCH 44/65] style: format python files with isort and double-quote-string-fixer * Original commit: espressif/esp-idf@0146f258d7c8bdbc59ba6d40e8798500a30eb4ce --- examples/protocols/websocket/example_test.py | 51 ++++++++++---------- 1 file changed, 26 insertions(+), 25 deletions(-) diff --git a/examples/protocols/websocket/example_test.py b/examples/protocols/websocket/example_test.py index c436fb179..90ce65628 100644 --- a/examples/protocols/websocket/example_test.py +++ b/examples/protocols/websocket/example_test.py @@ -1,14 +1,15 @@ -from __future__ import print_function -from __future__ import unicode_literals -import re +from __future__ import print_function, unicode_literals + import os -import socket import random +import re +import socket import string +from threading import Event, Thread + +import ttfw_idf from SimpleWebSocketServer import SimpleWebSocketServer, WebSocket from tiny_test_fw import Utility -from threading import Thread, Event -import ttfw_idf def get_my_ip(): @@ -66,15 +67,15 @@ class Websocket(object): def test_echo(dut): - dut.expect("WEBSOCKET_EVENT_CONNECTED") + dut.expect('WEBSOCKET_EVENT_CONNECTED') for i in range(0, 10): - dut.expect(re.compile(r"Received=hello (\d)"), timeout=30) - print("All echos received") + dut.expect(re.compile(r'Received=hello (\d)'), timeout=30) + print('All echos received') def test_close(dut): - code = dut.expect(re.compile(r"WEBSOCKET: Received closed message with code=(\d*)"), timeout=60)[0] - print("Received close frame with code {}".format(code)) + code = dut.expect(re.compile(r'WEBSOCKET: Received closed message with code=(\d*)'), timeout=60)[0] + print('Received close frame with code {}'.format(code)) def test_recv_long_msg(dut, websocket, msg_len, repeats): @@ -86,17 +87,17 @@ def test_recv_long_msg(dut, websocket, msg_len, repeats): recv_msg = '' while len(recv_msg) < msg_len: # Filter out color encoding - match = dut.expect(re.compile(r"Received=([a-zA-Z0-9]*).*\n"), timeout=30)[0] + match = dut.expect(re.compile(r'Received=([a-zA-Z0-9]*).*\n'), timeout=30)[0] recv_msg += match if recv_msg == send_msg: - print("Sent message and received message are equal") + print('Sent message and received message are equal') else: - raise ValueError("DUT received string do not match sent string, \nexpected: {}\nwith length {}\ - \nreceived: {}\nwith length {}".format(send_msg, len(send_msg), recv_msg, len(recv_msg))) + raise ValueError('DUT received string do not match sent string, \nexpected: {}\nwith length {}\ + \nreceived: {}\nwith length {}'.format(send_msg, len(send_msg), recv_msg, len(recv_msg))) -@ttfw_idf.idf_example_test(env_tag="Example_WIFI") +@ttfw_idf.idf_example_test(env_tag='Example_WIFI') def test_examples_protocol_websocket(env, extra_data): """ steps: @@ -104,17 +105,17 @@ def test_examples_protocol_websocket(env, extra_data): 2. connect to uri specified in the config 3. send and receive data """ - dut1 = env.get_dut("websocket", "examples/protocols/websocket", dut_class=ttfw_idf.ESP32DUT) + dut1 = env.get_dut('websocket', 'examples/protocols/websocket', dut_class=ttfw_idf.ESP32DUT) # check and log bin size - binary_file = os.path.join(dut1.app.binary_path, "websocket-example.bin") + binary_file = os.path.join(dut1.app.binary_path, 'websocket-example.bin') bin_size = os.path.getsize(binary_file) - ttfw_idf.log_performance("websocket_bin_size", "{}KB".format(bin_size // 1024)) + ttfw_idf.log_performance('websocket_bin_size', '{}KB'.format(bin_size // 1024)) try: - if "CONFIG_WEBSOCKET_URI_FROM_STDIN" in dut1.app.get_sdkconfig(): + if 'CONFIG_WEBSOCKET_URI_FROM_STDIN' in dut1.app.get_sdkconfig(): uri_from_stdin = True else: - uri = dut1.app.get_sdkconfig()["CONFIG_WEBSOCKET_URI"].strip('"') + uri = dut1.app.get_sdkconfig()['CONFIG_WEBSOCKET_URI'].strip('"') uri_from_stdin = False except Exception: @@ -127,9 +128,9 @@ def test_examples_protocol_websocket(env, extra_data): if uri_from_stdin: server_port = 4455 with Websocket(server_port) as ws: - uri = "ws://{}:{}".format(get_my_ip(), server_port) - print("DUT connecting to {}".format(uri)) - dut1.expect("Please enter uri of websocket endpoint", timeout=30) + uri = 'ws://{}:{}'.format(get_my_ip(), server_port) + print('DUT connecting to {}'.format(uri)) + dut1.expect('Please enter uri of websocket endpoint', timeout=30) dut1.write(uri) test_echo(dut1) # Message length should exceed DUT's buffer size to test fragmentation, default is 1024 byte @@ -137,7 +138,7 @@ def test_examples_protocol_websocket(env, extra_data): test_close(dut1) else: - print("DUT connecting to {}".format(uri)) + print('DUT connecting to {}'.format(uri)) test_echo(dut1) From 1933367f633b9101e8941dfcee308f9bf996a406 Mon Sep 17 00:00:00 2001 From: Akihiro YAMAZAKI Date: Thu, 14 Jan 2021 16:30:47 +0900 Subject: [PATCH 45/65] websocket: Add configurable ping interval Merges https://github.com/espressif/esp-idf/pull/6399 Signed-off-by: David Cermak * Original commit: espressif/esp-idf@9ff9137e7a8b64e956c1c63e95a48f4049ad571e --- .../esp_websocket_client/esp_websocket_client.c | 11 +++++++++-- .../include/esp_websocket_client.h | 1 + 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/components/esp_websocket_client/esp_websocket_client.c b/components/esp_websocket_client/esp_websocket_client.c index 8a578c931..167a9d4b0 100644 --- a/components/esp_websocket_client/esp_websocket_client.c +++ b/components/esp_websocket_client/esp_websocket_client.c @@ -37,7 +37,7 @@ static const char *TAG = "WEBSOCKET_CLIENT"; #define WEBSOCKET_TASK_PRIORITY (5) #define WEBSOCKET_TASK_STACK (4*1024) #define WEBSOCKET_NETWORK_TIMEOUT_MS (10*1000) -#define WEBSOCKET_PING_TIMEOUT_MS (10*1000) +#define WEBSOCKET_PING_INTERVAL_SEC (10) #define WEBSOCKET_EVENT_QUEUE_SIZE (1) #define WEBSOCKET_PINGPONG_TIMEOUT_SEC (120) #define WEBSOCKET_KEEP_ALIVE_IDLE (5) @@ -85,6 +85,7 @@ typedef struct { char *user_agent; char *headers; int pingpong_timeout_sec; + size_t ping_interval_sec; } websocket_config_storage_t; typedef enum { @@ -243,6 +244,12 @@ static esp_err_t esp_websocket_client_set_config(esp_websocket_client_handle_t c cfg->pingpong_timeout_sec = WEBSOCKET_PINGPONG_TIMEOUT_SEC; } + if (config->ping_interval_sec == 0) { + cfg->ping_interval_sec = WEBSOCKET_PING_INTERVAL_SEC; + } else { + cfg->ping_interval_sec = config->ping_interval_sec; + } + return ESP_OK; } @@ -612,7 +619,7 @@ static void esp_websocket_client_task(void *pv) case WEBSOCKET_STATE_CONNECTED: if ((CLOSE_FRAME_SENT_BIT & xEventGroupGetBits(client->status_bits)) == 0) { // only send and check for PING // if closing hasn't been initiated - if (_tick_get_ms() - client->ping_tick_ms > WEBSOCKET_PING_TIMEOUT_MS) { + if (_tick_get_ms() - client->ping_tick_ms > client->config->ping_interval_sec*1000) { client->ping_tick_ms = _tick_get_ms(); ESP_LOGD(TAG, "Sending PING..."); esp_transport_ws_send_raw(client->transport, WS_TRANSPORT_OPCODES_PING | WS_TRANSPORT_OPCODES_FIN, NULL, 0, client->config->network_timeout_ms); diff --git a/components/esp_websocket_client/include/esp_websocket_client.h b/components/esp_websocket_client/include/esp_websocket_client.h index c50184565..13f817685 100644 --- a/components/esp_websocket_client/include/esp_websocket_client.h +++ b/components/esp_websocket_client/include/esp_websocket_client.h @@ -99,6 +99,7 @@ typedef struct { int keep_alive_idle; /*!< Keep-alive idle time. Default is 5 (second) */ int keep_alive_interval; /*!< Keep-alive interval time. Default is 5 (second) */ int keep_alive_count; /*!< Keep-alive packet retry send count. Default is 3 counts */ + size_t ping_interval_sec; /*!< Websocket ping interval, defaults to 10 seconds if not set */ } esp_websocket_client_config_t; /** From 86aa0b8d392487aa401ca00728e088e4bd9bde2e Mon Sep 17 00:00:00 2001 From: David Cermak Date: Tue, 2 Feb 2021 10:38:34 +0100 Subject: [PATCH 46/65] websockets: Set keepalive options after adding transport to the list To be in line with other code and mainly to support base/foundation transport used by both tcp and ssl transport layers * Original commit: espressif/esp-idf@99805d880f41857702b3bbb35bc0dfaf7dec3aec --- components/esp_websocket_client/esp_websocket_client.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/components/esp_websocket_client/esp_websocket_client.c b/components/esp_websocket_client/esp_websocket_client.c index 167a9d4b0..055a23772 100644 --- a/components/esp_websocket_client/esp_websocket_client.c +++ b/components/esp_websocket_client/esp_websocket_client.c @@ -329,8 +329,8 @@ esp_websocket_client_handle_t esp_websocket_client_init(const esp_websocket_clie ESP_WS_CLIENT_MEM_CHECK(TAG, tcp, goto _websocket_init_fail); esp_transport_set_default_port(tcp, WEBSOCKET_TCP_DEFAULT_PORT); - esp_transport_tcp_set_keep_alive(tcp, &client->keep_alive_cfg); esp_transport_list_add(client->transport_list, tcp, "_tcp"); // need to save to transport list, for cleanup + esp_transport_tcp_set_keep_alive(tcp, &client->keep_alive_cfg); esp_transport_handle_t ws = esp_transport_ws_init(tcp); @@ -347,6 +347,7 @@ esp_websocket_client_handle_t esp_websocket_client_init(const esp_websocket_clie ESP_WS_CLIENT_MEM_CHECK(TAG, ssl, goto _websocket_init_fail); esp_transport_set_default_port(ssl, WEBSOCKET_SSL_DEFAULT_PORT); + esp_transport_list_add(client->transport_list, ssl, "_ssl"); // need to save to transport list, for cleanup if (config->use_global_ca_store == true) { esp_transport_ssl_enable_global_ca_store(ssl); } else if (config->cert_pem) { @@ -374,7 +375,6 @@ esp_websocket_client_handle_t esp_websocket_client_init(const esp_websocket_clie esp_transport_ssl_skip_common_name_check(ssl); } esp_transport_ssl_set_keep_alive(ssl, &client->keep_alive_cfg); - esp_transport_list_add(client->transport_list, ssl, "_ssl"); // need to save to transport list, for cleanup esp_transport_handle_t wss = esp_transport_ws_init(ssl); ESP_WS_CLIENT_MEM_CHECK(TAG, wss, goto _websocket_init_fail); From 9219ff710ac82211be2c7067164f14a4f295f5b9 Mon Sep 17 00:00:00 2001 From: yuanjm Date: Thu, 21 Jan 2021 19:45:00 +0800 Subject: [PATCH 47/65] websocket: Add websocket unit tests * Original commit: espressif/esp-idf@cd01a0ca81ef2ba5648fd7712c9bf45bbf252339 --- .../esp_websocket_client/test/CMakeLists.txt | 2 + .../esp_websocket_client/test/component.mk | 4 ++ .../test/test_websocket_client.c | 58 +++++++++++++++++++ 3 files changed, 64 insertions(+) create mode 100644 components/esp_websocket_client/test/CMakeLists.txt create mode 100644 components/esp_websocket_client/test/component.mk create mode 100644 components/esp_websocket_client/test/test_websocket_client.c diff --git a/components/esp_websocket_client/test/CMakeLists.txt b/components/esp_websocket_client/test/CMakeLists.txt new file mode 100644 index 000000000..b494e8dae --- /dev/null +++ b/components/esp_websocket_client/test/CMakeLists.txt @@ -0,0 +1,2 @@ +idf_component_register(SRC_DIRS "." + PRIV_REQUIRES cmock test_utils esp_websocket_client) diff --git a/components/esp_websocket_client/test/component.mk b/components/esp_websocket_client/test/component.mk new file mode 100644 index 000000000..8c6eb513e --- /dev/null +++ b/components/esp_websocket_client/test/component.mk @@ -0,0 +1,4 @@ +# +#Component Makefile +# +COMPONENT_ADD_LDFLAGS = -Wl,--whole-archive -l$(COMPONENT_NAME) -Wl,--no-whole-archive diff --git a/components/esp_websocket_client/test/test_websocket_client.c b/components/esp_websocket_client/test/test_websocket_client.c new file mode 100644 index 000000000..5d96b4983 --- /dev/null +++ b/components/esp_websocket_client/test/test_websocket_client.c @@ -0,0 +1,58 @@ +// Copyright 2021 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include +#include +#include + +#include "unity.h" +#include "test_utils.h" + +static void test_leak_setup(const char * file, long line) +{ + printf("%s:%ld\n", file, line); + unity_reset_leak_checks(); +} + +TEST_CASE("websocket init and deinit", "[websocket][leaks=0]") +{ + test_leak_setup(__FILE__, __LINE__); + const esp_websocket_client_config_t websocket_cfg = { + // no connection takes place, but the uri has to be valid for init() to succeed + .uri = "ws://echo.websocket.org", + }; + esp_websocket_client_handle_t client = esp_websocket_client_init(&websocket_cfg); + TEST_ASSERT_NOT_EQUAL(NULL, client); + esp_websocket_client_destroy(client); +} + +TEST_CASE("websocket init with invalid url", "[websocket][leaks=0]") +{ + test_leak_setup(__FILE__, __LINE__); + const esp_websocket_client_config_t websocket_cfg = { + .uri = "INVALID", + }; + esp_websocket_client_handle_t client = esp_websocket_client_init(&websocket_cfg); + TEST_ASSERT_NULL(client); +} + +TEST_CASE("websocket set url with invalid url", "[websocket][leaks=0]") +{ + test_leak_setup(__FILE__, __LINE__); + const esp_websocket_client_config_t websocket_cfg = {}; + esp_websocket_client_handle_t client = esp_websocket_client_init(&websocket_cfg); + TEST_ASSERT_NOT_EQUAL(NULL, client); + TEST_ASSERT_NOT_EQUAL(ESP_OK, esp_websocket_client_set_uri(client, "INVALID")); + esp_websocket_client_destroy(client); +} From f0351ff3782890babe0c0e5ed1ff08ea9cb7cb39 Mon Sep 17 00:00:00 2001 From: Angus Gratton Date: Thu, 18 Feb 2021 10:09:47 +1100 Subject: [PATCH 48/65] esp_websocket_client: Don't log the filename when logging "Websocket already stop" Progress towards https://jira.espressif.com:8443/browse/IDFGH-4477 * Original commit: espressif/esp-idf@10bde42551b479bd4bfccc9d3c6d983f8abe0b87 --- components/esp_websocket_client/esp_websocket_client.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/esp_websocket_client/esp_websocket_client.c b/components/esp_websocket_client/esp_websocket_client.c index 055a23772..5055736d4 100644 --- a/components/esp_websocket_client/esp_websocket_client.c +++ b/components/esp_websocket_client/esp_websocket_client.c @@ -58,7 +58,7 @@ static const char *TAG = "WEBSOCKET_CLIENT"; } #define ESP_WS_CLIENT_STATE_CHECK(TAG, a, action) if ((a->state) < WEBSOCKET_STATE_INIT) { \ - ESP_LOGE(TAG,"%s:%d (%s): %s", __FILE__, __LINE__, __FUNCTION__, "Websocket already stop"); \ + ESP_LOGE(TAG,"%s(%d): %s", __FUNCTION__, __LINE__, "Websocket already stop"); \ action; \ } From 4a608ec1cdf3de2a60eecb478c4027a4bd2492c5 Mon Sep 17 00:00:00 2001 From: yuanjm Date: Tue, 19 Jan 2021 17:50:31 +0800 Subject: [PATCH 49/65] components: Support bind socket to specified interface in esp_http_client and esp_websocket_client component * Original commit: espressif/esp-idf@bead3599abd875d746e64cd6749574ff2c155adb --- .../esp_websocket_client/esp_websocket_client.c | 12 +++++++++++- .../include/esp_websocket_client.h | 2 ++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/components/esp_websocket_client/esp_websocket_client.c b/components/esp_websocket_client/esp_websocket_client.c index 5055736d4..c9bb0fd32 100644 --- a/components/esp_websocket_client/esp_websocket_client.c +++ b/components/esp_websocket_client/esp_websocket_client.c @@ -121,6 +121,7 @@ struct esp_websocket_client { int payload_len; int payload_offset; esp_transport_keep_alive_t keep_alive_cfg; + struct ifreq *if_name; }; static uint64_t _tick_get_ms(void) @@ -316,6 +317,12 @@ esp_websocket_client_handle_t esp_websocket_client_init(const esp_websocket_clie client->keep_alive_cfg.keep_alive_count = (config->keep_alive_count == 0) ? WEBSOCKET_KEEP_ALIVE_COUNT : config->keep_alive_count; } + if (config->if_name) { + client->if_name = calloc(1, sizeof(struct ifreq) + 1); + ESP_WS_CLIENT_MEM_CHECK(TAG, client->if_name, goto _websocket_init_fail); + memcpy(client->if_name, config->if_name, sizeof(struct ifreq)); + } + client->lock = xSemaphoreCreateRecursiveMutex(); ESP_WS_CLIENT_MEM_CHECK(TAG, client->lock, goto _websocket_init_fail); @@ -331,7 +338,7 @@ esp_websocket_client_handle_t esp_websocket_client_init(const esp_websocket_clie esp_transport_set_default_port(tcp, WEBSOCKET_TCP_DEFAULT_PORT); esp_transport_list_add(client->transport_list, tcp, "_tcp"); // need to save to transport list, for cleanup esp_transport_tcp_set_keep_alive(tcp, &client->keep_alive_cfg); - + esp_transport_tcp_set_interface_name(tcp, client->if_name); esp_transport_handle_t ws = esp_transport_ws_init(tcp); ESP_WS_CLIENT_MEM_CHECK(TAG, ws, goto _websocket_init_fail); @@ -448,6 +455,9 @@ esp_err_t esp_websocket_client_destroy(esp_websocket_client_handle_t client) if (client->event_handle) { esp_event_loop_delete(client->event_handle); } + if (client->if_name) { + free(client->if_name); + } esp_websocket_client_destroy_config(client); esp_transport_list_destroy(client->transport_list); vQueueDelete(client->lock); diff --git a/components/esp_websocket_client/include/esp_websocket_client.h b/components/esp_websocket_client/include/esp_websocket_client.h index 13f817685..5ef08035d 100644 --- a/components/esp_websocket_client/include/esp_websocket_client.h +++ b/components/esp_websocket_client/include/esp_websocket_client.h @@ -22,6 +22,7 @@ #include "freertos/FreeRTOS.h" #include "esp_err.h" #include "esp_event.h" +#include #ifdef __cplusplus extern "C" { @@ -100,6 +101,7 @@ typedef struct { int keep_alive_interval; /*!< Keep-alive interval time. Default is 5 (second) */ int keep_alive_count; /*!< Keep-alive packet retry send count. Default is 3 counts */ size_t ping_interval_sec; /*!< Websocket ping interval, defaults to 10 seconds if not set */ + struct ifreq *if_name; /*!< The name of interface for data to go through. Use the default interface without setting */ } esp_websocket_client_config_t; /** From de7cd72f70731863d57c518d48a20cfc27c05d11 Mon Sep 17 00:00:00 2001 From: yuanjm Date: Fri, 19 Feb 2021 15:50:42 +0800 Subject: [PATCH 50/65] components: Remove repeated keep alive function by ssl layer function In esp_http_client and esp_websocket_client components, esp_transport_tcp_set_keep_alive has been called and keep-alive config has been saved in ssl->cfg.keep_alive_cfg, So no need to call esp_transport_ssl_set_keep_alive again. * Original commit: espressif/esp-idf@c79a907e4fef0c54175ad5659bc0df45a40745c9 --- components/esp_websocket_client/esp_websocket_client.c | 1 - 1 file changed, 1 deletion(-) diff --git a/components/esp_websocket_client/esp_websocket_client.c b/components/esp_websocket_client/esp_websocket_client.c index c9bb0fd32..2f6405d47 100644 --- a/components/esp_websocket_client/esp_websocket_client.c +++ b/components/esp_websocket_client/esp_websocket_client.c @@ -381,7 +381,6 @@ esp_websocket_client_handle_t esp_websocket_client_init(const esp_websocket_clie if (config->skip_cert_common_name_check) { esp_transport_ssl_skip_common_name_check(ssl); } - esp_transport_ssl_set_keep_alive(ssl, &client->keep_alive_cfg); esp_transport_handle_t wss = esp_transport_ws_init(ssl); ESP_WS_CLIENT_MEM_CHECK(TAG, wss, goto _websocket_init_fail); From 028be5a8d74874cfa5577a2b0cf022db9bf39f11 Mon Sep 17 00:00:00 2001 From: Shubham Kulkarni Date: Thu, 25 Mar 2021 15:20:30 +0530 Subject: [PATCH 51/65] Split example_tests with Example_WIFI tag group into Example_OTA and Example_Protocols * Original commit: espressif/esp-idf@0a395134d45caa0cba1bebdfd2ddb6fb2cf93d75 --- examples/protocols/websocket/example_test.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/protocols/websocket/example_test.py b/examples/protocols/websocket/example_test.py index 90ce65628..0955e7be9 100644 --- a/examples/protocols/websocket/example_test.py +++ b/examples/protocols/websocket/example_test.py @@ -97,7 +97,7 @@ def test_recv_long_msg(dut, websocket, msg_len, repeats): \nreceived: {}\nwith length {}'.format(send_msg, len(send_msg), recv_msg, len(recv_msg))) -@ttfw_idf.idf_example_test(env_tag='Example_WIFI') +@ttfw_idf.idf_example_test(env_tag='Example_WIFI_Protocols') def test_examples_protocol_websocket(env, extra_data): """ steps: From fbdbd550c01ac6e012a27c7a5450969db6fd59b8 Mon Sep 17 00:00:00 2001 From: 0xFEEDC0DE64 Date: Mon, 7 Jun 2021 16:58:25 +0200 Subject: [PATCH 52/65] ws_client: Fix const correctness in the API config structure Merges https://github.com/espressif/esp-idf/pull/7113 * Original commit: espressif/esp-idf@70b1247a47f4583fccd8a91bf6cc532e5741e632 --- .../esp_websocket_client/include/esp_websocket_client.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/components/esp_websocket_client/include/esp_websocket_client.h b/components/esp_websocket_client/include/esp_websocket_client.h index 5ef08035d..0ed635346 100644 --- a/components/esp_websocket_client/include/esp_websocket_client.h +++ b/components/esp_websocket_client/include/esp_websocket_client.h @@ -89,9 +89,9 @@ typedef struct { const char *client_key; /*!< Pointer to private key data in PEM or DER format for SSL mutual authentication, default is NULL, not required if mutual authentication is not needed. If it is not NULL, also `client_cert` has to be provided. PEM-format must have a terminating NULL-character. DER-format requires the length to be passed in client_key_len */ size_t client_key_len; /*!< Length of the buffer pointed to by client_key_pem. May be 0 for null-terminated pem */ esp_websocket_transport_t transport; /*!< Websocket transport type, see `esp_websocket_transport_t */ - char *subprotocol; /*!< Websocket subprotocol */ - char *user_agent; /*!< Websocket user-agent */ - char *headers; /*!< Websocket additional headers */ + const char *subprotocol; /*!< Websocket subprotocol */ + const char *user_agent; /*!< Websocket user-agent */ + const char *headers; /*!< Websocket additional headers */ int pingpong_timeout_sec; /*!< Period before connection is aborted due to no PONGs received */ bool disable_pingpong_discon; /*!< Disable auto-disconnect due to no PONG received within pingpong_timeout_sec */ bool use_global_ca_store; /*!< Use a global ca_store for all the connections in which this bool is set. */ From 9118e0f0449a955aa573672928c9f751840a5317 Mon Sep 17 00:00:00 2001 From: liuhan Date: Fri, 16 Apr 2021 17:23:18 +0800 Subject: [PATCH 53/65] transport: Add CONFI_WS_TRANSPORT for optimize the code size * Original commit: espressif/esp-idf@8b02c9026af32352c8c4ed23025fb42182db6cae --- components/esp_websocket_client/CMakeLists.txt | 8 ++++++++ components/esp_websocket_client/component.mk | 4 ++++ 2 files changed, 12 insertions(+) diff --git a/components/esp_websocket_client/CMakeLists.txt b/components/esp_websocket_client/CMakeLists.txt index de17d6fdd..af21d9d08 100644 --- a/components/esp_websocket_client/CMakeLists.txt +++ b/components/esp_websocket_client/CMakeLists.txt @@ -1,3 +1,11 @@ +if(NOT CONFIG_WS_TRANSPORT AND NOT CMAKE_BUILD_EARLY_EXPANSION) + message(STATUS "Websocket transport is disabled so the esp_websocket_client component will not be built") + # note: the component is still included in the build so it can become visible again in config + # without needing to re-run CMake. However no source or header files are built. + idf_component_register() + return() +endif() + idf_component_register(SRCS "esp_websocket_client.c" INCLUDE_DIRS "include" REQUIRES lwip esp-tls tcp_transport nghttp diff --git a/components/esp_websocket_client/component.mk b/components/esp_websocket_client/component.mk index e69de29bb..300fea3da 100644 --- a/components/esp_websocket_client/component.mk +++ b/components/esp_websocket_client/component.mk @@ -0,0 +1,4 @@ +ifdef CONFIG_WS_TRANSPORT +COMPONENT_SRCDIRS := . +COMPONENT_ADD_INCLUDEDIRS := include +endif From 665c520fafd2b95429796008a09924f216f02703 Mon Sep 17 00:00:00 2001 From: Jakob Hasse Date: Mon, 5 Jul 2021 14:33:56 +0800 Subject: [PATCH 54/65] [examples]: removed hyphens Replaced hyphens with underscores in examples project definition for all examples which had hyphens in their project name. dpp-enrollee is an exceptions because the name matches the project directory name while the project directory also contains hyphens. * Original commit: espressif/esp-idf@81e926620498e55beb8eb4bd9c4169e8f3338563 --- examples/protocols/websocket/CMakeLists.txt | 2 +- examples/protocols/websocket/Makefile | 2 +- examples/protocols/websocket/example_test.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/examples/protocols/websocket/CMakeLists.txt b/examples/protocols/websocket/CMakeLists.txt index f77ad23e5..320a49d19 100644 --- a/examples/protocols/websocket/CMakeLists.txt +++ b/examples/protocols/websocket/CMakeLists.txt @@ -7,4 +7,4 @@ cmake_minimum_required(VERSION 3.5) set(EXTRA_COMPONENT_DIRS $ENV{IDF_PATH}/examples/common_components/protocol_examples_common) include($ENV{IDF_PATH}/tools/cmake/project.cmake) -project(websocket-example) +project(websocket_example) diff --git a/examples/protocols/websocket/Makefile b/examples/protocols/websocket/Makefile index 063a0a099..9c0adb31c 100644 --- a/examples/protocols/websocket/Makefile +++ b/examples/protocols/websocket/Makefile @@ -2,7 +2,7 @@ # This is a project Makefile. It is assumed the directory this Makefile resides in is a # project subdirectory. # -PROJECT_NAME := websocket-example +PROJECT_NAME := websocket_example EXTRA_COMPONENT_DIRS = $(IDF_PATH)/examples/common_components/protocol_examples_common diff --git a/examples/protocols/websocket/example_test.py b/examples/protocols/websocket/example_test.py index 0955e7be9..2c99f384b 100644 --- a/examples/protocols/websocket/example_test.py +++ b/examples/protocols/websocket/example_test.py @@ -107,7 +107,7 @@ def test_examples_protocol_websocket(env, extra_data): """ dut1 = env.get_dut('websocket', 'examples/protocols/websocket', dut_class=ttfw_idf.ESP32DUT) # check and log bin size - binary_file = os.path.join(dut1.app.binary_path, 'websocket-example.bin') + binary_file = os.path.join(dut1.app.binary_path, 'websocket_example.bin') bin_size = os.path.getsize(binary_file) ttfw_idf.log_performance('websocket_bin_size', '{}KB'.format(bin_size // 1024)) From 19c0455b4dffbe27556066ee282f800271b0c88f Mon Sep 17 00:00:00 2001 From: Roland Dobai Date: Fri, 5 Nov 2021 15:38:25 +0100 Subject: [PATCH 55/65] Build & config: Remove leftover files from the unsupported "make" build system * Original commit: espressif/esp-idf@766aa5708443099f3f033b739cda0e1de101cca6 --- components/esp_websocket_client/component.mk | 4 ---- components/esp_websocket_client/test/component.mk | 4 ---- examples/protocols/websocket/Makefile | 9 --------- examples/protocols/websocket/main/component.mk | 0 4 files changed, 17 deletions(-) delete mode 100644 components/esp_websocket_client/component.mk delete mode 100644 components/esp_websocket_client/test/component.mk delete mode 100644 examples/protocols/websocket/Makefile delete mode 100644 examples/protocols/websocket/main/component.mk diff --git a/components/esp_websocket_client/component.mk b/components/esp_websocket_client/component.mk deleted file mode 100644 index 300fea3da..000000000 --- a/components/esp_websocket_client/component.mk +++ /dev/null @@ -1,4 +0,0 @@ -ifdef CONFIG_WS_TRANSPORT -COMPONENT_SRCDIRS := . -COMPONENT_ADD_INCLUDEDIRS := include -endif diff --git a/components/esp_websocket_client/test/component.mk b/components/esp_websocket_client/test/component.mk deleted file mode 100644 index 8c6eb513e..000000000 --- a/components/esp_websocket_client/test/component.mk +++ /dev/null @@ -1,4 +0,0 @@ -# -#Component Makefile -# -COMPONENT_ADD_LDFLAGS = -Wl,--whole-archive -l$(COMPONENT_NAME) -Wl,--no-whole-archive diff --git a/examples/protocols/websocket/Makefile b/examples/protocols/websocket/Makefile deleted file mode 100644 index 9c0adb31c..000000000 --- a/examples/protocols/websocket/Makefile +++ /dev/null @@ -1,9 +0,0 @@ -# -# This is a project Makefile. It is assumed the directory this Makefile resides in is a -# project subdirectory. -# -PROJECT_NAME := websocket_example - -EXTRA_COMPONENT_DIRS = $(IDF_PATH)/examples/common_components/protocol_examples_common - -include $(IDF_PATH)/make/project.mk diff --git a/examples/protocols/websocket/main/component.mk b/examples/protocols/websocket/main/component.mk deleted file mode 100644 index e69de29bb..000000000 From c4c323666ec74b3b2b49217265884458fed8959e Mon Sep 17 00:00:00 2001 From: Laukik Hase Date: Fri, 26 Nov 2021 10:44:48 +0530 Subject: [PATCH 56/65] docs: Fix spell and grammatical errors - PCNT - ESP-TLS - ESP WebSocket Client * Original commit: espressif/esp-idf@1df7f340bec9bdd0986218ddc58acf6ffa61fd86 --- docs/en/api-reference/protocols/esp_websocket_client.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/en/api-reference/protocols/esp_websocket_client.rst b/docs/en/api-reference/protocols/esp_websocket_client.rst index f54a9ab02..664b8f0b0 100644 --- a/docs/en/api-reference/protocols/esp_websocket_client.rst +++ b/docs/en/api-reference/protocols/esp_websocket_client.rst @@ -63,7 +63,7 @@ Configuration: .cert_pem = (const char *)websocket_org_pem_start, }; -.. note:: If you want to verify the server, then you need to provide a certificate in PEM format, and provide to ``cert_pem`` in :cpp:type:`websocket_client_config_t`. If no certficate is provided then the TLS connection will to default not requiring verification. +.. note:: If you want to verify the server, then you need to provide a certificate in PEM format, and provide to ``cert_pem`` in :cpp:type:`websocket_client_config_t`. If no certficate is provided then the TLS connection will default to not requiring verification. PEM certificate for this example could be extracted from an openssl `s_client` command connecting to websocket.org. In case a host operating system has `openssl` and `sed` packages installed, one could execute the following command to download and save the root or intermediate root certificate to a file (Note for Windows users: Both Linux like environment or Windows native packages may be used). From 525c70c0b23df2d6f80130c90e4bb3a1073f5518 Mon Sep 17 00:00:00 2001 From: Jakob Hasse Date: Thu, 18 Nov 2021 14:27:30 +0800 Subject: [PATCH 57/65] refactor (test_utils)!: separate file for memory check functions Memory check (leaks and heap tracing) functions for unit tests now have a separate file now and are renamed for more consistency. BREAKING CHANGE: renamed memory check function names which may be used in unit tests outside IDF. * Original commit: espressif/esp-idf@16514f93f06cd833306459d615458536a9f2e5cd --- .../test/test_websocket_client.c | 28 +++++++++---------- 1 file changed, 13 insertions(+), 15 deletions(-) diff --git a/components/esp_websocket_client/test/test_websocket_client.c b/components/esp_websocket_client/test/test_websocket_client.c index 5d96b4983..1c9e895a3 100644 --- a/components/esp_websocket_client/test/test_websocket_client.c +++ b/components/esp_websocket_client/test/test_websocket_client.c @@ -1,28 +1,26 @@ -// Copyright 2021 Espressif Systems (Shanghai) PTE LTD -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. +/* + * SPDX-FileCopyrightText: 2021 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Unlicense OR CC0-1.0 + * + * This test code is in the Public Domain (or CC0 licensed, at your option.) + * + * Unless required by applicable law or agreed to in writing, this + * software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR + * CONDITIONS OF ANY KIND, either express or implied. + */ #include #include #include #include "unity.h" -#include "test_utils.h" +#include "memory_checks.h" static void test_leak_setup(const char * file, long line) { printf("%s:%ld\n", file, line); - unity_reset_leak_checks(); + test_utils_record_free_mem(); } TEST_CASE("websocket init and deinit", "[websocket][leaks=0]") From 46bd32d9529a09a32a87ebb9bbc948d0156f9b05 Mon Sep 17 00:00:00 2001 From: Suren Gabrielyan Date: Thu, 2 Dec 2021 16:46:26 +0400 Subject: [PATCH 58/65] websocket: removed deprecated API "esp_websocket_client_send" Closes IDF-1470 * Original commit: espressif/esp-idf@7f6ab93f7e52bddaf4c030d7337ea5574f33381d --- .../esp_websocket_client.c | 23 +++---------- .../include/esp_websocket_client.h | 32 +++---------------- 2 files changed, 10 insertions(+), 45 deletions(-) diff --git a/components/esp_websocket_client/esp_websocket_client.c b/components/esp_websocket_client/esp_websocket_client.c index 2f6405d47..0b0dbfe21 100644 --- a/components/esp_websocket_client/esp_websocket_client.c +++ b/components/esp_websocket_client/esp_websocket_client.c @@ -1,16 +1,8 @@ -// Copyright 2015-2018 Espressif Systems (Shanghai) PTE LTD -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at - -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. +/* + * SPDX-FileCopyrightText: 2015-2021 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ #include @@ -829,11 +821,6 @@ int esp_websocket_client_send_text(esp_websocket_client_handle_t client, const c return esp_websocket_client_send_with_opcode(client, WS_TRANSPORT_OPCODES_TEXT, (const uint8_t *)data, len, timeout); } -int esp_websocket_client_send(esp_websocket_client_handle_t client, const char *data, int len, TickType_t timeout) -{ - return esp_websocket_client_send_with_opcode(client, WS_TRANSPORT_OPCODES_BINARY, (const uint8_t *)data, len, timeout); -} - int esp_websocket_client_send_bin(esp_websocket_client_handle_t client, const char *data, int len, TickType_t timeout) { return esp_websocket_client_send_with_opcode(client, WS_TRANSPORT_OPCODES_BINARY, (const uint8_t *)data, len, timeout); diff --git a/components/esp_websocket_client/include/esp_websocket_client.h b/components/esp_websocket_client/include/esp_websocket_client.h index 0ed635346..1431263b1 100644 --- a/components/esp_websocket_client/include/esp_websocket_client.h +++ b/components/esp_websocket_client/include/esp_websocket_client.h @@ -1,16 +1,8 @@ -// Copyright 2015-2018 Espressif Systems (Shanghai) PTE LTD -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at - -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. +/* + * SPDX-FileCopyrightText: 2015-2021 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ #ifndef _ESP_WEBSOCKET_CLIENT_H_ #define _ESP_WEBSOCKET_CLIENT_H_ @@ -169,20 +161,6 @@ esp_err_t esp_websocket_client_stop(esp_websocket_client_handle_t client); */ esp_err_t esp_websocket_client_destroy(esp_websocket_client_handle_t client); -/** - * @brief Generic write data to the WebSocket connection; defaults to binary send - * - * @param[in] client The client - * @param[in] data The data - * @param[in] len The length - * @param[in] timeout Write data timeout in RTOS ticks - * - * @return - * - Number of data was sent - * - (-1) if any errors - */ -int esp_websocket_client_send(esp_websocket_client_handle_t client, const char *data, int len, TickType_t timeout); - /** * @brief Write binary data to the WebSocket connection (data send with WS OPCODE=02, i.e. binary) * From bece6e7045133c2f86fc54dc621a112c3778430f Mon Sep 17 00:00:00 2001 From: Mahavir Jain Date: Fri, 31 Dec 2021 13:50:55 +0530 Subject: [PATCH 59/65] Add http_parser (new component) dependency * Original commit: espressif/esp-idf@8e94cf2bb1498e94045e73e649f1046111fc6f9f --- components/esp_websocket_client/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/esp_websocket_client/CMakeLists.txt b/components/esp_websocket_client/CMakeLists.txt index af21d9d08..ee57f73d2 100644 --- a/components/esp_websocket_client/CMakeLists.txt +++ b/components/esp_websocket_client/CMakeLists.txt @@ -8,5 +8,5 @@ endif() idf_component_register(SRCS "esp_websocket_client.c" INCLUDE_DIRS "include" - REQUIRES lwip esp-tls tcp_transport nghttp + REQUIRES lwip esp-tls tcp_transport http_parser PRIV_REQUIRES esp_timer) From 755f16222fe057b627d1cc89507ba271a7b74744 Mon Sep 17 00:00:00 2001 From: David Cermak Date: Fri, 14 Jan 2022 14:34:24 +0100 Subject: [PATCH 60/65] ci/websockets: Run ws-client example test on ethernet runners * Original commit: espressif/esp-idf@2649413ae863513412f50d6dc2b8886ce7a45f02 --- examples/protocols/websocket/example_test.py | 4 ++-- examples/protocols/websocket/sdkconfig.ci | 9 +++++++++ 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/examples/protocols/websocket/example_test.py b/examples/protocols/websocket/example_test.py index 2c99f384b..8dc846dd0 100644 --- a/examples/protocols/websocket/example_test.py +++ b/examples/protocols/websocket/example_test.py @@ -97,11 +97,11 @@ def test_recv_long_msg(dut, websocket, msg_len, repeats): \nreceived: {}\nwith length {}'.format(send_msg, len(send_msg), recv_msg, len(recv_msg))) -@ttfw_idf.idf_example_test(env_tag='Example_WIFI_Protocols') +@ttfw_idf.idf_example_test(env_tag='Example_EthKitV1') def test_examples_protocol_websocket(env, extra_data): """ steps: - 1. join AP + 1. obtain IP address 2. connect to uri specified in the config 3. send and receive data """ diff --git a/examples/protocols/websocket/sdkconfig.ci b/examples/protocols/websocket/sdkconfig.ci index 3ce203d46..9c2dea374 100644 --- a/examples/protocols/websocket/sdkconfig.ci +++ b/examples/protocols/websocket/sdkconfig.ci @@ -1,2 +1,11 @@ CONFIG_WEBSOCKET_URI_FROM_STDIN=y CONFIG_WEBSOCKET_URI_FROM_STRING=n +CONFIG_EXAMPLE_CONNECT_ETHERNET=y +CONFIG_EXAMPLE_CONNECT_WIFI=n +CONFIG_EXAMPLE_USE_INTERNAL_ETHERNET=y +CONFIG_EXAMPLE_ETH_PHY_IP101=y +CONFIG_EXAMPLE_ETH_MDC_GPIO=23 +CONFIG_EXAMPLE_ETH_MDIO_GPIO=18 +CONFIG_EXAMPLE_ETH_PHY_RST_GPIO=5 +CONFIG_EXAMPLE_ETH_PHY_ADDR=1 +CONFIG_EXAMPLE_CONNECT_IPV6=y From 59e82695e7926dce3c28009d3dee29f16610a34a Mon Sep 17 00:00:00 2001 From: David Cermak Date: Fri, 14 Jan 2022 18:11:59 +0100 Subject: [PATCH 61/65] ws_client: Optimize example test payloads and timeouts Important update: NO_DATA_TIMEOUT_SEC=5, as some ws-servers typically send pings in 30s or 10s intervals, so we might never fire shutdown test * Original commit: espressif/esp-idf@323622be64a74c885970b9eb2b1942a0c7756833 --- examples/protocols/websocket/example_test.py | 2 +- examples/protocols/websocket/main/websocket_example.c | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/examples/protocols/websocket/example_test.py b/examples/protocols/websocket/example_test.py index 8dc846dd0..17ad3ccc1 100644 --- a/examples/protocols/websocket/example_test.py +++ b/examples/protocols/websocket/example_test.py @@ -68,7 +68,7 @@ class Websocket(object): def test_echo(dut): dut.expect('WEBSOCKET_EVENT_CONNECTED') - for i in range(0, 10): + for i in range(0, 5): dut.expect(re.compile(r'Received=hello (\d)'), timeout=30) print('All echos received') diff --git a/examples/protocols/websocket/main/websocket_example.c b/examples/protocols/websocket/main/websocket_example.c index a1cb08c56..50515e265 100644 --- a/examples/protocols/websocket/main/websocket_example.c +++ b/examples/protocols/websocket/main/websocket_example.c @@ -24,7 +24,7 @@ #include "esp_websocket_client.h" #include "esp_event.h" -#define NO_DATA_TIMEOUT_SEC 10 +#define NO_DATA_TIMEOUT_SEC 5 static const char *TAG = "WEBSOCKET"; @@ -115,7 +115,7 @@ static void websocket_app_start(void) xTimerStart(shutdown_signal_timer, portMAX_DELAY); char data[32]; int i = 0; - while (i < 10) { + while (i < 5) { if (esp_websocket_client_is_connected(client)) { int len = sprintf(data, "hello %04d", i++); ESP_LOGI(TAG, "Sending %s", data); From fc7ed90d74ddafed02089924c175c3f29c5a2b26 Mon Sep 17 00:00:00 2001 From: dizcza Date: Wed, 19 Jan 2022 11:52:55 +0200 Subject: [PATCH 62/65] websocket: Updated Kconfig to use 'echo.websocket.events' echo server Updated README to show how to run websocket echo server using Flask Merges https://github.com/espressif/esp-idf/pull/8262 * Original commit: espressif/esp-idf@23598dfcec0ae4788631f619fac9499fea5adfdb --- examples/protocols/websocket/README.md | 34 ++++++++++++++++++- .../websocket/main/Kconfig.projbuild | 2 +- 2 files changed, 34 insertions(+), 2 deletions(-) diff --git a/examples/protocols/websocket/README.md b/examples/protocols/websocket/README.md index c434a7c13..791e9f430 100644 --- a/examples/protocols/websocket/README.md +++ b/examples/protocols/websocket/README.md @@ -36,7 +36,7 @@ I (4472) tcpip_adapter: eth ip: 192.168.2.137, mask: 255.255.255.0, gw: 192.168. I (4472) example_connect: Connected to Ethernet I (4472) example_connect: IPv4 address: 192.168.2.137 I (4472) example_connect: IPv6 address: fe80:0000:0000:0000:bedd:c2ff:fed4:a92b -I (4482) WEBSOCKET: Connecting to ws://echo.websocket.org... +I (4482) WEBSOCKET: Connecting to ws://echo.websocket.events... I (5012) WEBSOCKET: WEBSOCKET_EVENT_CONNECTED I (5492) WEBSOCKET: Sending hello 0000 I (6052) WEBSOCKET: WEBSOCKET_EVENT_DATA @@ -56,3 +56,35 @@ W (9162) WEBSOCKET: Received=hello 0003 ``` + +## Python Flask echo server + +By default, the `ws://echo.websocket.events` endpoint is used. You can setup a Python websocket echo server locally and try the `ws://:5000` endpoint. To do this, install Flask-sock Python package + +``` +pip install flask-sock +``` + +and start a Flask websocket echo server locally by executing the following Python code: + +```python +from flask import Flask +from flask_sock import Sock + +app = Flask(__name__) +sock = Sock(app) + + +@sock.route('/') +def echo(ws): + while True: + data = ws.receive() + ws.send(data) + + +if __name__ == '__main__': + # To run your Flask + WebSocket server in production you can use Gunicorn: + # gunicorn -b 0.0.0.0:5000 --workers 4 --threads 100 module:app + app.run(host="0.0.0.0", debug=True) +``` + diff --git a/examples/protocols/websocket/main/Kconfig.projbuild b/examples/protocols/websocket/main/Kconfig.projbuild index 0613b9033..d146488e3 100644 --- a/examples/protocols/websocket/main/Kconfig.projbuild +++ b/examples/protocols/websocket/main/Kconfig.projbuild @@ -16,7 +16,7 @@ menu "Example Configuration" config WEBSOCKET_URI string "Websocket endpoint URI" depends on WEBSOCKET_URI_FROM_STRING - default "ws://echo.websocket.org" + default "ws://echo.websocket.events" help URL of websocket endpoint this example connects to and sends echo From 8ce791e969a30802f8098d3e68d34420e670fa3a Mon Sep 17 00:00:00 2001 From: gabsuren Date: Mon, 31 Jan 2022 16:43:07 +0400 Subject: [PATCH 63/65] websocket: Added configs `reconnect_timeout_ms` and `network_timeout_ms` Closes https://github.com/espressif/esp-idf/issues/8263 * Original commit: espressif/esp-idf@6c26d6520311f83c2ebe852a487c36185a429a69 --- .../esp_websocket_client/esp_websocket_client.c | 17 ++++++++++++++--- .../include/esp_websocket_client.h | 2 ++ 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/components/esp_websocket_client/esp_websocket_client.c b/components/esp_websocket_client/esp_websocket_client.c index 0b0dbfe21..798a320da 100644 --- a/components/esp_websocket_client/esp_websocket_client.c +++ b/components/esp_websocket_client/esp_websocket_client.c @@ -153,7 +153,6 @@ static esp_err_t esp_websocket_client_abort_connection(esp_websocket_client_hand esp_transport_close(client->transport); if (client->config->auto_reconnect) { - client->wait_timeout_ms = WEBSOCKET_RECONNECT_TIMEOUT_MS; client->reconnect_tick_ms = _tick_get_ms(); ESP_LOGI(TAG, "Reconnect after %d ms", client->wait_timeout_ms); } @@ -222,7 +221,7 @@ static esp_err_t esp_websocket_client_set_config(esp_websocket_client_handle_t c ESP_WS_CLIENT_MEM_CHECK(TAG, cfg->headers, return ESP_ERR_NO_MEM); } - cfg->network_timeout_ms = WEBSOCKET_NETWORK_TIMEOUT_MS; + cfg->user_context = config->user_context; cfg->auto_reconnect = true; if (config->disable_auto_reconnect) { @@ -237,6 +236,13 @@ static esp_err_t esp_websocket_client_set_config(esp_websocket_client_handle_t c cfg->pingpong_timeout_sec = WEBSOCKET_PINGPONG_TIMEOUT_SEC; } + if (config->network_timeout_ms <= 0) { + cfg->network_timeout_ms = WEBSOCKET_NETWORK_TIMEOUT_MS; + ESP_LOGW(TAG, "`network_timeout_ms` is not set, or it is less than or equal to zero, using default time out %d (milliseconds)", WEBSOCKET_NETWORK_TIMEOUT_MS); + } else { + cfg->network_timeout_ms = config->network_timeout_ms; + } + if (config->ping_interval_sec == 0) { cfg->ping_interval_sec = WEBSOCKET_PING_INTERVAL_SEC; } else { @@ -373,7 +379,12 @@ esp_websocket_client_handle_t esp_websocket_client_init(const esp_websocket_clie if (config->skip_cert_common_name_check) { esp_transport_ssl_skip_common_name_check(ssl); } - + if (config->reconnect_timeout_ms <= 0) { + client->wait_timeout_ms = WEBSOCKET_RECONNECT_TIMEOUT_MS; + ESP_LOGW(TAG, "`reconnect_timeout_ms` is not set, or it is less than or equal to zero, using default time out %d (milliseconds)", WEBSOCKET_RECONNECT_TIMEOUT_MS); + } else { + client->wait_timeout_ms = config->reconnect_timeout_ms; + } esp_transport_handle_t wss = esp_transport_ws_init(ssl); ESP_WS_CLIENT_MEM_CHECK(TAG, wss, goto _websocket_init_fail); diff --git a/components/esp_websocket_client/include/esp_websocket_client.h b/components/esp_websocket_client/include/esp_websocket_client.h index 1431263b1..1946f2552 100644 --- a/components/esp_websocket_client/include/esp_websocket_client.h +++ b/components/esp_websocket_client/include/esp_websocket_client.h @@ -92,6 +92,8 @@ typedef struct { int keep_alive_idle; /*!< Keep-alive idle time. Default is 5 (second) */ int keep_alive_interval; /*!< Keep-alive interval time. Default is 5 (second) */ int keep_alive_count; /*!< Keep-alive packet retry send count. Default is 3 counts */ + int reconnect_timeout_ms; /*!< Reconnect after this value in miliseconds if disable_auto_reconnect is not enabled (defaults to 10s) */ + int network_timeout_ms; /*!< Abort network operation if it is not completed after this value, in milliseconds (defaults to 10s) */ size_t ping_interval_sec; /*!< Websocket ping interval, defaults to 10 seconds if not set */ struct ifreq *if_name; /*!< The name of interface for data to go through. Use the default interface without setting */ } esp_websocket_client_config_t; From b3c777ad432f1407a8db06cca50c4f2944c31bd2 Mon Sep 17 00:00:00 2001 From: Darian Leung Date: Tue, 8 Feb 2022 17:39:38 +0800 Subject: [PATCH 64/65] freertos: Remove legacy data types This commit removes the usage of all legacy FreeRTOS data types that are exposed via configENABLE_BACKWARD_COMPATIBILITY. Legacy types can still be used by enabling CONFIG_FREERTOS_ENABLE_BACKWARD_COMPATIBILITY. * Original commit: espressif/esp-idf@57fd78f5baf93a368a82cf4b2e00ca17ffc09115 --- components/esp_websocket_client/esp_websocket_client.c | 6 +++--- examples/protocols/websocket/main/websocket_example.c | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/components/esp_websocket_client/esp_websocket_client.c b/components/esp_websocket_client/esp_websocket_client.c index 798a320da..c7f09ea71 100644 --- a/components/esp_websocket_client/esp_websocket_client.c +++ b/components/esp_websocket_client/esp_websocket_client.c @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2015-2021 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2015-2022 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -105,7 +105,7 @@ struct esp_websocket_client { bool run; bool wait_for_pong_resp; EventGroupHandle_t status_bits; - xSemaphoreHandle lock; + SemaphoreHandle_t lock; char *rx_buffer; char *tx_buffer; int buffer_size; @@ -696,7 +696,7 @@ static void esp_websocket_client_task(void *pv) } } else if (WEBSOCKET_STATE_WAIT_TIMEOUT == client->state) { // waiting for reconnecting... - vTaskDelay(client->wait_timeout_ms / 2 / portTICK_RATE_MS); + vTaskDelay(client->wait_timeout_ms / 2 / portTICK_PERIOD_MS); } else if (WEBSOCKET_STATE_CLOSING == client->state && (CLOSE_FRAME_SENT_BIT & xEventGroupGetBits(client->status_bits))) { ESP_LOGD(TAG, " Waiting for TCP connection to be closed by the server"); diff --git a/examples/protocols/websocket/main/websocket_example.c b/examples/protocols/websocket/main/websocket_example.c index 50515e265..1cabb3962 100644 --- a/examples/protocols/websocket/main/websocket_example.c +++ b/examples/protocols/websocket/main/websocket_example.c @@ -121,7 +121,7 @@ static void websocket_app_start(void) ESP_LOGI(TAG, "Sending %s", data); esp_websocket_client_send_text(client, data, len, portMAX_DELAY); } - vTaskDelay(1000 / portTICK_RATE_MS); + vTaskDelay(1000 / portTICK_PERIOD_MS); } xSemaphoreTake(shutdown_sema, portMAX_DELAY); From 80c3cf0f028d7adadc98aa1246c27412f4007f42 Mon Sep 17 00:00:00 2001 From: gabsuren Date: Tue, 1 Mar 2022 13:42:35 +0400 Subject: [PATCH 65/65] websocket: Initial version based on IDF 5.0 --- .github/workflows/build-websockets.yml | 26 +++ .../publish-docs-component-websockets.yml | 39 ++++ components/esp_websocket_client/.gitignore | 93 ++++++++ components/esp_websocket_client/LICENSE | 202 ++++++++++++++++++ components/esp_websocket_client/README.md | 11 + components/esp_websocket_client/docs/Doxyfile | 75 +++++++ .../esp_websocket_client/docs/conf_common.py | 21 ++ .../esp_websocket_client/docs/en/conf.py | 24 +++ .../esp_websocket_client/docs/en/index.rst | 2 +- .../esp_websocket_client/docs/generate_docs | 27 +++ .../examples}/CMakeLists.txt | 6 +- .../esp_websocket_client/examples}/README.md | 3 +- .../examples}/example_test.py | 0 .../examples}/main/CMakeLists.txt | 0 .../examples}/main/Kconfig.projbuild | 0 .../examples}/main/websocket_example.c | 0 .../examples}/sdkconfig.ci | 0 .../esp_websocket_client/idf_component.yml | 5 + 18 files changed, 528 insertions(+), 6 deletions(-) create mode 100644 .github/workflows/build-websockets.yml create mode 100644 .github/workflows/publish-docs-component-websockets.yml create mode 100644 components/esp_websocket_client/.gitignore create mode 100644 components/esp_websocket_client/LICENSE create mode 100644 components/esp_websocket_client/README.md create mode 100644 components/esp_websocket_client/docs/Doxyfile create mode 100644 components/esp_websocket_client/docs/conf_common.py create mode 100644 components/esp_websocket_client/docs/en/conf.py rename docs/en/api-reference/protocols/esp_websocket_client.rst => components/esp_websocket_client/docs/en/index.rst (99%) create mode 100755 components/esp_websocket_client/docs/generate_docs rename {examples/protocols/websocket => components/esp_websocket_client/examples}/CMakeLists.txt (55%) rename {examples/protocols/websocket => components/esp_websocket_client/examples}/README.md (91%) rename {examples/protocols/websocket => components/esp_websocket_client/examples}/example_test.py (100%) rename {examples/protocols/websocket => components/esp_websocket_client/examples}/main/CMakeLists.txt (100%) rename {examples/protocols/websocket => components/esp_websocket_client/examples}/main/Kconfig.projbuild (100%) rename {examples/protocols/websocket => components/esp_websocket_client/examples}/main/websocket_example.c (100%) rename {examples/protocols/websocket => components/esp_websocket_client/examples}/sdkconfig.ci (100%) create mode 100644 components/esp_websocket_client/idf_component.yml diff --git a/.github/workflows/build-websockets.yml b/.github/workflows/build-websockets.yml new file mode 100644 index 000000000..4a9eea4ff --- /dev/null +++ b/.github/workflows/build-websockets.yml @@ -0,0 +1,26 @@ +name: Build Websockets + +on: [push, pull_request] + +jobs: + build: + strategy: + matrix: + idf_ver: ["latest"] + idf_target: ["esp32"] + + runs-on: ubuntu-20.04 + container: espressif/idf:${{ matrix.idf_ver }} + steps: + - name: Checkout esp-protocols + uses: actions/checkout@master + with: + path: esp-protocols + - name: Build ${{ matrix.example }} with IDF-${{ matrix.idf_ver }} for ${{ matrix.idf_target }} + env: + IDF_TARGET: ${{ matrix.idf_target }} + shell: bash + run: | + . ${IDF_PATH}/export.sh + cd $GITHUB_WORKSPACE/esp-protocols/components/esp_websocket_client/examples/ + idf.py build diff --git a/.github/workflows/publish-docs-component-websockets.yml b/.github/workflows/publish-docs-component-websockets.yml new file mode 100644 index 000000000..f51df78c6 --- /dev/null +++ b/.github/workflows/publish-docs-component-websockets.yml @@ -0,0 +1,39 @@ +name: Docs and Publish Websockets + +on: + push: + branches: + - master + +jobs: + docs_build: + name: Docs-Build-And-Upload + runs-on: ubuntu-latest + + steps: + - name: Checkout esp-protocols + uses: actions/checkout@master + with: + persist-credentials: false + fetch-depth: 0 + + - name: Generate docs + run: | + sudo apt-get update + sudo apt-get -y install doxygen clang python3-pip git + sudo git clone https://github.com/espressif/esp-idf + python -m pip install breathe recommonmark + python -m pip install -r esp-idf/docs/requirements.txt + cd $GITHUB_WORKSPACE/components/esp_websocket_client/docs + ./generate_docs + mkdir -p $GITHUB_WORKSPACE/docs/esp_websocket_client + cp -r html/. $GITHUB_WORKSPACE/docs/esp_websocket_client + cd $GITHUB_WORKSPACE/docs + touch .nojekyll + echo 'esp-websocket-client' >> index.html + + - name: Deploy generated docs. + uses: JamesIves/github-pages-deploy-action@4.1.5 + with: + branch: gh-pages + folder: docs diff --git a/components/esp_websocket_client/.gitignore b/components/esp_websocket_client/.gitignore new file mode 100644 index 000000000..5aa50beb2 --- /dev/null +++ b/components/esp_websocket_client/.gitignore @@ -0,0 +1,93 @@ +.config +*.o +*.pyc + +# gtags +GTAGS +GRTAGS +GPATH + +# emacs +.dir-locals.el + +# emacs temp file suffixes +*~ +.#* +\#*# + +# eclipse setting +.settings + +# MacOS directory files +.DS_Store + +# Components Unit Test Apps files +components/**/build +components/**/sdkconfig +components/**/sdkconfig.old + +# Example project files +examples/**/sdkconfig +examples/**/sdkconfig.old +examples/**/build + +# Doc build artifacts +docs/_build/ +docs/doxygen_sqlite3.db + +# Downloaded font files +docs/_static/DejaVuSans.ttf +docs/_static/NotoSansSC-Regular.otf + +# Unit test app files +tools/unit-test-app/sdkconfig +tools/unit-test-app/sdkconfig.old +tools/unit-test-app/build +tools/unit-test-app/builds +tools/unit-test-app/output +tools/unit-test-app/test_configs + +# Unit Test CMake compile log folder +log_ut_cmake + +# test application build files +test/**/build +test/**/sdkconfig +test/**/sdkconfig.old + +# IDF monitor test +tools/test_idf_monitor/outputs + +TEST_LOGS + +# gcov coverage reports +*.gcda +*.gcno +coverage.info +coverage_report/ + +test_multi_heap_host + +# VS Code Settings +.vscode/ + +# VIM files +*.swp +*.swo + +# Clion IDE CMake build & config +.idea/ +cmake-build-*/ + +# Results for the checking of the Python coding style and static analysis +.mypy_cache +flake8_output.txt + +# ESP-IDF default build directory name +build + +# lock files for examples and components +dependencies.lock + +# ignore generated docs +docs/html \ No newline at end of file diff --git a/components/esp_websocket_client/LICENSE b/components/esp_websocket_client/LICENSE new file mode 100644 index 000000000..d64569567 --- /dev/null +++ b/components/esp_websocket_client/LICENSE @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/components/esp_websocket_client/README.md b/components/esp_websocket_client/README.md new file mode 100644 index 000000000..11f5b4891 --- /dev/null +++ b/components/esp_websocket_client/README.md @@ -0,0 +1,11 @@ +# ESP WEBSOCKET CLIENT + +The `esp-websocket_client` component is a managed component for `esp-idf` that contains implementation of [WebSocket protocol client](https://datatracker.ietf.org/doc/html/rfc6455) for ESP32 + +## Examples + +Get started with example test [Example](examples/README.md): + +## Documentation + +* View the full [html documentation](https://espressif.github.io/esp-protocols/esp_websocket_client/index.html) diff --git a/components/esp_websocket_client/docs/Doxyfile b/components/esp_websocket_client/docs/Doxyfile new file mode 100644 index 000000000..b3ce4ddb6 --- /dev/null +++ b/components/esp_websocket_client/docs/Doxyfile @@ -0,0 +1,75 @@ +# This is Doxygen configuration file +# +# Doxygen provides over 260 configuration statements +# To make this file easier to follow, +# it contains only statements that are non-default +# +# NOTE: +# It is recommended not to change defaults unless specifically required +# Test any changes how they affect generated documentation +# Make sure that correct warnings are generated to flag issues with documented code +# +# For the complete list of configuration statements see: +# http://doxygen.nl/manual/config.html + + +PROJECT_NAME = "ESP Protocols Programming Guide" + +## The 'INPUT' statement below is used as input by script 'gen-df-input.py' +## to automatically generate API reference list files heder_file.inc +## These files are placed in '_inc' directory +## and used to include in API reference documentation + +INPUT = \ + $(PROJECT_PATH)/include/esp_websocket_client.h + +## Get warnings for functions that have no documentation for their parameters or return value +## +WARN_NO_PARAMDOC = YES + +## Enable preprocessing and remove __attribute__(...) expressions from the INPUT files +## +ENABLE_PREPROCESSING = YES +MACRO_EXPANSION = YES +EXPAND_ONLY_PREDEF = YES +PREDEFINED = \ + $(ENV_DOXYGEN_DEFINES) \ + __DOXYGEN__=1 \ + __attribute__(x)= \ + _Static_assert()= \ + IDF_DEPRECATED(X)= \ + IRAM_ATTR= \ + configSUPPORT_DYNAMIC_ALLOCATION=1 \ + configSUPPORT_STATIC_ALLOCATION=1 \ + configQUEUE_REGISTRY_SIZE=1 \ + configUSE_RECURSIVE_MUTEXES=1 \ + configTHREAD_LOCAL_STORAGE_DELETE_CALLBACKS=1 \ + configNUM_THREAD_LOCAL_STORAGE_POINTERS=1 \ + configUSE_APPLICATION_TASK_TAG=1 \ + configTASKLIST_INCLUDE_COREID=1 \ + "ESP_EVENT_DECLARE_BASE(x)=extern esp_event_base_t x" + +## Do not complain about not having dot +## +HAVE_DOT = NO + +## Generate XML that is required for Breathe +## +GENERATE_XML = YES +XML_OUTPUT = xml + +GENERATE_HTML = NO +HAVE_DOT = NO +GENERATE_LATEX = NO +GENERATE_MAN = YES +GENERATE_RTF = NO + +## Skip distracting progress messages +## +QUIET = YES + +## Enable Section Tags for conditional documentation +## +ENABLED_SECTIONS += \ + DOC_EXCLUDE_HEADER_SECTION \ ## To conditionally remove doc sections from IDF source files without affecting documentation in upstream files. + DOC_SINGLE_GROUP ## To conditionally remove groups from the documentation and create a 'flat' document without affecting documentation in upstream files. diff --git a/components/esp_websocket_client/docs/conf_common.py b/components/esp_websocket_client/docs/conf_common.py new file mode 100644 index 000000000..b22b3d503 --- /dev/null +++ b/components/esp_websocket_client/docs/conf_common.py @@ -0,0 +1,21 @@ +from esp_docs.conf_docs import * # noqa: F403,F401 + +extensions += ['sphinx_copybutton', + # Needed as a trigger for running doxygen + 'esp_docs.esp_extensions.dummy_build_system', + 'esp_docs.esp_extensions.run_doxygen', + ] + +# link roles config +github_repo = 'espressif/esp-idf' + +# context used by sphinx_idf_theme +html_context['github_user'] = 'espressif' +html_context['github_repo'] = 'esp-docs' + +# Extra options required by sphinx_idf_theme +project_slug = 'esp-idf' # >=5.0 +versions_url = 'https://dl.espressif.com/dl/esp-idf/idf_versions.js' + +idf_targets = ['esp32'] +languages = ['en'] diff --git a/components/esp_websocket_client/docs/en/conf.py b/components/esp_websocket_client/docs/en/conf.py new file mode 100644 index 000000000..7690761d9 --- /dev/null +++ b/components/esp_websocket_client/docs/en/conf.py @@ -0,0 +1,24 @@ +# -*- coding: utf-8 -*- +# +# English Language RTD & Sphinx config file +# +# Uses ../conf_common.py for most non-language-specific settings. + +# Importing conf_common adds all the non-language-specific +# parts to this conf module + +try: + from conf_common import * # noqa: F403,F401 +except ImportError: + import os + import sys + sys.path.insert(0, os.path.abspath('../')) + from conf_common import * # noqa: F403,F401 + +# General information about the project. +project = u'ESP-Docs' +copyright = u'2016 - 2022, Espressif Systems (Shanghai) Co., Ltd' + +# The language for content autogenerated by Sphinx. Refer to documentation +# for a list of supported languages. +language = 'en' diff --git a/docs/en/api-reference/protocols/esp_websocket_client.rst b/components/esp_websocket_client/docs/en/index.rst similarity index 99% rename from docs/en/api-reference/protocols/esp_websocket_client.rst rename to components/esp_websocket_client/docs/en/index.rst index 664b8f0b0..7c3abd970 100644 --- a/docs/en/api-reference/protocols/esp_websocket_client.rst +++ b/components/esp_websocket_client/docs/en/index.rst @@ -109,7 +109,7 @@ Limitations and Known Issues Application Example ------------------- -A simple WebSocket example that uses esp_websocket_client to establish a websocket connection and send/receive data with the `websocket.org `_ server can be found here: :example:`protocols/websocket`. +A simple WebSocket example that uses esp_websocket_client to establish a websocket connection and send/receive data with the `websocket.org `_ server can be found here: :example:`example <../examples>`. Sending Text Data ^^^^^^^^^^^^^^^^^ diff --git a/components/esp_websocket_client/docs/generate_docs b/components/esp_websocket_client/docs/generate_docs new file mode 100755 index 000000000..e60c1a8f9 --- /dev/null +++ b/components/esp_websocket_client/docs/generate_docs @@ -0,0 +1,27 @@ +build-docs --target esp32 --language en + +cp -rf _build/en/esp32/html . +rm -rf _build __pycache__ + +# Modifes some version and target fields of index.html +echo "" >> html/index.html + diff --git a/examples/protocols/websocket/CMakeLists.txt b/components/esp_websocket_client/examples/CMakeLists.txt similarity index 55% rename from examples/protocols/websocket/CMakeLists.txt rename to components/esp_websocket_client/examples/CMakeLists.txt index 320a49d19..3496c6a20 100644 --- a/examples/protocols/websocket/CMakeLists.txt +++ b/components/esp_websocket_client/examples/CMakeLists.txt @@ -1,10 +1,10 @@ -# The following four lines of boilerplate have to be in your project's CMakeLists +# The following lines of boilerplate have to be in your project's CMakeLists # in this exact order for cmake to work correctly cmake_minimum_required(VERSION 3.5) -# (Not part of the boilerplate) +set(EXTRA_COMPONENT_DIRS "../..") # This example uses an extra component for common functions such as Wi-Fi and Ethernet connection. -set(EXTRA_COMPONENT_DIRS $ENV{IDF_PATH}/examples/common_components/protocol_examples_common) +list(APPEND EXTRA_COMPONENT_DIRS $ENV{IDF_PATH}/examples/common_components/protocol_examples_common) include($ENV{IDF_PATH}/tools/cmake/project.cmake) project(websocket_example) diff --git a/examples/protocols/websocket/README.md b/components/esp_websocket_client/examples/README.md similarity index 91% rename from examples/protocols/websocket/README.md rename to components/esp_websocket_client/examples/README.md index 791e9f430..a14c18ab0 100644 --- a/examples/protocols/websocket/README.md +++ b/components/esp_websocket_client/examples/README.md @@ -1,6 +1,5 @@ # Websocket Sample application -(See the README.md file in the upper level 'examples' directory for more information about examples.) This example will shows how to set up and communicate over a websocket. ## How to Use Example @@ -12,7 +11,7 @@ This example can be executed on any ESP32 board, the only required interface is ### Configure the project * Open the project configuration menu (`idf.py menuconfig`) -* Configure Wi-Fi or Ethernet under "Example Connection Configuration" menu. See "Establishing Wi-Fi or Ethernet Connection" section in [examples/protocols/README.md](../../README.md) for more details. +* Configure Wi-Fi or Ethernet under "Example Connection Configuration" menu. * Configure the websocket endpoint URI under "Example Configuration", if "WEBSOCKET_URI_FROM_STDIN" is selected then the example application will connect to the URI it reads from stdin (used for testing) ### Build and Flash diff --git a/examples/protocols/websocket/example_test.py b/components/esp_websocket_client/examples/example_test.py similarity index 100% rename from examples/protocols/websocket/example_test.py rename to components/esp_websocket_client/examples/example_test.py diff --git a/examples/protocols/websocket/main/CMakeLists.txt b/components/esp_websocket_client/examples/main/CMakeLists.txt similarity index 100% rename from examples/protocols/websocket/main/CMakeLists.txt rename to components/esp_websocket_client/examples/main/CMakeLists.txt diff --git a/examples/protocols/websocket/main/Kconfig.projbuild b/components/esp_websocket_client/examples/main/Kconfig.projbuild similarity index 100% rename from examples/protocols/websocket/main/Kconfig.projbuild rename to components/esp_websocket_client/examples/main/Kconfig.projbuild diff --git a/examples/protocols/websocket/main/websocket_example.c b/components/esp_websocket_client/examples/main/websocket_example.c similarity index 100% rename from examples/protocols/websocket/main/websocket_example.c rename to components/esp_websocket_client/examples/main/websocket_example.c diff --git a/examples/protocols/websocket/sdkconfig.ci b/components/esp_websocket_client/examples/sdkconfig.ci similarity index 100% rename from examples/protocols/websocket/sdkconfig.ci rename to components/esp_websocket_client/examples/sdkconfig.ci diff --git a/components/esp_websocket_client/idf_component.yml b/components/esp_websocket_client/idf_component.yml new file mode 100644 index 000000000..474364171 --- /dev/null +++ b/components/esp_websocket_client/idf_component.yml @@ -0,0 +1,5 @@ +version: "0.0.1" +description: esp websocket client +dependencies: + idf: + version: ">=5.0"