├── .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 |
--------------------------------------------------------------------------------