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:

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.

Netconn API module 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() function

  • Application sets global event callback function when enabling server mode with lwesp_set_server() function

An example of client with its dedicated event callback 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).

Simple example for API call event, using DNS module
 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}