esp_websocket_client.c
1 // Copyright 2015-2018 Espressif Systems (Shanghai) PTE LTD 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 #include <stdio.h> 16 17 #include "esp_websocket_client.h" 18 #include "esp_transport.h" 19 #include "esp_transport_tcp.h" 20 #include "esp_transport_ssl.h" 21 #include "esp_transport_ws.h" 22 /* using uri parser */ 23 #include "http_parser.h" 24 #include "freertos/task.h" 25 #include "freertos/semphr.h" 26 #include "freertos/queue.h" 27 #include "freertos/event_groups.h" 28 #include "esp_log.h" 29 #include "esp_timer.h" 30 31 static const char *TAG = "WEBSOCKET_CLIENT"; 32 33 #define WEBSOCKET_TCP_DEFAULT_PORT (80) 34 #define WEBSOCKET_SSL_DEFAULT_PORT (443) 35 #define WEBSOCKET_BUFFER_SIZE_BYTE (1024) 36 #define WEBSOCKET_RECONNECT_TIMEOUT_MS (10*1000) 37 #define WEBSOCKET_TASK_PRIORITY (5) 38 #define WEBSOCKET_TASK_STACK (4*1024) 39 #define WEBSOCKET_NETWORK_TIMEOUT_MS (10*1000) 40 #define WEBSOCKET_PING_TIMEOUT_MS (10*1000) 41 #define WEBSOCKET_EVENT_QUEUE_SIZE (1) 42 #define WEBSOCKET_PINGPONG_TIMEOUT_SEC (120) 43 44 #define ESP_WS_CLIENT_MEM_CHECK(TAG, a, action) if (!(a)) { \ 45 ESP_LOGE(TAG,"%s(%d): %s", __FUNCTION__, __LINE__, "Memory exhausted"); \ 46 action; \ 47 } 48 49 #define ESP_WS_CLIENT_STATE_CHECK(TAG, a, action) if ((a->state) < WEBSOCKET_STATE_INIT) { \ 50 ESP_LOGE(TAG,"%s:%d (%s): %s", __FILE__, __LINE__, __FUNCTION__, "Websocket already stop"); \ 51 action; \ 52 } 53 54 const static int STOPPED_BIT = BIT0; 55 const static int CLOSE_FRAME_SENT_BIT = BIT1; // Indicates that a close frame was sent by the client 56 // and we are waiting for the server to continue with clean close 57 58 ESP_EVENT_DEFINE_BASE(WEBSOCKET_EVENTS); 59 60 typedef struct { 61 int task_stack; 62 int task_prio; 63 char *uri; 64 char *host; 65 char *path; 66 char *scheme; 67 char *username; 68 char *password; 69 int port; 70 bool auto_reconnect; 71 void *user_context; 72 int network_timeout_ms; 73 char *subprotocol; 74 char *user_agent; 75 char *headers; 76 int pingpong_timeout_sec; 77 } websocket_config_storage_t; 78 79 typedef enum { 80 WEBSOCKET_STATE_ERROR = -1, 81 WEBSOCKET_STATE_UNKNOW = 0, 82 WEBSOCKET_STATE_INIT, 83 WEBSOCKET_STATE_CONNECTED, 84 WEBSOCKET_STATE_WAIT_TIMEOUT, 85 WEBSOCKET_STATE_CLOSING, 86 } websocket_client_state_t; 87 88 struct esp_websocket_client { 89 esp_event_loop_handle_t event_handle; 90 TaskHandle_t task_handle; 91 esp_transport_list_handle_t transport_list; 92 esp_transport_handle_t transport; 93 websocket_config_storage_t *config; 94 websocket_client_state_t state; 95 uint64_t keepalive_tick_ms; 96 uint64_t reconnect_tick_ms; 97 uint64_t ping_tick_ms; 98 uint64_t pingpong_tick_ms; 99 int wait_timeout_ms; 100 int auto_reconnect; 101 bool run; 102 bool wait_for_pong_resp; 103 EventGroupHandle_t status_bits; 104 xSemaphoreHandle lock; 105 char *rx_buffer; 106 char *tx_buffer; 107 int buffer_size; 108 ws_transport_opcodes_t last_opcode; 109 int payload_len; 110 int payload_offset; 111 }; 112 113 static uint64_t _tick_get_ms(void) 114 { 115 return esp_timer_get_time()/1000; 116 } 117 118 static esp_err_t esp_websocket_client_dispatch_event(esp_websocket_client_handle_t client, 119 esp_websocket_event_id_t event, 120 const char *data, 121 int data_len) 122 { 123 esp_err_t err; 124 esp_websocket_event_data_t event_data; 125 126 event_data.client = client; 127 event_data.user_context = client->config->user_context; 128 event_data.data_ptr = data; 129 event_data.data_len = data_len; 130 event_data.op_code = client->last_opcode; 131 event_data.payload_len = client->payload_len; 132 event_data.payload_offset = client->payload_offset; 133 134 if ((err = esp_event_post_to(client->event_handle, 135 WEBSOCKET_EVENTS, event, 136 &event_data, 137 sizeof(esp_websocket_event_data_t), 138 portMAX_DELAY)) != ESP_OK) { 139 return err; 140 } 141 return esp_event_loop_run(client->event_handle, 0); 142 } 143 144 static esp_err_t esp_websocket_client_abort_connection(esp_websocket_client_handle_t client) 145 { 146 ESP_WS_CLIENT_STATE_CHECK(TAG, client, return ESP_FAIL); 147 esp_transport_close(client->transport); 148 149 if (client->config->auto_reconnect) { 150 client->wait_timeout_ms = WEBSOCKET_RECONNECT_TIMEOUT_MS; 151 client->reconnect_tick_ms = _tick_get_ms(); 152 ESP_LOGI(TAG, "Reconnect after %d ms", client->wait_timeout_ms); 153 } 154 client->state = WEBSOCKET_STATE_WAIT_TIMEOUT; 155 esp_websocket_client_dispatch_event(client, WEBSOCKET_EVENT_DISCONNECTED, NULL, 0); 156 return ESP_OK; 157 } 158 159 static esp_err_t esp_websocket_client_set_config(esp_websocket_client_handle_t client, const esp_websocket_client_config_t *config) 160 { 161 websocket_config_storage_t *cfg = client->config; 162 cfg->task_prio = config->task_prio; 163 if (cfg->task_prio <= 0) { 164 cfg->task_prio = WEBSOCKET_TASK_PRIORITY; 165 } 166 167 cfg->task_stack = config->task_stack; 168 if (cfg->task_stack == 0) { 169 cfg->task_stack = WEBSOCKET_TASK_STACK; 170 } 171 172 if (config->host) { 173 cfg->host = strdup(config->host); 174 ESP_WS_CLIENT_MEM_CHECK(TAG, cfg->host, return ESP_ERR_NO_MEM); 175 } 176 177 if (config->port) { 178 cfg->port = config->port; 179 } 180 181 if (config->username) { 182 free(cfg->username); 183 cfg->username = strdup(config->username); 184 ESP_WS_CLIENT_MEM_CHECK(TAG, cfg->username, return ESP_ERR_NO_MEM); 185 } 186 187 if (config->password) { 188 free(cfg->password); 189 cfg->password = strdup(config->password); 190 ESP_WS_CLIENT_MEM_CHECK(TAG, cfg->password, return ESP_ERR_NO_MEM); 191 } 192 193 if (config->uri) { 194 free(cfg->uri); 195 cfg->uri = strdup(config->uri); 196 ESP_WS_CLIENT_MEM_CHECK(TAG, cfg->uri, return ESP_ERR_NO_MEM); 197 } 198 if (config->path) { 199 free(cfg->path); 200 cfg->path = strdup(config->path); 201 ESP_WS_CLIENT_MEM_CHECK(TAG, cfg->path, return ESP_ERR_NO_MEM); 202 } 203 if (config->subprotocol) { 204 free(cfg->subprotocol); 205 cfg->subprotocol = strdup(config->subprotocol); 206 ESP_WS_CLIENT_MEM_CHECK(TAG, cfg->subprotocol, return ESP_ERR_NO_MEM); 207 } 208 if (config->user_agent) { 209 free(cfg->user_agent); 210 cfg->user_agent = strdup(config->user_agent); 211 ESP_WS_CLIENT_MEM_CHECK(TAG, cfg->user_agent, return ESP_ERR_NO_MEM); 212 } 213 if (config->headers) { 214 free(cfg->headers); 215 cfg->headers = strdup(config->headers); 216 ESP_WS_CLIENT_MEM_CHECK(TAG, cfg->headers, return ESP_ERR_NO_MEM); 217 } 218 219 cfg->network_timeout_ms = WEBSOCKET_NETWORK_TIMEOUT_MS; 220 cfg->user_context = config->user_context; 221 cfg->auto_reconnect = true; 222 if (config->disable_auto_reconnect) { 223 cfg->auto_reconnect = false; 224 } 225 226 if (config->disable_pingpong_discon){ 227 cfg->pingpong_timeout_sec = 0; 228 } else if (config->pingpong_timeout_sec) { 229 cfg->pingpong_timeout_sec = config->pingpong_timeout_sec; 230 } else { 231 cfg->pingpong_timeout_sec = WEBSOCKET_PINGPONG_TIMEOUT_SEC; 232 } 233 234 return ESP_OK; 235 } 236 237 static esp_err_t esp_websocket_client_destroy_config(esp_websocket_client_handle_t client) 238 { 239 if (client == NULL) { 240 return ESP_ERR_INVALID_ARG; 241 } 242 websocket_config_storage_t *cfg = client->config; 243 if (client->config == NULL) { 244 return ESP_ERR_INVALID_ARG; 245 } 246 free(cfg->host); 247 free(cfg->uri); 248 free(cfg->path); 249 free(cfg->scheme); 250 free(cfg->username); 251 free(cfg->password); 252 free(cfg->subprotocol); 253 free(cfg->user_agent); 254 free(cfg->headers); 255 memset(cfg, 0, sizeof(websocket_config_storage_t)); 256 free(client->config); 257 client->config = NULL; 258 return ESP_OK; 259 } 260 261 static void set_websocket_transport_optional_settings(esp_websocket_client_handle_t client, esp_transport_handle_t trans) 262 { 263 if (trans && client->config->path) { 264 esp_transport_ws_set_path(trans, client->config->path); 265 } 266 if (trans && client->config->subprotocol) { 267 esp_transport_ws_set_subprotocol(trans, client->config->subprotocol); 268 } 269 if (trans && client->config->user_agent) { 270 esp_transport_ws_set_user_agent(trans, client->config->user_agent); 271 } 272 if (trans && client->config->headers) { 273 esp_transport_ws_set_headers(trans, client->config->headers); 274 } 275 } 276 277 esp_websocket_client_handle_t esp_websocket_client_init(const esp_websocket_client_config_t *config) 278 { 279 esp_websocket_client_handle_t client = calloc(1, sizeof(struct esp_websocket_client)); 280 ESP_WS_CLIENT_MEM_CHECK(TAG, client, return NULL); 281 282 esp_event_loop_args_t event_args = { 283 .queue_size = WEBSOCKET_EVENT_QUEUE_SIZE, 284 .task_name = NULL // no task will be created 285 }; 286 287 if (esp_event_loop_create(&event_args, &client->event_handle) != ESP_OK) { 288 ESP_LOGE(TAG, "Error create event handler for websocket client"); 289 free(client); 290 return NULL; 291 } 292 293 client->lock = xSemaphoreCreateRecursiveMutex(); 294 ESP_WS_CLIENT_MEM_CHECK(TAG, client->lock, goto _websocket_init_fail); 295 296 client->config = calloc(1, sizeof(websocket_config_storage_t)); 297 ESP_WS_CLIENT_MEM_CHECK(TAG, client->config, goto _websocket_init_fail); 298 299 client->transport_list = esp_transport_list_init(); 300 ESP_WS_CLIENT_MEM_CHECK(TAG, client->transport_list, goto _websocket_init_fail); 301 302 esp_transport_handle_t tcp = esp_transport_tcp_init(); 303 ESP_WS_CLIENT_MEM_CHECK(TAG, tcp, goto _websocket_init_fail); 304 305 esp_transport_set_default_port(tcp, WEBSOCKET_TCP_DEFAULT_PORT); 306 esp_transport_list_add(client->transport_list, tcp, "_tcp"); // need to save to transport list, for cleanup 307 308 309 esp_transport_handle_t ws = esp_transport_ws_init(tcp); 310 ESP_WS_CLIENT_MEM_CHECK(TAG, ws, goto _websocket_init_fail); 311 312 esp_transport_set_default_port(ws, WEBSOCKET_TCP_DEFAULT_PORT); 313 esp_transport_list_add(client->transport_list, ws, "ws"); 314 if (config->transport == WEBSOCKET_TRANSPORT_OVER_TCP) { 315 asprintf(&client->config->scheme, "ws"); 316 ESP_WS_CLIENT_MEM_CHECK(TAG, client->config->scheme, goto _websocket_init_fail); 317 } 318 319 esp_transport_handle_t ssl = esp_transport_ssl_init(); 320 ESP_WS_CLIENT_MEM_CHECK(TAG, ssl, goto _websocket_init_fail); 321 322 esp_transport_set_default_port(ssl, WEBSOCKET_SSL_DEFAULT_PORT); 323 if (config->cert_pem) { 324 esp_transport_ssl_set_cert_data(ssl, config->cert_pem, strlen(config->cert_pem)); 325 } 326 esp_transport_list_add(client->transport_list, ssl, "_ssl"); // need to save to transport list, for cleanup 327 328 esp_transport_handle_t wss = esp_transport_ws_init(ssl); 329 ESP_WS_CLIENT_MEM_CHECK(TAG, wss, goto _websocket_init_fail); 330 331 esp_transport_set_default_port(wss, WEBSOCKET_SSL_DEFAULT_PORT); 332 333 esp_transport_list_add(client->transport_list, wss, "wss"); 334 if (config->transport == WEBSOCKET_TRANSPORT_OVER_SSL) { 335 asprintf(&client->config->scheme, "wss"); 336 ESP_WS_CLIENT_MEM_CHECK(TAG, client->config->scheme, goto _websocket_init_fail); 337 } 338 339 if (config->uri) { 340 if (esp_websocket_client_set_uri(client, config->uri) != ESP_OK) { 341 ESP_LOGE(TAG, "Invalid uri"); 342 goto _websocket_init_fail; 343 } 344 } 345 346 if (esp_websocket_client_set_config(client, config) != ESP_OK) { 347 ESP_LOGE(TAG, "Failed to set the configuration"); 348 goto _websocket_init_fail; 349 } 350 351 if (client->config->scheme == NULL) { 352 asprintf(&client->config->scheme, "ws"); 353 ESP_WS_CLIENT_MEM_CHECK(TAG, client->config->scheme, goto _websocket_init_fail); 354 } 355 356 set_websocket_transport_optional_settings(client, esp_transport_list_get_transport(client->transport_list, "ws")); 357 set_websocket_transport_optional_settings(client, esp_transport_list_get_transport(client->transport_list, "wss")); 358 359 client->keepalive_tick_ms = _tick_get_ms(); 360 client->reconnect_tick_ms = _tick_get_ms(); 361 client->ping_tick_ms = _tick_get_ms(); 362 client->wait_for_pong_resp = false; 363 364 int buffer_size = config->buffer_size; 365 if (buffer_size <= 0) { 366 buffer_size = WEBSOCKET_BUFFER_SIZE_BYTE; 367 } 368 client->rx_buffer = malloc(buffer_size); 369 ESP_WS_CLIENT_MEM_CHECK(TAG, client->rx_buffer, { 370 goto _websocket_init_fail; 371 }); 372 client->tx_buffer = malloc(buffer_size); 373 ESP_WS_CLIENT_MEM_CHECK(TAG, client->tx_buffer, { 374 goto _websocket_init_fail; 375 }); 376 client->status_bits = xEventGroupCreate(); 377 ESP_WS_CLIENT_MEM_CHECK(TAG, client->status_bits, { 378 goto _websocket_init_fail; 379 }); 380 381 client->buffer_size = buffer_size; 382 return client; 383 384 _websocket_init_fail: 385 esp_websocket_client_destroy(client); 386 return NULL; 387 } 388 389 esp_err_t esp_websocket_client_destroy(esp_websocket_client_handle_t client) 390 { 391 if (client == NULL) { 392 return ESP_ERR_INVALID_ARG; 393 } 394 if (client->run) { 395 esp_websocket_client_stop(client); 396 } 397 if (client->event_handle) { 398 esp_event_loop_delete(client->event_handle); 399 } 400 esp_websocket_client_destroy_config(client); 401 esp_transport_list_destroy(client->transport_list); 402 vQueueDelete(client->lock); 403 free(client->tx_buffer); 404 free(client->rx_buffer); 405 if (client->status_bits) { 406 vEventGroupDelete(client->status_bits); 407 } 408 free(client); 409 client = NULL; 410 return ESP_OK; 411 } 412 413 esp_err_t esp_websocket_client_set_uri(esp_websocket_client_handle_t client, const char *uri) 414 { 415 if (client == NULL || uri == NULL) { 416 return ESP_ERR_INVALID_ARG; 417 } 418 struct http_parser_url puri; 419 http_parser_url_init(&puri); 420 int parser_status = http_parser_parse_url(uri, strlen(uri), 0, &puri); 421 if (parser_status != 0) { 422 ESP_LOGE(TAG, "Error parse uri = %s", uri); 423 return ESP_FAIL; 424 } 425 if (puri.field_data[UF_SCHEMA].len) { 426 free(client->config->scheme); 427 asprintf(&client->config->scheme, "%.*s", puri.field_data[UF_SCHEMA].len, uri + puri.field_data[UF_SCHEMA].off); 428 ESP_WS_CLIENT_MEM_CHECK(TAG, client->config->scheme, return ESP_ERR_NO_MEM); 429 } 430 431 if (puri.field_data[UF_HOST].len) { 432 free(client->config->host); 433 asprintf(&client->config->host, "%.*s", puri.field_data[UF_HOST].len, uri + puri.field_data[UF_HOST].off); 434 ESP_WS_CLIENT_MEM_CHECK(TAG, client->config->host, return ESP_ERR_NO_MEM); 435 } 436 437 438 if (puri.field_data[UF_PATH].len || puri.field_data[UF_QUERY].len) { 439 free(client->config->path); 440 if (puri.field_data[UF_QUERY].len == 0) { 441 asprintf(&client->config->path, "%.*s", puri.field_data[UF_PATH].len, uri + puri.field_data[UF_PATH].off); 442 } else if (puri.field_data[UF_PATH].len == 0) { 443 asprintf(&client->config->path, "/?%.*s", puri.field_data[UF_QUERY].len, uri + puri.field_data[UF_QUERY].off); 444 } else { 445 asprintf(&client->config->path, "%.*s?%.*s", puri.field_data[UF_PATH].len, uri + puri.field_data[UF_PATH].off, 446 puri.field_data[UF_QUERY].len, uri + puri.field_data[UF_QUERY].off); 447 } 448 ESP_WS_CLIENT_MEM_CHECK(TAG, client->config->path, return ESP_ERR_NO_MEM); 449 } 450 if (puri.field_data[UF_PORT].off) { 451 client->config->port = strtol((const char*)(uri + puri.field_data[UF_PORT].off), NULL, 10); 452 } 453 454 if (puri.field_data[UF_USERINFO].len) { 455 char *user_info = NULL; 456 asprintf(&user_info, "%.*s", puri.field_data[UF_USERINFO].len, uri + puri.field_data[UF_USERINFO].off); 457 if (user_info) { 458 char *pass = strchr(user_info, ':'); 459 if (pass) { 460 pass[0] = 0; //terminal username 461 pass ++; 462 free(client->config->password); 463 client->config->password = strdup(pass); 464 ESP_WS_CLIENT_MEM_CHECK(TAG, client->config->password, return ESP_ERR_NO_MEM); 465 } 466 free(client->config->username); 467 client->config->username = strdup(user_info); 468 ESP_WS_CLIENT_MEM_CHECK(TAG, client->config->username, return ESP_ERR_NO_MEM); 469 free(user_info); 470 } else { 471 return ESP_ERR_NO_MEM; 472 } 473 } 474 return ESP_OK; 475 } 476 477 static esp_err_t esp_websocket_client_recv(esp_websocket_client_handle_t client) 478 { 479 int rlen; 480 client->payload_offset = 0; 481 do { 482 rlen = esp_transport_read(client->transport, client->rx_buffer, client->buffer_size, client->config->network_timeout_ms); 483 if (rlen < 0) { 484 ESP_LOGE(TAG, "Error read data"); 485 return ESP_FAIL; 486 } 487 client->payload_len = esp_transport_ws_get_read_payload_len(client->transport); 488 client->last_opcode = esp_transport_ws_get_read_opcode(client->transport); 489 490 esp_websocket_client_dispatch_event(client, WEBSOCKET_EVENT_DATA, client->rx_buffer, rlen); 491 492 client->payload_offset += rlen; 493 } while (client->payload_offset < client->payload_len); 494 495 // if a PING message received -> send out the PONG, this will not work for PING messages with payload longer than buffer len 496 if (client->last_opcode == WS_TRANSPORT_OPCODES_PING) { 497 const char *data = (client->payload_len == 0) ? NULL : client->rx_buffer; 498 esp_transport_ws_send_raw(client->transport, WS_TRANSPORT_OPCODES_PONG | WS_TRANSPORT_OPCODES_FIN, data, client->payload_len, 499 client->config->network_timeout_ms); 500 } else if (client->last_opcode == WS_TRANSPORT_OPCODES_PONG) { 501 client->wait_for_pong_resp = false; 502 } else if (client->last_opcode == WS_TRANSPORT_OPCODES_CLOSE) { 503 ESP_LOGD(TAG, "Received close frame"); 504 client->state = WEBSOCKET_STATE_CLOSING; 505 } 506 507 return ESP_OK; 508 } 509 510 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); 511 512 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); 513 514 static void esp_websocket_client_task(void *pv) 515 { 516 const int lock_timeout = portMAX_DELAY; 517 esp_websocket_client_handle_t client = (esp_websocket_client_handle_t) pv; 518 client->run = true; 519 520 //get transport by scheme 521 client->transport = esp_transport_list_get_transport(client->transport_list, client->config->scheme); 522 523 if (client->transport == NULL) { 524 ESP_LOGE(TAG, "There are no transports valid, stop websocket client"); 525 client->run = false; 526 } 527 //default port 528 if (client->config->port == 0) { 529 client->config->port = esp_transport_get_default_port(client->transport); 530 } 531 532 client->state = WEBSOCKET_STATE_INIT; 533 xEventGroupClearBits(client->status_bits, STOPPED_BIT | CLOSE_FRAME_SENT_BIT); 534 int read_select = 0; 535 while (client->run) { 536 if (xSemaphoreTakeRecursive(client->lock, lock_timeout) != pdPASS) { 537 ESP_LOGE(TAG, "Failed to lock ws-client tasks, exitting the task..."); 538 break; 539 } 540 switch ((int)client->state) { 541 case WEBSOCKET_STATE_INIT: 542 if (client->transport == NULL) { 543 ESP_LOGE(TAG, "There are no transport"); 544 client->run = false; 545 break; 546 } 547 if (esp_transport_connect(client->transport, 548 client->config->host, 549 client->config->port, 550 client->config->network_timeout_ms) < 0) { 551 ESP_LOGE(TAG, "Error transport connect"); 552 esp_websocket_client_abort_connection(client); 553 break; 554 } 555 ESP_LOGD(TAG, "Transport connected to %s://%s:%d", client->config->scheme, client->config->host, client->config->port); 556 557 client->state = WEBSOCKET_STATE_CONNECTED; 558 client->wait_for_pong_resp = false; 559 esp_websocket_client_dispatch_event(client, WEBSOCKET_EVENT_CONNECTED, NULL, 0); 560 561 break; 562 case WEBSOCKET_STATE_CONNECTED: 563 if ((CLOSE_FRAME_SENT_BIT & xEventGroupGetBits(client->status_bits)) == 0) { // only send and check for PING 564 // if closing hasn't been initiated 565 if (_tick_get_ms() - client->ping_tick_ms > WEBSOCKET_PING_TIMEOUT_MS) { 566 client->ping_tick_ms = _tick_get_ms(); 567 ESP_LOGD(TAG, "Sending PING..."); 568 esp_transport_ws_send_raw(client->transport, WS_TRANSPORT_OPCODES_PING | WS_TRANSPORT_OPCODES_FIN, NULL, 0, client->config->network_timeout_ms); 569 570 if (!client->wait_for_pong_resp && client->config->pingpong_timeout_sec) { 571 client->pingpong_tick_ms = _tick_get_ms(); 572 client->wait_for_pong_resp = true; 573 } 574 } 575 576 if ( _tick_get_ms() - client->pingpong_tick_ms > client->config->pingpong_timeout_sec*1000 ) { 577 if (client->wait_for_pong_resp) { 578 ESP_LOGE(TAG, "Error, no PONG received for more than %d seconds after PING", client->config->pingpong_timeout_sec); 579 esp_websocket_client_abort_connection(client); 580 break; 581 } 582 } 583 } 584 585 if (read_select == 0) { 586 ESP_LOGV(TAG, "Read poll timeout: skipping esp_transport_read()..."); 587 break; 588 } 589 client->ping_tick_ms = _tick_get_ms(); 590 591 if (esp_websocket_client_recv(client) == ESP_FAIL) { 592 ESP_LOGE(TAG, "Error receive data"); 593 esp_websocket_client_abort_connection(client); 594 break; 595 } 596 break; 597 case WEBSOCKET_STATE_WAIT_TIMEOUT: 598 599 if (!client->config->auto_reconnect) { 600 client->run = false; 601 break; 602 } 603 if (_tick_get_ms() - client->reconnect_tick_ms > client->wait_timeout_ms) { 604 client->state = WEBSOCKET_STATE_INIT; 605 client->reconnect_tick_ms = _tick_get_ms(); 606 ESP_LOGD(TAG, "Reconnecting..."); 607 } 608 break; 609 case WEBSOCKET_STATE_CLOSING: 610 // if closing not initiated by the client echo the close message back 611 if ((CLOSE_FRAME_SENT_BIT & xEventGroupGetBits(client->status_bits)) == 0) { 612 ESP_LOGD(TAG, "Closing initiated by the server, sending close frame"); 613 esp_transport_ws_send_raw(client->transport, WS_TRANSPORT_OPCODES_CLOSE | WS_TRANSPORT_OPCODES_FIN, NULL, 0, client->config->network_timeout_ms); 614 xEventGroupSetBits(client->status_bits, CLOSE_FRAME_SENT_BIT); 615 } 616 break; 617 default: 618 ESP_LOGD(TAG, "Client run iteration in a default state: %d", client->state); 619 break; 620 } 621 xSemaphoreGiveRecursive(client->lock); 622 if (WEBSOCKET_STATE_CONNECTED == client->state) { 623 read_select = esp_transport_poll_read(client->transport, 1000); //Poll every 1000ms 624 if (read_select < 0) { 625 ESP_LOGE(TAG, "Network error: esp_transport_poll_read() returned %d, errno=%d", read_select, errno); 626 esp_websocket_client_abort_connection(client); 627 } 628 } else if (WEBSOCKET_STATE_WAIT_TIMEOUT == client->state) { 629 // waiting for reconnecting... 630 vTaskDelay(client->wait_timeout_ms / 2 / portTICK_RATE_MS); 631 } else if (WEBSOCKET_STATE_CLOSING == client->state && 632 (CLOSE_FRAME_SENT_BIT & xEventGroupGetBits(client->status_bits))) { 633 ESP_LOGD(TAG, " Waiting for TCP connection to be closed by the server"); 634 int ret = esp_transport_ws_poll_connection_closed(client->transport, 1000); 635 if (ret == 0) { 636 // still waiting 637 break; 638 } 639 if (ret < 0) { 640 ESP_LOGW(TAG, "Connection terminated while waiting for clean TCP close"); 641 } 642 client->run = false; 643 client->state = WEBSOCKET_STATE_UNKNOW; 644 esp_websocket_client_dispatch_event(client, WEBSOCKET_EVENT_CLOSED, NULL, 0); 645 break; 646 } 647 } 648 649 esp_transport_close(client->transport); 650 xEventGroupSetBits(client->status_bits, STOPPED_BIT); 651 client->state = WEBSOCKET_STATE_UNKNOW; 652 vTaskDelete(NULL); 653 } 654 655 esp_err_t esp_websocket_client_start(esp_websocket_client_handle_t client) 656 { 657 if (client == NULL) { 658 return ESP_ERR_INVALID_ARG; 659 } 660 if (client->state >= WEBSOCKET_STATE_INIT) { 661 ESP_LOGE(TAG, "The client has started"); 662 return ESP_FAIL; 663 } 664 if (xTaskCreate(esp_websocket_client_task, "websocket_task", client->config->task_stack, client, client->config->task_prio, &client->task_handle) != pdTRUE) { 665 ESP_LOGE(TAG, "Error create websocket task"); 666 return ESP_FAIL; 667 } 668 xEventGroupClearBits(client->status_bits, STOPPED_BIT | CLOSE_FRAME_SENT_BIT); 669 return ESP_OK; 670 } 671 672 esp_err_t esp_websocket_client_stop(esp_websocket_client_handle_t client) 673 { 674 if (client == NULL) { 675 return ESP_ERR_INVALID_ARG; 676 } 677 if (!client->run) { 678 ESP_LOGW(TAG, "Client was not started"); 679 return ESP_FAIL; 680 } 681 682 /* A running client cannot be stopped from the websocket task/event handler */ 683 TaskHandle_t running_task = xTaskGetCurrentTaskHandle(); 684 if (running_task == client->task_handle) { 685 ESP_LOGE(TAG, "Client cannot be stopped from websocket task"); 686 return ESP_FAIL; 687 } 688 689 690 client->run = false; 691 xEventGroupWaitBits(client->status_bits, STOPPED_BIT, false, true, portMAX_DELAY); 692 client->state = WEBSOCKET_STATE_UNKNOW; 693 return ESP_OK; 694 } 695 696 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) 697 { 698 uint8_t *close_status_data = NULL; 699 // RFC6455#section-5.5.1: The Close frame MAY contain a body (indicated by total_len >= 2) 700 if (total_len >= 2) { 701 close_status_data = calloc(1, total_len); 702 ESP_WS_CLIENT_MEM_CHECK(TAG, close_status_data, return -1); 703 // RFC6455#section-5.5.1: The first two bytes of the body MUST be a 2-byte representing a status 704 uint16_t *code_network_order = (uint16_t *) close_status_data; 705 *code_network_order = htons(code); 706 memcpy(close_status_data + 2, additional_data, total_len - 2); 707 } 708 int ret = esp_websocket_client_send_with_opcode(client, WS_TRANSPORT_OPCODES_CLOSE, close_status_data, total_len, timeout); 709 free(close_status_data); 710 return ret; 711 } 712 713 714 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) 715 { 716 if (client == NULL) { 717 return ESP_ERR_INVALID_ARG; 718 } 719 if (!client->run) { 720 ESP_LOGW(TAG, "Client was not started"); 721 return ESP_FAIL; 722 } 723 724 /* A running client cannot be stopped from the websocket task/event handler */ 725 TaskHandle_t running_task = xTaskGetCurrentTaskHandle(); 726 if (running_task == client->task_handle) { 727 ESP_LOGE(TAG, "Client cannot be stopped from websocket task"); 728 return ESP_FAIL; 729 } 730 731 if (send_body) { 732 esp_websocket_client_send_close(client, code, data, len + 2, portMAX_DELAY); // len + 2 -> always sending the code 733 } else { 734 esp_websocket_client_send_close(client, 0, NULL, 0, portMAX_DELAY); // only opcode frame 735 } 736 737 // Set closing bit to prevent from sending PING frames while connected 738 xEventGroupSetBits(client->status_bits, CLOSE_FRAME_SENT_BIT); 739 740 if (STOPPED_BIT & xEventGroupWaitBits(client->status_bits, STOPPED_BIT, false, true, timeout)) { 741 return ESP_OK; 742 } 743 744 // If could not close gracefully within timeout, stop the client and disconnect 745 client->run = false; 746 xEventGroupWaitBits(client->status_bits, STOPPED_BIT, false, true, portMAX_DELAY); 747 client->state = WEBSOCKET_STATE_UNKNOW; 748 return ESP_OK; 749 } 750 751 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) 752 { 753 return esp_websocket_client_close_with_optional_body(client, true, code, data, len, timeout); 754 } 755 756 esp_err_t esp_websocket_client_close(esp_websocket_client_handle_t client, TickType_t timeout) 757 { 758 return esp_websocket_client_close_with_optional_body(client, false, 0, NULL, 0, timeout); 759 } 760 761 int esp_websocket_client_send_text(esp_websocket_client_handle_t client, const char *data, int len, TickType_t timeout) 762 { 763 return esp_websocket_client_send_with_opcode(client, WS_TRANSPORT_OPCODES_TEXT, (const uint8_t *)data, len, timeout); 764 } 765 766 int esp_websocket_client_send(esp_websocket_client_handle_t client, const char *data, int len, TickType_t timeout) 767 { 768 return esp_websocket_client_send_with_opcode(client, WS_TRANSPORT_OPCODES_BINARY, (const uint8_t *)data, len, timeout); 769 } 770 771 int esp_websocket_client_send_bin(esp_websocket_client_handle_t client, const char *data, int len, TickType_t timeout) 772 { 773 return esp_websocket_client_send_with_opcode(client, WS_TRANSPORT_OPCODES_BINARY, (const uint8_t *)data, len, timeout); 774 } 775 776 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) 777 { 778 int need_write = len; 779 int wlen = 0, widx = 0; 780 int ret = ESP_FAIL; 781 782 if (client == NULL || len < 0 || 783 (opcode != WS_TRANSPORT_OPCODES_CLOSE && (data == NULL || len <= 0))) { 784 ESP_LOGE(TAG, "Invalid arguments"); 785 return ESP_FAIL; 786 } 787 788 if (xSemaphoreTakeRecursive(client->lock, timeout) != pdPASS) { 789 ESP_LOGE(TAG, "Could not lock ws-client within %d timeout", timeout); 790 return ESP_FAIL; 791 } 792 793 if (!esp_websocket_client_is_connected(client)) { 794 ESP_LOGE(TAG, "Websocket client is not connected"); 795 goto unlock_and_return; 796 } 797 798 if (client->transport == NULL) { 799 ESP_LOGE(TAG, "Invalid transport"); 800 goto unlock_and_return; 801 } 802 uint32_t current_opcode = opcode; 803 while (widx < len || current_opcode) { // allow for sending "current_opcode" only message with len==0 804 if (need_write > client->buffer_size) { 805 need_write = client->buffer_size; 806 } else { 807 current_opcode |= WS_TRANSPORT_OPCODES_FIN; 808 } 809 memcpy(client->tx_buffer, data + widx, need_write); 810 // send with ws specific way and specific opcode 811 wlen = esp_transport_ws_send_raw(client->transport, current_opcode, (char *)client->tx_buffer, need_write, 812 (timeout==portMAX_DELAY)? -1 : timeout * portTICK_PERIOD_MS); 813 if (wlen < 0 || (wlen == 0 && need_write != 0)) { 814 ret = wlen; 815 ESP_LOGE(TAG, "Network error: esp_transport_write() returned %d, errno=%d", ret, errno); 816 esp_websocket_client_abort_connection(client); 817 goto unlock_and_return; 818 } 819 current_opcode = 0; 820 widx += wlen; 821 need_write = len - widx; 822 823 } 824 ret = widx; 825 unlock_and_return: 826 xSemaphoreGiveRecursive(client->lock); 827 return ret; 828 } 829 830 bool esp_websocket_client_is_connected(esp_websocket_client_handle_t client) 831 { 832 if (client == NULL) { 833 return false; 834 } 835 return client->state == WEBSOCKET_STATE_CONNECTED; 836 } 837 838 esp_err_t esp_websocket_register_events(esp_websocket_client_handle_t client, 839 esp_websocket_event_id_t event, 840 esp_event_handler_t event_handler, 841 void *event_handler_arg) 842 { 843 if (client == NULL) { 844 return ESP_ERR_INVALID_ARG; 845 } 846 return esp_event_handler_register_with(client->event_handle, WEBSOCKET_EVENTS, event, event_handler, event_handler_arg); 847 }