├── .gitattributes ├── .gitignore ├── ESP32_WS.html ├── Makefile ├── README.md └── main ├── WebSocket_Task.c ├── WebSocket_Task.h ├── component.mk └── main.c /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | 4 | # Custom for Visual Studio 5 | *.cs diff=csharp 6 | 7 | # Standard to msysgit 8 | *.doc diff=astextplain 9 | *.DOC diff=astextplain 10 | *.docx diff=astextplain 11 | *.DOCX diff=astextplain 12 | *.dot diff=astextplain 13 | *.DOT diff=astextplain 14 | *.pdf diff=astextplain 15 | *.PDF diff=astextplain 16 | *.rtf diff=astextplain 17 | *.RTF diff=astextplain 18 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Windows image file caches 2 | Thumbs.db 3 | ehthumbs.db 4 | 5 | # Folder config file 6 | Desktop.ini 7 | 8 | # Recycle Bin used on file shares 9 | $RECYCLE.BIN/ 10 | 11 | # Windows Installer files 12 | *.cab 13 | *.msi 14 | *.msm 15 | *.msp 16 | 17 | # Windows shortcuts 18 | *.lnk 19 | 20 | # ========================= 21 | # Operating System Files 22 | # ========================= 23 | 24 | # OSX 25 | # ========================= 26 | 27 | .DS_Store 28 | .AppleDouble 29 | .LSOverride 30 | 31 | # Thumbnails 32 | ._* 33 | 34 | # Files that might appear in the root of a volume 35 | .DocumentRevisions-V100 36 | .fseventsd 37 | .Spotlight-V100 38 | .TemporaryItems 39 | .Trashes 40 | .VolumeIcon.icns 41 | 42 | # Directories potentially created on remote AFP share 43 | .AppleDB 44 | .AppleDesktop 45 | Network Trash Folder 46 | Temporary Items 47 | .apdisk 48 | -------------------------------------------------------------------------------- /ESP32_WS.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | ESP32 WebSocket Test 6 | 7 | 53 | 54 | 55 | 56 | 57 |
58 | WebSocket Server: 59 | 60 | Connect 61 |
62 |
63 |
64 | Received WebSocket Messages:
65 |
66 | 67 | 68 | 69 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # 2 | # This is a project Makefile. It is assumed the directory this Makefile resides in is a 3 | # project subdirectory. 4 | # 5 | 6 | PROJECT_NAME := WebSocket_demo 7 | 8 | 9 | include $(IDF_PATH)/make/project.mk 10 | 11 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # WebSockets-on-the-ESP32 2 | WebSocket example project for the Espressif ESP32 3 | 4 | Download and install the ESP IDF framework from https://github.com/espressif/esp-idf. The WebSocket project works the same way as the other example projects. 5 | The ESP32_WS.html file can be used to test the server. 6 | 7 | More Infos here: 8 | http://www.barth-dev.de/websockets-on-the-esp32/ 9 | -------------------------------------------------------------------------------- /main/WebSocket_Task.c: -------------------------------------------------------------------------------- 1 | /** 2 | * @section License 3 | * 4 | * The MIT License (MIT) 5 | * 6 | * Copyright (c) 2017, Thomas Barth, barth-dev.de 7 | * 8 | * Permission is hereby granted, free of charge, to any person 9 | * obtaining a copy of this software and associated documentation 10 | * files (the "Software"), to deal in the Software without 11 | * restriction, including without limitation the rights to use, copy, 12 | * modify, merge, publish, distribute, sublicense, and/or sell copies 13 | * of the Software, and to permit persons to whom the Software is 14 | * furnished to do so, subject to the following conditions: 15 | * 16 | * The above copyright notice and this permission notice shall be 17 | * included in all copies or substantial portions of the Software. 18 | * 19 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 20 | * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 21 | * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 22 | * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS 23 | * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN 24 | * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 25 | * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 26 | * SOFTWARE. 27 | */ 28 | 29 | #include "WebSocket_Task.h" 30 | 31 | #include "freertos/FreeRTOS.h" 32 | #include "esp_heap_alloc_caps.h" 33 | #include "hwcrypto/sha.h" 34 | #include "esp_system.h" 35 | #include "wpa2/utils/base64.h" 36 | #include 37 | #include 38 | 39 | #define WS_PORT 9998 /**< \brief TCP Port for the Server*/ 40 | #define WS_CLIENT_KEY_L 24 /**< \brief Length of the Client Key*/ 41 | #define SHA1_RES_L 20 /**< \brief SHA1 result*/ 42 | #define WS_STD_LEN 125 /**< \brief Maximum Length of standard length frames*/ 43 | #define WS_SPRINTF_ARG_L 4 /**< \brief Length of sprintf argument for string (%.*s)*/ 44 | 45 | /** \brief Opcode according to RFC 6455*/ 46 | typedef enum { 47 | WS_OP_CON = 0x0, /*!< Continuation Frame*/ 48 | WS_OP_TXT = 0x1, /*!< Text Frame*/ 49 | WS_OP_BIN = 0x2, /*!< Binary Frame*/ 50 | WS_OP_CLS = 0x8, /*!< Connection Close Frame*/ 51 | WS_OP_PIN = 0x9, /*!< Ping Frame*/ 52 | WS_OP_PON = 0xa /*!< Pong Frame*/ 53 | } WS_OPCODES; 54 | 55 | //reference to the RX queue 56 | extern QueueHandle_t WebSocket_rx_queue; 57 | 58 | //Reference to open websocket connection 59 | static struct netconn* WS_conn = NULL; 60 | 61 | const char WS_sec_WS_keys[] = "Sec-WebSocket-Key:"; 62 | const char WS_sec_conKey[] = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"; 63 | const char WS_srv_hs[] ="HTTP/1.1 101 Switching Protocols \r\nUpgrade: websocket\r\nConnection: Upgrade\r\nSec-WebSocket-Accept: %.*s\r\n\r\n"; 64 | 65 | 66 | err_t WS_write_data(char* p_data, size_t length) { 67 | 68 | //check if we have an open connection 69 | if (WS_conn == NULL) 70 | return ERR_CONN; 71 | 72 | //currently only frames with a payload length WS_STD_LEN) 74 | return ERR_VAL; 75 | 76 | //netconn_write result buffer 77 | err_t result; 78 | 79 | //prepare header 80 | WS_frame_header_t hdr; 81 | hdr.FIN = 0x1; 82 | hdr.payload_length = length; 83 | hdr.mask = 0; 84 | hdr.reserved = 0; 85 | hdr.opcode = WS_OP_TXT; 86 | 87 | //send header 88 | result = netconn_write(WS_conn, &hdr, sizeof(WS_frame_header_t), NETCONN_COPY); 89 | 90 | //check if header was send 91 | if (result != ERR_OK) 92 | return result; 93 | 94 | //send payload 95 | return netconn_write(WS_conn, p_data, length, NETCONN_COPY); 96 | } 97 | 98 | static void ws_server_netconn_serve(struct netconn *conn) { 99 | 100 | //Netbuf 101 | struct netbuf *inbuf; 102 | 103 | //message buffer 104 | char *buf; 105 | 106 | //pointer to buffer (multi purpose) 107 | char* p_buf; 108 | 109 | //Pointer to SHA1 input 110 | char* p_SHA1_Inp; 111 | 112 | //Pointer to SHA1 result 113 | char* p_SHA1_result; 114 | 115 | //multi purpose number buffer 116 | uint16_t i; 117 | 118 | //will point to payload (send and receive 119 | char* p_payload; 120 | 121 | //Frame header pointer 122 | WS_frame_header_t* p_frame_hdr; 123 | 124 | //allocate memory for SHA1 input 125 | p_SHA1_Inp = pvPortMallocCaps(WS_CLIENT_KEY_L + sizeof(WS_sec_conKey), 126 | MALLOC_CAP_8BIT); 127 | 128 | //allocate memory for SHA1 result 129 | p_SHA1_result = pvPortMallocCaps(SHA1_RES_L, MALLOC_CAP_8BIT); 130 | 131 | //Check if malloc suceeded 132 | if ((p_SHA1_Inp != NULL) && (p_SHA1_result != NULL)) { 133 | 134 | //receive handshake request 135 | if (netconn_recv(conn, &inbuf) == ERR_OK) { 136 | 137 | //read buffer 138 | netbuf_data(inbuf, (void**) &buf, &i); 139 | 140 | //write static key into SHA1 Input 141 | for (i = 0; i < sizeof(WS_sec_conKey); i++) 142 | p_SHA1_Inp[i + WS_CLIENT_KEY_L] = WS_sec_conKey[i]; 143 | 144 | //find Client Sec-WebSocket-Key: 145 | p_buf = strstr(buf, WS_sec_WS_keys); 146 | 147 | //check if needle "Sec-WebSocket-Key:" was found 148 | if (p_buf != NULL) { 149 | 150 | //get Client Key 151 | for (i = 0; i < WS_CLIENT_KEY_L; i++) 152 | p_SHA1_Inp[i] = *(p_buf + sizeof(WS_sec_WS_keys) + i); 153 | 154 | // calculate hash 155 | esp_sha(SHA1, (unsigned char*) p_SHA1_Inp, strlen(p_SHA1_Inp), 156 | (unsigned char*) p_SHA1_result); 157 | 158 | //hex to base64 159 | p_buf = (char*) _base64_encode((unsigned char*) p_SHA1_result, 160 | SHA1_RES_L, (size_t*) &i); 161 | 162 | //free SHA1 input 163 | free(p_SHA1_Inp); 164 | 165 | //free SHA1 result 166 | free(p_SHA1_result); 167 | 168 | //allocate memory for handshake 169 | p_payload = pvPortMallocCaps( 170 | sizeof(WS_srv_hs) + i - WS_SPRINTF_ARG_L, 171 | MALLOC_CAP_8BIT); 172 | 173 | //check if malloc suceeded 174 | if (p_payload != NULL) { 175 | 176 | //prepare handshake 177 | sprintf(p_payload, WS_srv_hs, i - 1, p_buf); 178 | 179 | //send handshake 180 | netconn_write(conn, p_payload, strlen(p_payload), 181 | NETCONN_COPY); 182 | 183 | //free base 64 encoded sec key 184 | free(p_buf); 185 | 186 | //free handshake memory 187 | free(p_payload); 188 | 189 | //set pointer to open WebSocket connection 190 | WS_conn = conn; 191 | 192 | //Wait for new data 193 | while (netconn_recv(conn, &inbuf) == ERR_OK) { 194 | 195 | //read data from inbuf 196 | netbuf_data(inbuf, (void**) &buf, &i); 197 | 198 | //get pointer to header 199 | p_frame_hdr = (WS_frame_header_t*) buf; 200 | 201 | //check if clients wants to close the connection 202 | if (p_frame_hdr->opcode == WS_OP_CLS) 203 | break; 204 | 205 | //get payload length 206 | if (p_frame_hdr->payload_length <= WS_STD_LEN) { 207 | 208 | //get beginning of mask or payload 209 | p_buf = (char*) &buf[sizeof(WS_frame_header_t)]; 210 | 211 | //check if content is masked 212 | if (p_frame_hdr->mask) { 213 | 214 | //allocate memory for decoded message 215 | p_payload = pvPortMallocCaps( 216 | p_frame_hdr->payload_length + 1, 217 | MALLOC_CAP_8BIT); 218 | 219 | //check if malloc succeeded 220 | if (p_payload != NULL) { 221 | 222 | //decode playload 223 | for (i = 0; i < p_frame_hdr->payload_length; 224 | i++) 225 | p_payload[i] = (p_buf + WS_MASK_L)[i] 226 | ^ p_buf[i % WS_MASK_L]; 227 | 228 | //add 0 terminator 229 | p_payload[p_frame_hdr->payload_length] = 0; 230 | } 231 | } else 232 | //content is not masked 233 | p_payload = p_buf; 234 | 235 | //do stuff 236 | if ((p_payload != NULL) && (p_frame_hdr->opcode == WS_OP_TXT)) { 237 | 238 | //prepare FreeRTOS message 239 | WebSocket_frame_t __ws_frame; 240 | __ws_frame.conenction=conn; 241 | __ws_frame.frame_header=*p_frame_hdr; 242 | __ws_frame.payload_length=p_frame_hdr->payload_length; 243 | __ws_frame.payload=p_payload; 244 | 245 | //send message 246 | xQueueSendFromISR(WebSocket_rx_queue,&__ws_frame,0); 247 | } 248 | 249 | //free payload buffer (in this demo done by the receive task) 250 | // if (p_frame_hdr->mask && p_payload != NULL) 251 | // free(p_payload); 252 | 253 | } //p_frame_hdr->payload_length<126 254 | 255 | //free input buffer 256 | netbuf_delete(inbuf); 257 | 258 | } //while(netconn_recv(conn, &inbuf)==ERR_OK) 259 | } //p_payload!=NULL 260 | } //check if needle "Sec-WebSocket-Key:" was found 261 | } //receive handshake 262 | } //p_SHA1_Inp!=NULL&p_SHA1_result!=NULL 263 | 264 | //release pointer to open WebSocket connection 265 | WS_conn = NULL; 266 | 267 | //delete buffer 268 | netbuf_delete(inbuf); 269 | 270 | // Close the connection 271 | netconn_close(conn); 272 | 273 | //Delete connection 274 | netconn_delete(conn); 275 | 276 | } 277 | 278 | void ws_server(void *pvParameters) { 279 | //connection references 280 | struct netconn *conn, *newconn; 281 | 282 | //set up new TCP listener 283 | conn = netconn_new(NETCONN_TCP); 284 | netconn_bind(conn, NULL, WS_PORT); 285 | netconn_listen(conn); 286 | 287 | //wait for connections 288 | while (netconn_accept(conn, &newconn) == ERR_OK) 289 | ws_server_netconn_serve(newconn); 290 | 291 | //close connection 292 | netconn_close(conn); 293 | netconn_delete(conn); 294 | } 295 | -------------------------------------------------------------------------------- /main/WebSocket_Task.h: -------------------------------------------------------------------------------- 1 | /** 2 | * @section License 3 | * 4 | * The MIT License (MIT) 5 | * 6 | * Copyright (c) 2017, Thomas Barth, barth-dev.de 7 | * 8 | * Permission is hereby granted, free of charge, to any person 9 | * obtaining a copy of this software and associated documentation 10 | * files (the "Software"), to deal in the Software without 11 | * restriction, including without limitation the rights to use, copy, 12 | * modify, merge, publish, distribute, sublicense, and/or sell copies 13 | * of the Software, and to permit persons to whom the Software is 14 | * furnished to do so, subject to the following conditions: 15 | * 16 | * The above copyright notice and this permission notice shall be 17 | * included in all copies or substantial portions of the Software. 18 | * 19 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 20 | * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 21 | * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 22 | * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS 23 | * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN 24 | * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 25 | * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 26 | * SOFTWARE. 27 | */ 28 | 29 | 30 | #ifndef _WEBSOCKET_TASK_H_ 31 | #define _WEBSOCKET_TASK_H_ 32 | 33 | #include "lwip/api.h" 34 | 35 | #define WS_MASK_L 0x4 /**< \brief Length of MASK field in WebSocket Header*/ 36 | 37 | 38 | /** \brief Websocket frame header type*/ 39 | typedef struct { 40 | uint8_t opcode :WS_MASK_L; 41 | uint8_t reserved :3; 42 | uint8_t FIN :1; 43 | uint8_t payload_length :7; 44 | uint8_t mask :1; 45 | } WS_frame_header_t; 46 | 47 | /** \brief Websocket frame type*/ 48 | typedef struct{ 49 | struct netconn* conenction; 50 | WS_frame_header_t frame_header; 51 | size_t payload_length; 52 | char* payload; 53 | }WebSocket_frame_t; 54 | 55 | 56 | /** 57 | * \brief Send data to the websocket client 58 | * 59 | * \return #ERR_VAL: Payload length exceeded 2^7 bytes. 60 | * #ERR_CONN: There is no open connection 61 | * #ERR_OK: Header and payload send 62 | * all other values: derived from #netconn_write (sending frame header or payload) 63 | */ 64 | err_t WS_write_data(char* p_data, size_t length); 65 | 66 | /** 67 | * \brief WebSocket Server task 68 | */ 69 | void ws_server(void *pvParameters); 70 | 71 | 72 | #endif /* _WEBSOCKET_TASK_H_ */ 73 | -------------------------------------------------------------------------------- /main/component.mk: -------------------------------------------------------------------------------- 1 | # 2 | # Main component makefile. 3 | # 4 | # This Makefile can be left empty. By default, it will take the sources in the 5 | # src/ directory, compile them and link them into lib(subdirectory_name).a 6 | # in the build directory. This behaviour is entirely configurable, 7 | # please read the ESP-IDF documents if you need to do this. 8 | # 9 | -------------------------------------------------------------------------------- /main/main.c: -------------------------------------------------------------------------------- 1 | /** 2 | * @section License 3 | * 4 | * The MIT License (MIT) 5 | * 6 | * Copyright (c) 2017, Thomas Barth, barth-dev.de 7 | * 8 | * Permission is hereby granted, free of charge, to any person 9 | * obtaining a copy of this software and associated documentation 10 | * files (the "Software"), to deal in the Software without 11 | * restriction, including without limitation the rights to use, copy, 12 | * modify, merge, publish, distribute, sublicense, and/or sell copies 13 | * of the Software, and to permit persons to whom the Software is 14 | * furnished to do so, subject to the following conditions: 15 | * 16 | * The above copyright notice and this permission notice shall be 17 | * included in all copies or substantial portions of the Software. 18 | * 19 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 20 | * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 21 | * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 22 | * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS 23 | * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN 24 | * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 25 | * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 26 | * SOFTWARE. 27 | * 28 | * \version 0.1 29 | * \brief A basic WebSocket Server Espressif ESP32 30 | * 31 | * Within this demo, a very basic WebSocket server is created, which loops back WebSocket messages with a maximum length of 125 bytes. 32 | * \see http://www.barth-dev.de/websockets-on-the-esp32 33 | */ 34 | 35 | #include "freertos/FreeRTOS.h" 36 | #include "freertos/task.h" 37 | 38 | #include "esp_wifi.h" 39 | #include "esp_system.h" 40 | #include "esp_event.h" 41 | #include "esp_event_loop.h" 42 | #include "nvs_flash.h" 43 | 44 | #include "WebSocket_Task.h" 45 | 46 | //WebSocket frame receive queue 47 | QueueHandle_t WebSocket_rx_queue; 48 | 49 | void task_process_WebSocket( void *pvParameters ){ 50 | (void)pvParameters; 51 | 52 | //frame buffer 53 | WebSocket_frame_t __RX_frame; 54 | 55 | //create WebSocket RX Queue 56 | WebSocket_rx_queue = xQueueCreate(10,sizeof(WebSocket_frame_t)); 57 | 58 | while (1){ 59 | //receive next WebSocket frame from queue 60 | if(xQueueReceive(WebSocket_rx_queue,&__RX_frame, 3*portTICK_PERIOD_MS)==pdTRUE){ 61 | 62 | //write frame inforamtion to UART 63 | printf("New Websocket frame. Length %d, payload %.*s \r\n", __RX_frame.payload_length, __RX_frame.payload_length, __RX_frame.payload); 64 | 65 | //loop back frame 66 | WS_write_data(__RX_frame.payload, __RX_frame.payload_length); 67 | 68 | //free memory 69 | if (__RX_frame.payload != NULL) 70 | free(__RX_frame.payload); 71 | 72 | } 73 | } 74 | } 75 | 76 | esp_err_t event_handler(void *ctx, system_event_t *event) 77 | { 78 | return ESP_OK; 79 | } 80 | 81 | void app_main(void) 82 | { 83 | 84 | nvs_flash_init(); 85 | tcpip_adapter_init(); 86 | ESP_ERROR_CHECK( esp_event_loop_init(event_handler, NULL) ); 87 | wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT(); 88 | ESP_ERROR_CHECK( esp_wifi_init(&cfg) ); 89 | ESP_ERROR_CHECK( esp_wifi_set_storage(WIFI_STORAGE_RAM) ); 90 | ESP_ERROR_CHECK( esp_wifi_set_mode(WIFI_MODE_STA) ); 91 | wifi_config_t sta_config = { 92 | .sta = { 93 | .ssid = "access_point_name", 94 | .password = "password", 95 | .bssid_set = false 96 | } 97 | }; 98 | ESP_ERROR_CHECK( esp_wifi_set_config(WIFI_IF_STA, &sta_config) ); 99 | ESP_ERROR_CHECK( esp_wifi_start() ); 100 | ESP_ERROR_CHECK( esp_wifi_connect() ); 101 | 102 | //create WebSocker receive task 103 | xTaskCreate(&task_process_WebSocket, "ws_process_rx", 2048, NULL, 5, NULL); 104 | 105 | //Create Websocket Server Task 106 | xTaskCreate(&ws_server, "ws_server", 2048, NULL, 5, NULL); 107 | 108 | } 109 | --------------------------------------------------------------------------------