Events and callback functions
Library uses events to notify application layer for (possible, but not limited to) unexpected events. This concept is used aswell for commands with longer executing time, such as scanning access points or when application starts new connection as client mode.
There are 3
types of events/callbacks available:
Global event callback function, assigned when initializing library
Connection specific event callback function, to process only events related to connection, such as connection error, data send, data receive, connection closed
API function call based event callback function
Every callback is always called from protected area of middleware (when exclusing access is granted to single thread only),
and it can be called from one of these 3
threads:
Producing thread
Processing thread
Input thread, when
LWESP_CFG_INPUT_USE_PROCESS
is enabled andlwesp_input_process()
function is called
Tip
Check Inter thread communication for more details about Producing and Processing thread.
Global event callback
Global event callback function is assigned at library initialization. It is used by the application to receive any kind of event, except the one related to connection:
ESP station successfully connected to access point
ESP physical device reset has been detected
Restore operation finished
New station has connected to access point
and many more..
Tip
Check Event management section for different kind of events
By default, global event function is single function.
If the application tries to split different events with different callback functions,
it is possible to do so by using lwesp_evt_register()
function to register a new,
custom, event function.
Tip
Implementation of Netconn API leverages lwesp_evt_register()
to
receive event when station disconnected from wifi access point.
Check its source file for actual implementation.
1/**
2 * \file lwesp_netconn.c
3 * \brief API functions for sequential calls
4 */
5
6/*
7 * Copyright (c) 2023 Tilen MAJERLE
8 *
9 * Permission is hereby granted, free of charge, to any person
10 * obtaining a copy of this software and associated documentation
11 * files (the "Software"), to deal in the Software without restriction,
12 * including without limitation the rights to use, copy, modify, merge,
13 * publish, distribute, sublicense, and/or sell copies of the Software,
14 * and to permit persons to whom the Software is furnished to do so,
15 * subject to the following conditions:
16 *
17 * The above copyright notice and this permission notice shall be
18 * included in all copies or substantial portions of the Software.
19 *
20 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
21 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
22 * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE
23 * AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
24 * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
25 * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
26 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
27 * OTHER DEALINGS IN THE SOFTWARE.
28 *
29 * This file is part of LwESP - Lightweight ESP-AT parser library.
30 *
31 * Author: Tilen MAJERLE <tilen@majerle.eu>
32 * Version: v1.1.2-dev
33 */
34#include "lwesp/lwesp_netconn.h"
35#include "lwesp/lwesp_conn.h"
36#include "lwesp/lwesp_mem.h"
37#include "lwesp/lwesp_private.h"
38
39#if LWESP_CFG_NETCONN || __DOXYGEN__
40
41/* Check conditions */
42#if LWESP_CFG_NETCONN_RECEIVE_QUEUE_LEN < 2
43#error "LWESP_CFG_NETCONN_RECEIVE_QUEUE_LEN must be greater or equal to 2"
44#endif /* LWESP_CFG_NETCONN_RECEIVE_QUEUE_LEN < 2 */
45
46#if LWESP_CFG_NETCONN_ACCEPT_QUEUE_LEN < 2
47#error "LWESP_CFG_NETCONN_ACCEPT_QUEUE_LEN must be greater or equal to 2"
48#endif /* LWESP_CFG_NETCONN_ACCEPT_QUEUE_LEN < 2 */
49
50/* Check for IP status */
51#if LWESP_CFG_IPV6
52#define NETCONN_IS_TCP(nc) ((nc)->type == LWESP_NETCONN_TYPE_TCP || (nc)->type == LWESP_NETCONN_TYPE_TCPV6)
53#define NETCONN_IS_SSL(nc) ((nc)->type == LWESP_NETCONN_TYPE_SSL || (nc)->type == LWESP_NETCONN_TYPE_SSLV6)
54#define NETCONN_IS_UDP(nc) ((nc)->type == LWESP_NETCONN_TYPE_UDP || (nc)->type == LWESP_NETCONN_TYPE_UDPV6)
55#else
56#define NETCONN_IS_TCP(nc) ((nc)->type == LWESP_NETCONN_TYPE_TCP)
57#define NETCONN_IS_SSL(nc) ((nc)->type == LWESP_NETCONN_TYPE_SSL)
58#define NETCONN_IS_UDP(nc) ((nc)->type == LWESP_NETCONN_TYPE_UDP)
59#endif /* LWESP_CFG_IPV6 */
60
61/**
62 * \brief Sequential API structure
63 */
64typedef struct lwesp_netconn {
65 struct lwesp_netconn* next; /*!< Linked list entry */
66
67 lwesp_netconn_type_t type; /*!< Netconn type */
68 lwesp_port_t listen_port; /*!< Port on which we are listening */
69
70 size_t rcv_packets; /*!< Number of received packets so far on this connection */
71 lwesp_conn_p conn; /*!< Pointer to actual connection */
72
73 lwesp_sys_mbox_t mbox_accept; /*!< List of active connections waiting to be processed */
74 lwesp_sys_mbox_t mbox_receive; /*!< Message queue for receive mbox */
75 size_t mbox_receive_entries; /*!< Number of entries written to receive mbox */
76
77 lwesp_linbuff_t buff; /*!< Linear buffer structure */
78
79 uint16_t conn_timeout; /*!< Connection timeout in units of seconds when
80 netconn is in server (listen) mode.
81 Connection will be automatically closed if there is no
82 data exchange in time. Set to `0` when timeout feature is disabled. */
83
84#if LWESP_CFG_NETCONN_RECEIVE_TIMEOUT || __DOXYGEN__
85 uint32_t rcv_timeout; /*!< Receive timeout in unit of milliseconds */
86#endif
87} lwesp_netconn_t;
88
89static uint8_t recv_closed = 0xFF, recv_not_present = 0xFF;
90static lwesp_netconn_t* listen_api; /*!< Main connection in listening mode */
91static lwesp_netconn_t* netconn_list; /*!< Linked list of netconn entries */
92
93/**
94 * \brief Flush all mboxes and clear possible used memories
95 * \param[in] nc: Pointer to netconn to flush
96 * \param[in] protect: Set to 1 to protect against multi-thread access
97 */
98static void
99flush_mboxes(lwesp_netconn_t* nc, uint8_t protect) {
100 lwesp_pbuf_p pbuf;
101 lwesp_netconn_t* new_nc;
102 if (protect) {
103 lwesp_core_lock();
104 }
105 if (lwesp_sys_mbox_isvalid(&nc->mbox_receive)) {
106 while (lwesp_sys_mbox_getnow(&nc->mbox_receive, (void**)&pbuf)) {
107 if (nc->mbox_receive_entries > 0) {
108 --nc->mbox_receive_entries;
109 }
110 if (pbuf != NULL && (uint8_t*)pbuf != (uint8_t*)&recv_closed) {
111 lwesp_pbuf_free(pbuf); /* Free received data buffers */
112 }
113 }
114 lwesp_sys_mbox_delete(&nc->mbox_receive); /* Delete message queue */
115 lwesp_sys_mbox_invalid(&nc->mbox_receive); /* Invalid handle */
116 }
117 if (lwesp_sys_mbox_isvalid(&nc->mbox_accept)) {
118 while (lwesp_sys_mbox_getnow(&nc->mbox_accept, (void**)&new_nc)) {
119 if (new_nc != NULL && (uint8_t*)new_nc != (uint8_t*)&recv_closed
120 && (uint8_t*)new_nc != (uint8_t*)&recv_not_present) {
121 lwesp_netconn_close(new_nc); /* Close netconn connection */
122 }
123 }
124 lwesp_sys_mbox_delete(&nc->mbox_accept); /* Delete message queue */
125 lwesp_sys_mbox_invalid(&nc->mbox_accept); /* Invalid handle */
126 }
127 if (protect) {
128 lwesp_core_unlock();
129 }
130}
131
132/**
133 * \brief Callback function for every server connection
134 * \param[in] evt: Pointer to callback structure
135 * \return Member of \ref lwespr_t enumeration
136 */
137static lwespr_t
138netconn_evt(lwesp_evt_t* evt) {
139 lwesp_conn_p conn;
140 lwesp_netconn_t* nc = NULL;
141 uint8_t close = 0;
142
143 conn = lwesp_conn_get_from_evt(evt); /* Get connection from event */
144 switch (lwesp_evt_get_type(evt)) {
145 /*
146 * A new connection has been active
147 * and should be handled by netconn API
148 */
149 case LWESP_EVT_CONN_ACTIVE: { /* A new connection active is active */
150 if (lwesp_conn_is_client(conn)) { /* Was connection started by us? */
151 nc = lwesp_conn_get_arg(conn); /* Argument should be already set */
152 if (nc != NULL) {
153 nc->conn = conn; /* Save actual connection */
154 } else {
155 close = 1; /* Close this connection, invalid netconn */
156 }
157
158 /* Is the connection server type and we have known listening API? */
159 } else if (lwesp_conn_is_server(conn) && listen_api != NULL) {
160 /*
161 * Create a new netconn structure
162 * and set it as connection argument.
163 */
164 nc = lwesp_netconn_new(LWESP_NETCONN_TYPE_TCP); /* Create new API */
165 LWESP_DEBUGW(LWESP_CFG_DBG_NETCONN | LWESP_DBG_TYPE_TRACE | LWESP_DBG_LVL_WARNING, nc == NULL,
166 "[LWESP NETCONN] Cannot create new structure for incoming server connection!\r\n");
167
168 if (nc != NULL) {
169 nc->conn = conn; /* Set connection handle */
170 lwesp_conn_set_arg(conn, nc); /* Set argument for connection */
171
172 /*
173 * In case there is no listening connection,
174 * simply close the connection
175 */
176 if (!lwesp_sys_mbox_isvalid(&listen_api->mbox_accept)
177 || !lwesp_sys_mbox_putnow(&listen_api->mbox_accept, nc)) {
178 close = 1;
179 }
180 } else {
181 close = 1;
182 }
183 } else {
184 LWESP_DEBUGW(LWESP_CFG_DBG_NETCONN | LWESP_DBG_TYPE_TRACE | LWESP_DBG_LVL_WARNING, listen_api == NULL,
185 "[LWESP NETCONN] Closing connection as there is no listening API in netconn!\r\n");
186 close = 1; /* Close the connection at this point */
187 }
188
189 /* Decide if some events want to close the connection */
190 if (close) {
191 if (nc != NULL) {
192 lwesp_conn_set_arg(conn, NULL); /* Reset argument */
193 lwesp_netconn_delete(nc); /* Free memory for API */
194 }
195 lwesp_conn_close(conn, 0); /* Close the connection */
196 close = 0;
197 }
198 break;
199 }
200
201 /*
202 * We have a new data received which
203 * should have netconn structure as argument
204 */
205 case LWESP_EVT_CONN_RECV: {
206 lwesp_pbuf_p pbuf;
207
208 nc = lwesp_conn_get_arg(conn); /* Get API from connection */
209 pbuf = lwesp_evt_conn_recv_get_buff(evt); /* Get received buff */
210
211 lwesp_pbuf_ref(pbuf); /* Increase reference counter */
212 if (nc == NULL || !lwesp_sys_mbox_isvalid(&nc->mbox_receive)
213 || !lwesp_sys_mbox_putnow(&nc->mbox_receive, pbuf)) {
214 LWESP_DEBUGF(LWESP_CFG_DBG_NETCONN, "[LWESP NETCONN] Ignoring more data for receive!\r\n");
215 lwesp_pbuf_free(pbuf); /* Free pbuf */
216 return lwespOKIGNOREMORE; /* Return OK to free the memory and ignore further data */
217 }
218 ++nc->mbox_receive_entries; /* Increase number of packets in receive mbox */
219 /* Check against 1 less to still allow potential close event to be written to queue */
220 if (nc->mbox_receive_entries >= (LWESP_CFG_NETCONN_RECEIVE_QUEUE_LEN - 1)) {
221 conn->status.f.receive_blocked = 1; /* Block reading more data */
222 }
223
224 ++nc->rcv_packets; /* Increase number of packets received */
225 LWESP_DEBUGF(LWESP_CFG_DBG_NETCONN | LWESP_DBG_TYPE_TRACE,
226 "[LWESP NETCONN] Received pbuf contains %d bytes. Handle written to receive mbox\r\n",
227 (int)lwesp_pbuf_length(pbuf, 0));
228 break;
229 }
230
231 /* Connection was just closed */
232 case LWESP_EVT_CONN_CLOSE: {
233 nc = lwesp_conn_get_arg(conn); /* Get API from connection */
234
235 /*
236 * In case we have a netconn available,
237 * simply write pointer to received variable to indicate closed state
238 */
239 if (nc != NULL && lwesp_sys_mbox_isvalid(&nc->mbox_receive)) {
240 if (lwesp_sys_mbox_putnow(&nc->mbox_receive, (void*)&recv_closed)) {
241 ++nc->mbox_receive_entries;
242 }
243 }
244
245 break;
246 }
247 default:
248 return lwespERR;
249 }
250 return lwespOK;
251}
252
253/**
254 * \brief Global event callback function
255 * \param[in] evt: Callback information and data
256 * \return \ref lwespOK on success, member of \ref lwespr_t otherwise
257 */
258static lwespr_t
259lwesp_evt(lwesp_evt_t* evt) {
260 switch (lwesp_evt_get_type(evt)) {
261 case LWESP_EVT_WIFI_DISCONNECTED: { /* Wifi disconnected event */
262 if (listen_api != NULL) { /* Check if listen API active */
263 lwesp_sys_mbox_putnow(&listen_api->mbox_accept, &recv_closed);
264 }
265 break;
266 }
267 case LWESP_EVT_DEVICE_PRESENT: { /* Device present event */
268 if (listen_api != NULL && !lwesp_device_is_present()) { /* Check if device present */
269 lwesp_sys_mbox_putnow(&listen_api->mbox_accept, &recv_not_present);
270 }
271 }
272 default:
273 break;
274 }
275 return lwespOK;
276}
277
278/**
279 * \brief Create new netconn connection
280 * \param[in] type: Netconn connection type
281 * \return New netconn connection on success, `NULL` otherwise
282 */
283lwesp_netconn_p
284lwesp_netconn_new(lwesp_netconn_type_t type) {
285 lwesp_netconn_t* a;
286 static uint8_t first = 1;
287
288 /* Register only once! */
289 lwesp_core_lock();
290 if (first) {
291 first = 0;
292 lwesp_evt_register(lwesp_evt); /* Register global event function */
293 }
294 lwesp_core_unlock();
295 a = lwesp_mem_calloc(1, sizeof(*a)); /* Allocate memory for core object */
296 if (a != NULL) {
297 a->type = type; /* Save netconn type */
298 a->conn_timeout = 0; /* Default connection timeout */
299 if (!lwesp_sys_mbox_create(
300 &a->mbox_accept, LWESP_CFG_NETCONN_ACCEPT_QUEUE_LEN)) { /* Allocate memory for accepting message box */
301 LWESP_DEBUGF(LWESP_CFG_DBG_NETCONN | LWESP_DBG_TYPE_TRACE | LWESP_DBG_LVL_DANGER,
302 "[LWESP NETCONN] Cannot create accept MBOX\r\n");
303 goto free_ret;
304 }
305 if (!lwesp_sys_mbox_create(
306 &a->mbox_receive,
307 LWESP_CFG_NETCONN_RECEIVE_QUEUE_LEN)) { /* Allocate memory for receiving message box */
308 LWESP_DEBUGF(LWESP_CFG_DBG_NETCONN | LWESP_DBG_TYPE_TRACE | LWESP_DBG_LVL_DANGER,
309 "[LWESP NETCONN] Cannot create receive MBOX\r\n");
310 goto free_ret;
311 }
312 lwesp_core_lock();
313 if (netconn_list == NULL) { /* Add new netconn to the existing list */
314 netconn_list = a;
315 } else {
316 a->next = netconn_list; /* Add it to beginning of the list */
317 netconn_list = a;
318 }
319 lwesp_core_unlock();
320 }
321 return a;
322free_ret:
323 if (lwesp_sys_mbox_isvalid(&a->mbox_accept)) {
324 lwesp_sys_mbox_delete(&a->mbox_accept);
325 lwesp_sys_mbox_invalid(&a->mbox_accept);
326 }
327 if (lwesp_sys_mbox_isvalid(&a->mbox_receive)) {
328 lwesp_sys_mbox_delete(&a->mbox_receive);
329 lwesp_sys_mbox_invalid(&a->mbox_receive);
330 }
331 if (a != NULL) {
332 lwesp_mem_free_s((void**)&a);
333 }
334 return NULL;
335}
336
337/**
338 * \brief Delete netconn connection
339 * \param[in] nc: Netconn handle
340 * \return \ref lwespOK on success, member of \ref lwespr_t enumeration otherwise
341 */
342lwespr_t
343lwesp_netconn_delete(lwesp_netconn_p nc) {
344 LWESP_ASSERT(nc != NULL);
345
346 lwesp_core_lock();
347 flush_mboxes(nc, 0); /* Clear mboxes */
348
349 /* Stop listening on netconn */
350 if (nc == listen_api) {
351 listen_api = NULL;
352 lwesp_core_unlock();
353 lwesp_set_server(0, nc->listen_port, 0, 0, NULL, NULL, NULL, 1);
354 lwesp_core_lock();
355 }
356
357 /* Remove netconn from linkedlist */
358 if (nc == netconn_list) {
359 netconn_list = netconn_list->next; /* Remove first from linked list */
360 } else if (netconn_list != NULL) {
361 lwesp_netconn_p tmp, prev;
362 /* Find element on the list */
363 for (prev = netconn_list, tmp = netconn_list->next; tmp != NULL; prev = tmp, tmp = tmp->next) {
364 if (nc == tmp) {
365 prev->next = tmp->next; /* Remove tmp from linked list */
366 break;
367 }
368 }
369 }
370 lwesp_core_unlock();
371
372 lwesp_mem_free_s((void**)&nc);
373 return lwespOK;
374}
375
376/**
377 * \brief Connect to server as client
378 * \param[in] nc: Netconn handle
379 * \param[in] host: Pointer to host, such as domain name or IP address in string format
380 * \param[in] port: Target port to use
381 * \return \ref lwespOK if successfully connected, member of \ref lwespr_t otherwise
382 */
383lwespr_t
384lwesp_netconn_connect(lwesp_netconn_p nc, const char* host, lwesp_port_t port) {
385 lwespr_t res;
386
387 LWESP_ASSERT(nc != NULL);
388 LWESP_ASSERT(host != NULL);
389 LWESP_ASSERT(port > 0);
390
391 /*
392 * Start a new connection as client and:
393 *
394 * - Set current netconn structure as argument
395 * - Set netconn callback function for connection management
396 * - Start connection in blocking mode
397 */
398 res = lwesp_conn_start(NULL, (lwesp_conn_type_t)nc->type, host, port, nc, netconn_evt, 1);
399 return res;
400}
401
402/**
403 * \brief Connect to server as client, allow keep-alive option
404 * \param[in] nc: Netconn handle
405 * \param[in] host: Pointer to host, such as domain name or IP address in string format
406 * \param[in] port: Target port to use
407 * \param[in] keep_alive: Keep alive period seconds
408 * \param[in] local_ip: Local ip in connected command
409 * \param[in] local_port: Local port address
410 * \param[in] mode: UDP mode
411 * \return \ref lwespOK if successfully connected, member of \ref lwespr_t otherwise
412 */
413lwespr_t
414lwesp_netconn_connect_ex(lwesp_netconn_p nc, const char* host, lwesp_port_t port, uint16_t keep_alive,
415 const char* local_ip, lwesp_port_t local_port, uint8_t mode) {
416 lwesp_conn_start_t cs = {0};
417 lwespr_t res;
418
419 LWESP_ASSERT(nc != NULL);
420 LWESP_ASSERT(host != NULL);
421 LWESP_ASSERT(port > 0);
422
423 /*
424 * Start a new connection as client and:
425 *
426 * - Set current netconn structure as argument
427 * - Set netconn callback function for connection management
428 * - Start connection in blocking mode
429 */
430 cs.type = (lwesp_conn_type_t)nc->type;
431 cs.remote_host = host;
432 cs.remote_port = port;
433 cs.local_ip = local_ip;
434 if (NETCONN_IS_TCP(nc) || NETCONN_IS_SSL(nc)) {
435 cs.ext.tcp_ssl.keep_alive = keep_alive;
436 } else {
437 cs.ext.udp.local_port = local_port;
438 cs.ext.udp.mode = mode;
439 }
440 res = lwesp_conn_startex(NULL, &cs, nc, netconn_evt, 1);
441 return res;
442}
443
444/**
445 * \brief Bind a connection to specific port, can be only used for server connections
446 * \param[in] nc: Netconn handle
447 * \param[in] port: Port used to bind a connection to
448 * \return \ref lwespOK on success, member of \ref lwespr_t enumeration otherwise
449 */
450lwespr_t
451lwesp_netconn_bind(lwesp_netconn_p nc, lwesp_port_t port) {
452 lwespr_t res = lwespOK;
453
454 LWESP_ASSERT(nc != NULL);
455
456 /*
457 * Protection is not needed as it is expected
458 * that this function is called only from single
459 * thread for single netconn connection,
460 * thus it is considered reentrant
461 */
462
463 nc->listen_port = port;
464
465 return res;
466}
467
468/**
469 * \brief Set timeout value in units of seconds when connection is in listening mode
470 * If new connection is accepted, it will be automatically closed after `seconds` elapsed
471 * without any data exchange.
472 * \note Call this function before you put connection to listen mode with \ref lwesp_netconn_listen
473 * \param[in] nc: Netconn handle used for listen mode
474 * \param[in] timeout: Time in units of seconds. Set to `0` to disable timeout feature
475 * \return \ref lwespOK on success, member of \ref lwespr_t otherwise
476 */
477lwespr_t
478lwesp_netconn_set_listen_conn_timeout(lwesp_netconn_p nc, uint16_t timeout) {
479 lwespr_t res = lwespOK;
480 LWESP_ASSERT(nc != NULL);
481
482 /*
483 * Protection is not needed as it is expected
484 * that this function is called only from single
485 * thread for single netconn connection,
486 * thus it is reentrant in this case
487 */
488
489 nc->conn_timeout = timeout;
490
491 return res;
492}
493
494/**
495 * \brief Listen on previously binded connection
496 * \param[in] nc: Netconn handle used to listen for new connections
497 * \return \ref lwespOK on success, member of \ref lwespr_t enumeration otherwise
498 */
499lwespr_t
500lwesp_netconn_listen(lwesp_netconn_p nc) {
501 return lwesp_netconn_listen_with_max_conn(nc, LWESP_CFG_MAX_CONNS);
502}
503
504/**
505 * \brief Listen on previously binded connection with max allowed connections at a time
506 * \param[in] nc: Netconn handle used to listen for new connections
507 * \param[in] max_connections: Maximal number of connections server can accept at a time
508 * This parameter may not be larger than \ref LWESP_CFG_MAX_CONNS
509 * \return \ref lwespOK on success, member of \ref lwespr_t otherwise
510 */
511lwespr_t
512lwesp_netconn_listen_with_max_conn(lwesp_netconn_p nc, uint16_t max_connections) {
513 lwespr_t res;
514
515 LWESP_ASSERT(nc != NULL);
516 LWESP_ASSERT(NETCONN_IS_TCP(nc));
517
518 /* Enable server on port and set default netconn callback */
519 if ((res = lwesp_set_server(1, nc->listen_port, LWESP_U16(LWESP_MIN(max_connections, LWESP_CFG_MAX_CONNS)),
520 nc->conn_timeout, netconn_evt, NULL, NULL, 1))
521 == lwespOK) {
522 lwesp_core_lock();
523 listen_api = nc; /* Set current main API in listening state */
524 lwesp_core_unlock();
525 }
526 return res;
527}
528
529/**
530 * \brief Accept a new connection
531 * \param[in] nc: Netconn handle used as base connection to accept new clients
532 * \param[out] client: Pointer to netconn handle to save new connection to
533 * \return \ref lwespOK on success, member of \ref lwespr_t enumeration otherwise
534 */
535lwespr_t
536lwesp_netconn_accept(lwesp_netconn_p nc, lwesp_netconn_p* client) {
537 lwesp_netconn_t* tmp;
538 uint32_t time;
539
540 LWESP_ASSERT(nc != NULL);
541 LWESP_ASSERT(client != NULL);
542 LWESP_ASSERT(NETCONN_IS_TCP(nc));
543 LWESP_ASSERT(nc == listen_api);
544
545 *client = NULL;
546 time = lwesp_sys_mbox_get(&nc->mbox_accept, (void**)&tmp, 0);
547 if (time == LWESP_SYS_TIMEOUT) {
548 return lwespTIMEOUT;
549 }
550 if ((uint8_t*)tmp == (uint8_t*)&recv_closed) {
551 lwesp_core_lock();
552 listen_api = NULL; /* Disable listening at this point */
553 lwesp_core_unlock();
554 return lwespERRWIFINOTCONNECTED; /* Wifi disconnected */
555 } else if ((uint8_t*)tmp == (uint8_t*)&recv_not_present) {
556 lwesp_core_lock();
557 listen_api = NULL; /* Disable listening at this point */
558 lwesp_core_unlock();
559 return lwespERRNODEVICE; /* Device not present */
560 }
561 *client = tmp; /* Set new pointer */
562 return lwespOK; /* We have a new connection */
563}
564
565/**
566 * \brief Write data to connection output buffers
567 * \note This function may only be used on TCP or SSL connections
568 * \param[in] nc: Netconn handle used to write data to
569 * \param[in] data: Pointer to data to write
570 * \param[in] btw: Number of bytes to write
571 * \return \ref lwespOK on success, member of \ref lwespr_t enumeration otherwise
572 */
573lwespr_t
574lwesp_netconn_write(lwesp_netconn_p nc, const void* data, size_t btw) {
575 size_t len, sent;
576 const uint8_t* d = data;
577 lwespr_t res;
578
579 LWESP_ASSERT(nc != NULL);
580 LWESP_ASSERT(NETCONN_IS_TCP(nc) || NETCONN_IS_SSL(nc));
581 LWESP_ASSERT(lwesp_conn_is_active(nc->conn));
582
583 /*
584 * Several steps are done in write process
585 *
586 * 1. Check if buffer is set and check if there is something to write to it.
587 * 1. In case buffer will be full after copy, send it and free memory.
588 * 2. Check how many bytes we can write directly without need to copy
589 * 3. Try to allocate a new buffer and copy remaining input data to it
590 * 4. In case buffer allocation fails, send data directly (may have impact on speed and effectivenes)
591 */
592
593 /* Step 1 */
594 if (nc->buff.buff != NULL) { /* Is there a write buffer ready to accept more data? */
595 len = LWESP_MIN(nc->buff.len - nc->buff.ptr, btw); /* Get number of bytes we can write to buffer */
596 if (len > 0) {
597 LWESP_MEMCPY(&nc->buff.buff[nc->buff.ptr], data, len); /* Copy memory to temporary write buffer */
598 d += len;
599 nc->buff.ptr += len;
600 btw -= len;
601 }
602
603 /* Step 1.1 */
604 if (nc->buff.ptr == nc->buff.len) {
605 res = lwesp_conn_send(nc->conn, nc->buff.buff, nc->buff.len, &sent, 1);
606
607 lwesp_mem_free_s((void**)&nc->buff.buff);
608 if (res != lwespOK) {
609 return res;
610 }
611 } else {
612 return lwespOK; /* Buffer is not full yet */
613 }
614 }
615
616 /* Step 2 */
617 if (btw >= LWESP_CFG_CONN_MAX_DATA_LEN) {
618 size_t rem;
619 rem = btw % LWESP_CFG_CONN_MAX_DATA_LEN; /* Get remaining bytes for max data length */
620 res = lwesp_conn_send(nc->conn, d, btw - rem, &sent, 1); /* Write data directly */
621 if (res != lwespOK) {
622 return res;
623 }
624 d += sent; /* Advance in data pointer */
625 btw -= sent; /* Decrease remaining data to send */
626 }
627
628 if (btw == 0) { /* Sent everything? */
629 return lwespOK;
630 }
631
632 /* Step 3 */
633 if (nc->buff.buff == NULL) { /* Check if we should allocate a new buffer */
634 nc->buff.buff = lwesp_mem_malloc(sizeof(*nc->buff.buff) * LWESP_CFG_CONN_MAX_DATA_LEN);
635 nc->buff.len = LWESP_CFG_CONN_MAX_DATA_LEN; /* Save buffer length */
636 nc->buff.ptr = 0; /* Save buffer pointer */
637 }
638
639 /* Step 4 */
640 if (nc->buff.buff != NULL) { /* Memory available? */
641 LWESP_MEMCPY(&nc->buff.buff[nc->buff.ptr], d, btw); /* Copy data to buffer */
642 nc->buff.ptr += btw;
643 } else { /* Still no memory available? */
644 return lwesp_conn_send(nc->conn, data, btw, NULL, 1); /* Simply send directly blocking */
645 }
646 return lwespOK;
647}
648
649/**
650 * \brief Flush buffered data on netconn TCP/SSL connection
651 * \note This function may only be used on TCP/SSL connection
652 * \param[in] nc: Netconn handle to flush data
653 * \return \ref lwespOK on success, member of \ref lwespr_t enumeration otherwise
654 */
655lwespr_t
656lwesp_netconn_flush(lwesp_netconn_p nc) {
657 LWESP_ASSERT(nc != NULL);
658 LWESP_ASSERT(NETCONN_IS_TCP(nc) || NETCONN_IS_SSL(nc));
659 LWESP_ASSERT(lwesp_conn_is_active(nc->conn));
660
661 /*
662 * In case we have data in write buffer,
663 * flush them out to network
664 */
665 if (nc->buff.buff != NULL) { /* Check remaining data */
666 if (nc->buff.ptr > 0) { /* Do we have data in current buffer? */
667 lwesp_conn_send(nc->conn, nc->buff.buff, nc->buff.ptr, NULL, 1); /* Send data */
668 }
669 lwesp_mem_free_s((void**)&nc->buff.buff);
670 }
671 return lwespOK;
672}
673
674/**
675 * \brief Send data on UDP connection to default IP and port
676 * \param[in] nc: Netconn handle used to send
677 * \param[in] data: Pointer to data to write
678 * \param[in] btw: Number of bytes to write
679 * \return \ref lwespOK on success, member of \ref lwespr_t enumeration otherwise
680 */
681lwespr_t
682lwesp_netconn_send(lwesp_netconn_p nc, const void* data, size_t btw) {
683 LWESP_ASSERT(nc != NULL);
684 LWESP_ASSERT(nc->type == LWESP_NETCONN_TYPE_UDP);
685 LWESP_ASSERT(lwesp_conn_is_active(nc->conn));
686
687 return lwesp_conn_send(nc->conn, data, btw, NULL, 1);
688}
689
690/**
691 * \brief Send data on UDP connection to specific IP and port
692 * \note Use this function in case of UDP type netconn
693 * \param[in] nc: Netconn handle used to send
694 * \param[in] ip: Pointer to IP address
695 * \param[in] port: Port number used to send data
696 * \param[in] data: Pointer to data to write
697 * \param[in] btw: Number of bytes to write
698 * \return \ref lwespOK on success, member of \ref lwespr_t enumeration otherwise
699 */
700lwespr_t
701lwesp_netconn_sendto(lwesp_netconn_p nc, const lwesp_ip_t* ip, lwesp_port_t port, const void* data, size_t btw) {
702 LWESP_ASSERT(nc != NULL);
703 LWESP_ASSERT(nc->type == LWESP_NETCONN_TYPE_UDP);
704 LWESP_ASSERT(lwesp_conn_is_active(nc->conn));
705
706 return lwesp_conn_sendto(nc->conn, ip, port, data, btw, NULL, 1);
707}
708
709/**
710 * \brief Receive data from connection
711 * \param[in] nc: Netconn handle used to receive from
712 * \param[in] pbuf: Pointer to pointer to save new receive buffer to.
713 * When function returns, user must check for valid pbuf value `pbuf != NULL`
714 * \return \ref lwespOK when new data ready
715 * \return \ref lwespCLOSED when connection closed by remote side
716 * \return \ref lwespTIMEOUT when receive timeout occurs
717 * \return Any other member of \ref lwespr_t otherwise
718 */
719lwespr_t
720lwesp_netconn_receive(lwesp_netconn_p nc, lwesp_pbuf_p* pbuf) {
721 LWESP_ASSERT(nc != NULL);
722 LWESP_ASSERT(pbuf != NULL);
723
724 *pbuf = NULL;
725#if LWESP_CFG_NETCONN_RECEIVE_TIMEOUT
726 /*
727 * Wait for new received data for up to specific timeout
728 * or throw error for timeout notification
729 */
730 if (nc->rcv_timeout == LWESP_NETCONN_RECEIVE_NO_WAIT) {
731 if (!lwesp_sys_mbox_getnow(&nc->mbox_receive, (void**)pbuf)) {
732 return lwespTIMEOUT;
733 }
734 } else if (lwesp_sys_mbox_get(&nc->mbox_receive, (void**)pbuf, nc->rcv_timeout) == LWESP_SYS_TIMEOUT) {
735 return lwespTIMEOUT;
736 }
737#else /* LWESP_CFG_NETCONN_RECEIVE_TIMEOUT */
738 /* Forever wait for new receive packet */
739 lwesp_sys_mbox_get(&nc->mbox_receive, (void**)pbuf, 0);
740#endif /* !LWESP_CFG_NETCONN_RECEIVE_TIMEOUT */
741
742 lwesp_core_lock();
743 if (nc->mbox_receive_entries > 0) {
744 --nc->mbox_receive_entries;
745 }
746 lwesp_core_unlock();
747
748 /* Check if connection closed */
749 if ((uint8_t*)(*pbuf) == (uint8_t*)&recv_closed) {
750 *pbuf = NULL; /* Reset pbuf */
751 return lwespCLOSED;
752 } else {
753 lwesp_core_lock();
754 nc->conn->status.f.receive_blocked = 0; /* Resume reading more data */
755 lwesp_conn_recved(nc->conn, *pbuf); /* Notify stack about received data */
756 lwesp_core_unlock();
757 }
758 return lwespOK; /* We have data available */
759}
760
761/**
762 * \brief Close a netconn connection
763 * \param[in] nc: Netconn handle to close
764 * \return \ref lwespOK on success, member of \ref lwespr_t enumeration otherwise
765 */
766lwespr_t
767lwesp_netconn_close(lwesp_netconn_p nc) {
768 lwesp_conn_p conn;
769
770 LWESP_ASSERT(nc != NULL);
771 LWESP_ASSERT(nc->conn != NULL);
772 LWESP_ASSERT(lwesp_conn_is_active(nc->conn));
773
774 lwesp_netconn_flush(nc); /* Flush data and ignore result */
775 conn = nc->conn;
776 nc->conn = NULL;
777
778 lwesp_conn_set_arg(conn, NULL); /* Reset argument */
779 lwesp_conn_close(conn, 1); /* Close the connection */
780 flush_mboxes(nc, 1); /* Flush message queues */
781 return lwespOK;
782}
783
784/**
785 * \brief Get connection number used for netconn
786 * \param[in] nc: Netconn handle
787 * \return `-1` on failure, connection number between `0` and \ref LWESP_CFG_MAX_CONNS otherwise
788 */
789int8_t
790lwesp_netconn_get_connnum(lwesp_netconn_p nc) {
791 if (nc != NULL && nc->conn != NULL) {
792 return lwesp_conn_getnum(nc->conn);
793 }
794 return -1;
795}
796
797#if LWESP_CFG_NETCONN_RECEIVE_TIMEOUT || __DOXYGEN__
798
799/**
800 * \brief Set timeout value for receiving data.
801 *
802 * When enabled, \ref lwesp_netconn_receive will only block for up to
803 * `timeout` value and will return if no new data within this time
804 *
805 * \param[in] nc: Netconn handle
806 * \param[in] timeout: Timeout in units of milliseconds.
807 * Set to `0` to disable timeout feature
808 * Set to `> 0` to set maximum milliseconds to wait before timeout
809 * Set to \ref LWESP_NETCONN_RECEIVE_NO_WAIT to enable non-blocking receive
810 */
811void
812lwesp_netconn_set_receive_timeout(lwesp_netconn_p nc, uint32_t timeout) {
813 nc->rcv_timeout = timeout;
814}
815
816/**
817 * \brief Get netconn receive timeout value
818 * \param[in] nc: Netconn handle
819 * \return Timeout in units of milliseconds.
820 * If value is `0`, timeout is disabled (wait forever)
821 */
822uint32_t
823lwesp_netconn_get_receive_timeout(lwesp_netconn_p nc) {
824 return nc->rcv_timeout;
825}
826
827#endif /* LWESP_CFG_NETCONN_RECEIVE_TIMEOUT || __DOXYGEN__ */
828
829/**
830 * \brief Get netconn connection handle
831 * \param[in] nc: Netconn handle
832 * \return ESP connection handle
833 */
834lwesp_conn_p
835lwesp_netconn_get_conn(lwesp_netconn_p nc) {
836 return nc->conn;
837}
838
839/**
840 * \brief Get netconn connection type
841 * \param[in] nc: Netconn handle
842 * \return ESP connection type
843 */
844lwesp_netconn_type_t
845lwesp_netconn_get_type(lwesp_netconn_p nc) {
846 return nc->type;
847}
848
849#endif /* LWESP_CFG_NETCONN || __DOXYGEN__ */
Connection specific event
This events are subset of global event callback. They work exactly the same way as global, but only receive events related to connections.
Tip
Connection related events start with LWESP_EVT_CONN_*
, such as LWESP_EVT_CONN_RECV
.
Check Event management for list of all connection events.
Connection events callback function is set for 2
cases:
Each client (when application starts connection) sets event callback function when trying to connect with
lwesp_conn_start()
functionApplication sets global event callback function when enabling server mode with
lwesp_set_server()
function
1#include "client.h"
2#include "lwesp/lwesp.h"
3
4/* Host parameter */
5#define CONN_HOST "example.com"
6#define CONN_PORT 80
7
8static lwespr_t conn_callback_func(lwesp_evt_t* evt);
9
10/**
11 * \brief Request data for connection
12 */
13static const
14uint8_t req_data[] = ""
15 "GET / HTTP/1.1\r\n"
16 "Host: " CONN_HOST "\r\n"
17 "Connection: close\r\n"
18 "\r\n";
19
20/**
21 * \brief Start a new connection(s) as client
22 */
23void
24client_connect(void) {
25 lwespr_t res;
26
27 /* Start a new connection as client in non-blocking mode */
28 if ((res = lwesp_conn_start(NULL, LWESP_CONN_TYPE_TCP, "example.com", 80, NULL, conn_callback_func, 0)) == lwespOK) {
29 printf("Connection to " CONN_HOST " started...\r\n");
30 } else {
31 printf("Cannot start connection to " CONN_HOST "!\r\n");
32 }
33
34 /* Start 2 more */
35 lwesp_conn_start(NULL, LWESP_CONN_TYPE_TCP, CONN_HOST, CONN_PORT, NULL, conn_callback_func, 0);
36
37 /*
38 * An example of connection which should fail in connecting.
39 * When this is the case, \ref LWESP_EVT_CONN_ERROR event should be triggered
40 * in callback function processing
41 */
42 lwesp_conn_start(NULL, LWESP_CONN_TYPE_TCP, CONN_HOST, 10, NULL, conn_callback_func, 0);
43}
44
45/**
46 * \brief Event callback function for connection-only
47 * \param[in] evt: Event information with data
48 * \return \ref lwespOK on success, member of \ref lwespr_t otherwise
49 */
50static lwespr_t
51conn_callback_func(lwesp_evt_t* evt) {
52 lwesp_conn_p conn;
53 lwespr_t res;
54 uint8_t conn_num;
55
56 conn = lwesp_conn_get_from_evt(evt); /* Get connection handle from event */
57 if (conn == NULL) {
58 return lwespERR;
59 }
60 conn_num = lwesp_conn_getnum(conn); /* Get connection number for identification */
61 switch (lwesp_evt_get_type(evt)) {
62 case LWESP_EVT_CONN_ACTIVE: { /* Connection just active */
63 printf("Connection %d active!\r\n", (int)conn_num);
64 res = lwesp_conn_send(conn, req_data, sizeof(req_data) - 1, NULL, 0); /* Start sending data in non-blocking mode */
65 if (res == lwespOK) {
66 printf("Sending request data to server...\r\n");
67 } else {
68 printf("Cannot send request data to server. Closing connection manually...\r\n");
69 lwesp_conn_close(conn, 0); /* Close the connection */
70 }
71 break;
72 }
73 case LWESP_EVT_CONN_CLOSE: { /* Connection closed */
74 if (lwesp_evt_conn_close_is_forced(evt)) {
75 printf("Connection %d closed by client!\r\n", (int)conn_num);
76 } else {
77 printf("Connection %d closed by remote side!\r\n", (int)conn_num);
78 }
79 break;
80 }
81 case LWESP_EVT_CONN_SEND: { /* Data send event */
82 lwespr_t res = lwesp_evt_conn_send_get_result(evt);
83 if (res == lwespOK) {
84 printf("Data sent successfully on connection %d...waiting to receive data from remote side...\r\n", (int)conn_num);
85 } else {
86 printf("Error while sending data on connection %d!\r\n", (int)conn_num);
87 }
88 break;
89 }
90 case LWESP_EVT_CONN_RECV: { /* Data received from remote side */
91 lwesp_pbuf_p pbuf = lwesp_evt_conn_recv_get_buff(evt);
92 lwesp_conn_recved(conn, pbuf); /* Notify stack about received pbuf */
93 printf("Received %d bytes on connection %d..\r\n", (int)lwesp_pbuf_length(pbuf, 1), (int)conn_num);
94 break;
95 }
96 case LWESP_EVT_CONN_ERROR: { /* Error connecting to server */
97 const char* host = lwesp_evt_conn_error_get_host(evt);
98 lwesp_port_t port = lwesp_evt_conn_error_get_port(evt);
99 printf("Error connecting to %s:%d\r\n", host, (int)port);
100 break;
101 }
102 default:
103 break;
104 }
105 return lwespOK;
106}
API call event
API function call event function is special type of event and is linked to command execution. It is especially useful when dealing with non-blocking commands to understand when specific command execution finished and when next operation could start.
Every API function, which directly operates with AT command on physical device layer,
has optional 2
parameters for API call event:
Callback function, called when command finished
Custom user parameter for callback function
Below is an example code for DNS resolver. It uses custom API callback function with custom argument, used to distinguis domain name (when multiple domains are to be resolved).
1/*
2 * This snippet shows how to use ESP's DNS module to
3 * obtain IP address from domain name
4 */
5#include "dns.h"
6#include "lwesp/lwesp.h"
7
8/* Host to resolve */
9#define DNS_HOST1 "example.com"
10#define DNS_HOST2 "example.net"
11
12/**
13 * \brief Variable to hold result of DNS resolver
14 */
15static lwesp_ip_t ip;
16
17/**
18 * \brief Function to print actual resolved IP address
19 */
20static void
21prv_print_ip(void) {
22 if (0) {
23#if LWESP_CFG_IPV6
24 } else if (ip.type == LWESP_IPTYPE_V6) {
25 printf("IPv6: %04X:%04X:%04X:%04X:%04X:%04X:%04X:%04X\r\n",
26 (unsigned)ip.addr.ip6.addr[0], (unsigned)ip.addr.ip6.addr[1], (unsigned)ip.addr.ip6.addr[2],
27 (unsigned)ip.addr.ip6.addr[3], (unsigned)ip.addr.ip6.addr[4], (unsigned)ip.addr.ip6.addr[5],
28 (unsigned)ip.addr.ip6.addr[6], (unsigned)ip.addr.ip6.addr[7]);
29#endif /* LWESP_CFG_IPV6 */
30 } else {
31 printf("IPv4: %d.%d.%d.%d\r\n",
32 (int)ip.addr.ip4.addr[0], (int)ip.addr.ip4.addr[1], (int)ip.addr.ip4.addr[2], (int)ip.addr.ip4.addr[3]);
33 }
34}
35
36/**
37 * \brief Event callback function for API call,
38 * called when API command finished with execution
39 */
40static void
41prv_dns_resolve_evt(lwespr_t res, void* arg) {
42 LWESP_UNUSED(arg);
43 /* Check result of command */
44 if (res == lwespOK) {
45 /* Print actual resolved IP */
46 prv_print_ip();
47 }
48}
49
50/**
51 * \brief Start DNS resolver
52 */
53void
54dns_start(void) {
55 /* Use DNS protocol to get IP address of domain name */
56
57 /* Get IP with non-blocking mode */
58 if (lwesp_dns_gethostbyname(DNS_HOST2, &ip, prv_dns_resolve_evt, DNS_HOST2, 0) == lwespOK) {
59 printf("Request for DNS record for " DNS_HOST2 " has started\r\n");
60 } else {
61 printf("Could not start command for DNS\r\n");
62 }
63
64 /* Get IP with blocking mode */
65 if (lwesp_dns_gethostbyname(DNS_HOST1, &ip, prv_dns_resolve_evt, DNS_HOST1, 1) == lwespOK) {
66 /* Print actual resolved IP */
67 prv_print_ip();
68 } else {
69 printf("Could not retrieve IP address for " DNS_HOST1 "\r\n");
70 }
71}