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) 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.0-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() 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#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}