diff --git a/components/esp_websocket_client/esp_websocket_client.c b/components/esp_websocket_client/esp_websocket_client.c index a066c288c..f7a7f7984 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() @@ -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, @@ -457,7 +459,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..."); @@ -471,8 +473,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: @@ -529,7 +538,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; @@ -558,10 +584,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 898fab5ae..043982838 100644 --- a/components/esp_websocket_client/include/esp_websocket_client.h +++ b/components/esp_websocket_client/include/esp_websocket_client.h @@ -50,6 +50,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; /** @@ -73,7 +74,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/components/tcp_transport/include/esp_transport_ws.h b/components/tcp_transport/include/esp_transport_ws.h index f47fd049c..0876480a4 100644 --- a/components/tcp_transport/include/esp_transport_ws.h +++ b/components/tcp_transport/include/esp_transport_ws.h @@ -13,6 +13,13 @@ extern "C" { #endif +typedef enum ws_transport_opcodes { + WS_TRANSPORT_OPCODES_TEXT = 0x01, + WS_TRANSPORT_OPCODES_BINARY = 0x02, + WS_TRANSPORT_OPCODES_CLOSE = 0x08, + WS_TRANSPORT_OPCODES_PING = 0x09, + WS_TRANSPORT_OPCODES_PONG = 0x0a, +} ws_transport_opcodes_t; /** * @brief Create web socket transport @@ -43,6 +50,37 @@ void esp_transport_ws_set_path(esp_transport_handle_t t, const char *path); */ esp_err_t esp_transport_ws_set_subprotocol(esp_transport_handle_t t, const char *sub_protocol); +/** + * @brief Sends websocket raw message with custom opcode and payload + * + * Note that generic esp_transport_write for ws handle sends + * binary massages by default if size is > 0 and + * ping message if message size is set to 0. + * This API is provided to support explicit messages with arbitrary opcode, + * should it be PING, PONG or TEXT message with arbitrary data. + * + * @param[in] t Websocket transport handle + * @param[in] opcode ws operation code + * @param[in] buffer The buffer + * @param[in] len The length + * @param[in] timeout_ms The timeout milliseconds + * + * @return + * - Number of bytes was written + * - (-1) if there are any errors, should check errno + */ +int esp_transport_ws_send_raw(esp_transport_handle_t t, ws_transport_opcodes_t opcode, const char *b, int len, int timeout_ms); + +/** + * @brief Returns websocket op-code for last received data + * + * @param t websocket transport handle + * + * @return + * - Received op-code as enum + */ +ws_transport_opcodes_t esp_transport_ws_get_read_opcode(esp_transport_handle_t t); + #ifdef __cplusplus } diff --git a/components/tcp_transport/transport_ws.c b/components/tcp_transport/transport_ws.c index 12aaa3d23..2efe57edf 100644 --- a/components/tcp_transport/transport_ws.c +++ b/components/tcp_transport/transport_ws.c @@ -32,9 +32,15 @@ typedef struct { char *path; char *buffer; char *sub_protocol; + uint8_t read_opcode; esp_transport_handle_t parent; } transport_ws_t; +static inline uint8_t ws_get_bin_opcode(ws_transport_opcodes_t opcode) +{ + return (uint8_t)opcode; +} + static esp_transport_handle_t ws_get_payload_transport_handle(esp_transport_handle_t t) { transport_ws_t *ws = esp_transport_get_context_data(t); @@ -221,9 +227,23 @@ static int _ws_write(esp_transport_handle_t t, int opcode, int mask_flag, const return ret; } +int esp_transport_ws_send_raw(esp_transport_handle_t t, ws_transport_opcodes_t opcode, const char *b, int len, int timeout_ms) +{ + uint8_t op_code = ws_get_bin_opcode(opcode); + if (t == NULL) { + ESP_LOGE(TAG, "Transport must be a valid ws handle"); + return ESP_ERR_INVALID_ARG; + } + ESP_LOGD(TAG, "Sending raw ws message with opcode %d", op_code); + return _ws_write(t, op_code | WS_FIN, WS_MASK, b, len, timeout_ms); +} + static int ws_write(esp_transport_handle_t t, const char *b, int len, int timeout_ms) { if (len == 0) { + // Default transport write of zero length in ws layer sends out a ping message. + // This behaviour could however be altered in IDF 5.0, since a separate API for sending + // messages with user defined opcodes has been introduced. ESP_LOGD(TAG, "Write PING message"); return _ws_write(t, WS_OPCODE_PING | WS_FIN, 0, NULL, 0, timeout_ms); } @@ -235,7 +255,7 @@ static int ws_read(esp_transport_handle_t t, char *buffer, int len, int timeout_ transport_ws_t *ws = esp_transport_get_context_data(t); int payload_len; char ws_header[MAX_WEBSOCKET_HEADER_SIZE]; - char *data_ptr = ws_header, opcode, mask, *mask_key = NULL; + char *data_ptr = ws_header, mask, *mask_key = NULL; int rlen; int poll_read; if ((poll_read = esp_transport_poll_read(ws->parent, timeout_ms)) <= 0) { @@ -248,12 +268,12 @@ static int ws_read(esp_transport_handle_t t, char *buffer, int len, int timeout_ ESP_LOGE(TAG, "Error read data"); return rlen; } - opcode = (*data_ptr & 0x0F); + ws->read_opcode = (*data_ptr & 0x0F); data_ptr ++; mask = ((*data_ptr >> 7) & 0x01); payload_len = (*data_ptr & 0x7F); data_ptr++; - ESP_LOGD(TAG, "Opcode: %d, mask: %d, len: %d\r\n", opcode, mask, payload_len); + ESP_LOGD(TAG, "Opcode: %d, mask: %d, len: %d\r\n", ws->read_opcode, mask, payload_len); if (payload_len == 126) { // headerLen += 2; if ((rlen = esp_transport_read(ws->parent, data_ptr, header, timeout_ms)) <= 0) { @@ -378,3 +398,9 @@ esp_err_t esp_transport_ws_set_subprotocol(esp_transport_handle_t t, const char } return ESP_OK; } + +ws_transport_opcodes_t esp_transport_ws_get_read_opcode(esp_transport_handle_t t) +{ + transport_ws_t *ws = esp_transport_get_context_data(t); + return ws->read_opcode; +} diff --git a/examples/protocols/websocket/main/websocket_example.c b/examples/protocols/websocket/main/websocket_example.c index 120a5b4fd..8cd756150 100644 --- a/examples/protocols/websocket/main/websocket_example.c +++ b/examples/protocols/websocket/main/websocket_example.c @@ -45,6 +45,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: