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