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