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
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
/**
 * \file            lwesp_netconn.c
 * \brief           API functions for sequential calls
 */

/*
 * Copyright (c) 2020 Tilen MAJERLE
 *
 * Permission is hereby granted, free of charge, to any person
 * obtaining a copy of this software and associated documentation
 * files (the "Software"), to deal in the Software without restriction,
 * including without limitation the rights to use, copy, modify, merge,
 * publish, distribute, sublicense, and/or sell copies of the Software,
 * and to permit persons to whom the Software is furnished to do so,
 * subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be
 * included in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
 * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE
 * AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
 * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
 * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
 * OTHER DEALINGS IN THE SOFTWARE.
 *
 * This file is part of LwESP - Lightweight ESP-AT parser library.
 *
 * Author:          Tilen MAJERLE <tilen@majerle.eu>
 * Version:         v1.0.0
 */
#include "lwesp/lwesp_netconn.h"
#include "lwesp/lwesp_private.h"
#include "lwesp/lwesp_conn.h"
#include "lwesp/lwesp_mem.h"

#if LWESP_CFG_NETCONN || __DOXYGEN__

/* Check conditions */
#if LWESP_CFG_NETCONN_RECEIVE_QUEUE_LEN < 2
#error "LWESP_CFG_NETCONN_RECEIVE_QUEUE_LEN must be greater or equal to 2"
#endif /* LWESP_CFG_NETCONN_RECEIVE_QUEUE_LEN < 2 */

#if LWESP_CFG_NETCONN_ACCEPT_QUEUE_LEN < 2
#error "LWESP_CFG_NETCONN_ACCEPT_QUEUE_LEN must be greater or equal to 2"
#endif /* LWESP_CFG_NETCONN_ACCEPT_QUEUE_LEN < 2 */

/**
 * \brief           Sequential API structure
 */
typedef struct lwesp_netconn {
    struct lwesp_netconn* next;                 /*!< Linked list entry */

    lwesp_netconn_type_t type;                  /*!< Netconn type */
    lwesp_port_t listen_port;                   /*!< Port on which we are listening */

    size_t rcv_packets;                         /*!< Number of received packets so far on this connection */
    lwesp_conn_p conn;                          /*!< Pointer to actual connection */

    lwesp_sys_mbox_t mbox_accept;               /*!< List of active connections waiting to be processed */
    lwesp_sys_mbox_t mbox_receive;              /*!< Message queue for receive mbox */
    size_t mbox_receive_entries;                /*!< Number of entries written to receive mbox */

    lwesp_linbuff_t buff;                       /*!< Linear buffer structure */

    uint16_t conn_timeout;                      /*!< Connection timeout in units of seconds when
                                                    netconn is in server (listen) mode.
                                                    Connection will be automatically closed if there is no
                                                    data exchange in time. Set to `0` when timeout feature is disabled. */

#if LWESP_CFG_NETCONN_RECEIVE_TIMEOUT || __DOXYGEN__
    uint32_t rcv_timeout;                       /*!< Receive timeout in unit of milliseconds */
#endif
} lwesp_netconn_t;

static uint8_t recv_closed = 0xFF, recv_not_present = 0xFF;
static lwesp_netconn_t* listen_api;             /*!< Main connection in listening mode */
static lwesp_netconn_t* netconn_list;           /*!< Linked list of netconn entries */

/**
 * \brief           Flush all mboxes and clear possible used memories
 * \param[in]       nc: Pointer to netconn to flush
 * \param[in]       protect: Set to 1 to protect against multi-thread access
 */
static void
flush_mboxes(lwesp_netconn_t* nc, uint8_t protect) {
    lwesp_pbuf_p pbuf;
    lwesp_netconn_t* new_nc;
    if (protect) {
        lwesp_core_lock();
    }
    if (lwesp_sys_mbox_isvalid(&nc->mbox_receive)) {
        while (lwesp_sys_mbox_getnow(&nc->mbox_receive, (void**)&pbuf)) {
            if (nc->mbox_receive_entries > 0) {
                --nc->mbox_receive_entries;
            }
            if (pbuf != NULL && (uint8_t*)pbuf != (uint8_t*)&recv_closed) {
                lwesp_pbuf_free(pbuf);          /* Free received data buffers */
            }
        }
        lwesp_sys_mbox_delete(&nc->mbox_receive);   /* Delete message queue */
        lwesp_sys_mbox_invalid(&nc->mbox_receive);  /* Invalid handle */
    }
    if (lwesp_sys_mbox_isvalid(&nc->mbox_accept)) {
        while (lwesp_sys_mbox_getnow(&nc->mbox_accept, (void**)&new_nc)) {
            if (new_nc != NULL
                && (uint8_t*)new_nc != (uint8_t*)&recv_closed
                && (uint8_t*)new_nc != (uint8_t*)&recv_not_present) {
                lwesp_netconn_close(new_nc);    /* Close netconn connection */
            }
        }
        lwesp_sys_mbox_delete(&nc->mbox_accept);/* Delete message queue */
        lwesp_sys_mbox_invalid(&nc->mbox_accept);   /* Invalid handle */
    }
    if (protect) {
        lwesp_core_unlock();
    }
}

/**
 * \brief           Callback function for every server connection
 * \param[in]       evt: Pointer to callback structure
 * \return          Member of \ref lwespr_t enumeration
 */
static lwespr_t
netconn_evt(lwesp_evt_t* evt) {
    lwesp_conn_p conn;
    lwesp_netconn_t* nc = NULL;
    uint8_t close = 0;

    conn = lwesp_conn_get_from_evt(evt);        /* Get connection from event */
    switch (lwesp_evt_get_type(evt)) {
        /*
         * A new connection has been active
         * and should be handled by netconn API
         */
        case LWESP_EVT_CONN_ACTIVE: {           /* A new connection active is active */
            if (lwesp_conn_is_client(conn)) {   /* Was connection started by us? */
                nc = lwesp_conn_get_arg(conn);  /* Argument should be already set */
                if (nc != NULL) {
                    nc->conn = conn;            /* Save actual connection */
                } else {
                    close = 1;                  /* Close this connection, invalid netconn */
                }

            /* Is the connection server type and we have known listening API? */
            } else if (lwesp_conn_is_server(conn) && listen_api != NULL) {
                /*
                 * Create a new netconn structure
                 * and set it as connection argument.
                 */
                nc = lwesp_netconn_new(LWESP_NETCONN_TYPE_TCP); /* Create new API */
                LWESP_DEBUGW(LWESP_CFG_DBG_NETCONN | LWESP_DBG_TYPE_TRACE | LWESP_DBG_LVL_WARNING,
                           nc == NULL, "[NETCONN] Cannot create new structure for incoming server connection!\r\n");

                if (nc != NULL) {
                    nc->conn = conn;            /* Set connection handle */
                    lwesp_conn_set_arg(conn, nc);   /* Set argument for connection */

                    /*
                     * In case there is no listening connection,
                     * simply close the connection
                     */
                    if (!lwesp_sys_mbox_isvalid(&listen_api->mbox_accept)
                        || !lwesp_sys_mbox_putnow(&listen_api->mbox_accept, nc)) {
                        close = 1;
                    }
                } else {
                    close = 1;
                }
            } else {
                LWESP_DEBUGW(LWESP_CFG_DBG_NETCONN | LWESP_DBG_TYPE_TRACE | LWESP_DBG_LVL_WARNING, listen_api == NULL,
                           "[NETCONN] Closing connection as there is no listening API in netconn!\r\n");
                close = 1;                      /* Close the connection at this point */
            }

            /* Decide if some events want to close the connection */
            if (close) {
                if (nc != NULL) {
                    lwesp_conn_set_arg(conn, NULL); /* Reset argument */
                    lwesp_netconn_delete(nc);   /* Free memory for API */
                }
                lwesp_conn_close(conn, 0);      /* Close the connection */
                close = 0;
            }
            break;
        }

        /*
         * We have a new data received which
         * should have netconn structure as argument
         */
        case LWESP_EVT_CONN_RECV: {
            lwesp_pbuf_p pbuf;

            nc = lwesp_conn_get_arg(conn);      /* Get API from connection */
            pbuf = lwesp_evt_conn_recv_get_buff(evt);   /* Get received buff */

#if !LWESP_CFG_CONN_MANUAL_TCP_RECEIVE
            lwesp_conn_recved(conn, pbuf);      /* Notify stack about received data */
#endif /* !LWESP_CFG_CONN_MANUAL_TCP_RECEIVE */

            lwesp_pbuf_ref(pbuf);               /* Increase reference counter */
            if (nc == NULL || !lwesp_sys_mbox_isvalid(&nc->mbox_receive)
                || !lwesp_sys_mbox_putnow(&nc->mbox_receive, pbuf)) {
                LWESP_DEBUGF(LWESP_CFG_DBG_NETCONN,
                           "[NETCONN] Ignoring more data for receive!\r\n");
                lwesp_pbuf_free(pbuf);          /* Free pbuf */
                return lwespOKIGNOREMORE;       /* Return OK to free the memory and ignore further data */
            }
            ++nc->mbox_receive_entries;         /* Increase number of packets in receive mbox */
#if LWESP_CFG_CONN_MANUAL_TCP_RECEIVE
            /* Check against 1 less to still allow potential close event to be written to queue */
            if (nc->mbox_receive_entries >= (LWESP_CFG_NETCONN_RECEIVE_QUEUE_LEN - 1)) {
                conn->status.f.receive_blocked = 1; /* Block reading more data */
            }
#endif /* LWESP_CFG_CONN_MANUAL_TCP_RECEIVE */

            ++nc->rcv_packets;                  /* Increase number of packets received */
            LWESP_DEBUGF(LWESP_CFG_DBG_NETCONN | LWESP_DBG_TYPE_TRACE,
                       "[NETCONN] Received pbuf contains %d bytes. Handle written to receive mbox\r\n",
                       (int)lwesp_pbuf_length(pbuf, 0));
            break;
        }

        /* Connection was just closed */
        case LWESP_EVT_CONN_CLOSE: {
            nc = lwesp_conn_get_arg(conn);      /* Get API from connection */

            /*
             * In case we have a netconn available,
             * simply write pointer to received variable to indicate closed state
             */
            if (nc != NULL && lwesp_sys_mbox_isvalid(&nc->mbox_receive)) {
                if (lwesp_sys_mbox_putnow(&nc->mbox_receive, (void*)&recv_closed)) {
                    ++nc->mbox_receive_entries;
                }
            }

            break;
        }
        default:
            return lwespERR;
    }
    return lwespOK;
}

/**
 * \brief           Global event callback function
 * \param[in]       evt: Callback information and data
 * \return          \ref lwespOK on success, member of \ref lwespr_t otherwise
 */
static lwespr_t
lwesp_evt(lwesp_evt_t* evt) {
    switch (lwesp_evt_get_type(evt)) {
        case LWESP_EVT_WIFI_DISCONNECTED: {     /* Wifi disconnected event */
            if (listen_api != NULL) {           /* Check if listen API active */
                lwesp_sys_mbox_putnow(&listen_api->mbox_accept, &recv_closed);
            }
            break;
        }
        case LWESP_EVT_DEVICE_PRESENT: {        /* Device present event */
            if (listen_api != NULL && !lwesp_device_is_present()) { /* Check if device present */
                lwesp_sys_mbox_putnow(&listen_api->mbox_accept, &recv_not_present);
            }
        }
        default:
            break;
    }
    return lwespOK;
}

/**
 * \brief           Create new netconn connection
 * \param[in]       type: Netconn connection type
 * \return          New netconn connection on success, `NULL` otherwise
 */
lwesp_netconn_p
lwesp_netconn_new(lwesp_netconn_type_t type) {
    lwesp_netconn_t* a;
    static uint8_t first = 1;

    /* Register only once! */
    lwesp_core_lock();
    if (first) {
        first = 0;
        lwesp_evt_register(lwesp_evt);          /* Register global event function */
    }
    lwesp_core_unlock();
    a = lwesp_mem_calloc(1, sizeof(*a));        /* Allocate memory for core object */
    if (a != NULL) {
        a->type = type;                         /* Save netconn type */
        a->conn_timeout = 0;                    /* Default connection timeout */
        if (!lwesp_sys_mbox_create(&a->mbox_accept, LWESP_CFG_NETCONN_ACCEPT_QUEUE_LEN)) {  /* Allocate memory for accepting message box */
            LWESP_DEBUGF(LWESP_CFG_DBG_NETCONN | LWESP_DBG_TYPE_TRACE | LWESP_DBG_LVL_DANGER,
                       "[NETCONN] Cannot create accept MBOX\r\n");
            goto free_ret;
        }
        if (!lwesp_sys_mbox_create(&a->mbox_receive, LWESP_CFG_NETCONN_RECEIVE_QUEUE_LEN)) {/* Allocate memory for receiving message box */
            LWESP_DEBUGF(LWESP_CFG_DBG_NETCONN | LWESP_DBG_TYPE_TRACE | LWESP_DBG_LVL_DANGER,
                       "[NETCONN] Cannot create receive MBOX\r\n");
            goto free_ret;
        }
        lwesp_core_lock();
        if (netconn_list == NULL) {             /* Add new netconn to the existing list */
            netconn_list = a;
        } else {
            a->next = netconn_list;             /* Add it to beginning of the list */
            netconn_list = a;
        }
        lwesp_core_unlock();
    }
    return a;
free_ret:
    if (lwesp_sys_mbox_isvalid(&a->mbox_accept)) {
        lwesp_sys_mbox_delete(&a->mbox_accept);
        lwesp_sys_mbox_invalid(&a->mbox_accept);
    }
    if (lwesp_sys_mbox_isvalid(&a->mbox_receive)) {
        lwesp_sys_mbox_delete(&a->mbox_receive);
        lwesp_sys_mbox_invalid(&a->mbox_receive);
    }
    if (a != NULL) {
        lwesp_mem_free_s((void**)&a);
    }
    return NULL;
}

/**
 * \brief           Delete netconn connection
 * \param[in]       nc: Netconn handle
 * \return          \ref lwespOK on success, member of \ref lwespr_t enumeration otherwise
 */
lwespr_t
lwesp_netconn_delete(lwesp_netconn_p nc) {
    LWESP_ASSERT("netconn != NULL", nc != NULL);

    lwesp_core_lock();
    flush_mboxes(nc, 0);                        /* Clear mboxes */

    /* Stop listening on netconn */
    if (nc == listen_api) {
        listen_api = NULL;
        lwesp_core_unlock();
        lwesp_set_server(0, nc->listen_port, 0, 0, NULL, NULL, NULL, 1);
        lwesp_core_lock();
    }

    /* Remove netconn from linkedlist */
    if (nc == netconn_list) {
        netconn_list = netconn_list->next;      /* Remove first from linked list */
    } else if (netconn_list != NULL) {
        lwesp_netconn_p tmp, prev;
        /* Find element on the list */
        for (prev = netconn_list, tmp = netconn_list->next;
             tmp != NULL; prev = tmp, tmp = tmp->next) {
            if (nc == tmp) {
                prev->next = tmp->next;         /* Remove tmp from linked list */
                break;
            }
        }
    }
    lwesp_core_unlock();

    lwesp_mem_free_s((void**)&nc);
    return lwespOK;
}

/**
 * \brief           Connect to server as client
 * \param[in]       nc: Netconn handle
 * \param[in]       host: Pointer to host, such as domain name or IP address in string format
 * \param[in]       port: Target port to use
 * \return          \ref lwespOK if successfully connected, member of \ref lwespr_t otherwise
 */
lwespr_t
lwesp_netconn_connect(lwesp_netconn_p nc, const char* host, lwesp_port_t port) {
    lwespr_t res;

    LWESP_ASSERT("nc != NULL", nc != NULL);
    LWESP_ASSERT("host != NULL", host != NULL);
    LWESP_ASSERT("port > 0", port > 0);

    /*
     * Start a new connection as client and:
     *
     *  - Set current netconn structure as argument
     *  - Set netconn callback function for connection management
     *  - Start connection in blocking mode
     */
    res = lwesp_conn_start(NULL, (lwesp_conn_type_t)nc->type, host, port, nc, netconn_evt, 1);
    return res;
}

/**
 * \brief           Connect to server as client, allow keep-alive option
 * \param[in]       nc: Netconn handle
 * \param[in]       host: Pointer to host, such as domain name or IP address in string format
 * \param[in]       port: Target port to use
 * \param[in]       keep_alive: Keep alive period seconds
 * \param[in]       local_ip: Local ip in connected command
 * \param[in]       local_port: Local port address
 * \param[in]       mode: UDP mode
 * \return          \ref lwespOK if successfully connected, member of \ref lwespr_t otherwise
 */
lwespr_t
lwesp_netconn_connect_ex(lwesp_netconn_p nc, const char* host, lwesp_port_t port, uint16_t keep_alive, const char* local_ip, lwesp_port_t local_port, uint8_t mode) {
    lwesp_conn_start_t cs = {0};
    lwespr_t res;

    LWESP_ASSERT("nc != NULL", nc != NULL);
    LWESP_ASSERT("host != NULL", host != NULL);
    LWESP_ASSERT("port > 0", port > 0);

    /*
     * Start a new connection as client and:
     *
     *  - Set current netconn structure as argument
     *  - Set netconn callback function for connection management
     *  - Start connection in blocking mode
     */
    cs.type = nc->type;
    cs.remote_host = host;
    cs.remote_port = port;
    cs.local_ip = local_ip;
    if (nc->type == LWESP_NETCONN_TYPE_TCP || nc->type == LWESP_NETCONN_TYPE_SSL) {
        cs.ext.tcp_ssl.keep_alive = keep_alive;
    } else {
        cs.ext.udp.local_port = local_port;
        cs.ext.udp.mode = mode;
    }
    res = lwesp_conn_startex(NULL, &cs, nc, netconn_evt, 1);
    return res;
}

/**
 * \brief           Bind a connection to specific port, can be only used for server connections
 * \param[in]       nc: Netconn handle
 * \param[in]       port: Port used to bind a connection to
 * \return          \ref lwespOK on success, member of \ref lwespr_t enumeration otherwise
 */
lwespr_t
lwesp_netconn_bind(lwesp_netconn_p nc, lwesp_port_t port) {
    lwespr_t res = lwespOK;

    LWESP_ASSERT("nc != NULL", nc != NULL);

    /*
     * Protection is not needed as it is expected
     * that this function is called only from single
     * thread for single netconn connection,
     * thus it is considered reentrant
     */

    nc->listen_port = port;

    return res;
}

/**
 * \brief           Set timeout value in units of seconds when connection is in listening mode
 *                  If new connection is accepted, it will be automatically closed after `seconds` elapsed
 *                  without any data exchange.
 * \note            Call this function before you put connection to listen mode with \ref lwesp_netconn_listen
 * \param[in]       nc: Netconn handle used for listen mode
 * \param[in]       timeout: Time in units of seconds. Set to `0` to disable timeout feature
 * \return          \ref lwespOK on success, member of \ref lwespr_t otherwise
 */
lwespr_t
lwesp_netconn_set_listen_conn_timeout(lwesp_netconn_p nc, uint16_t timeout) {
    lwespr_t res = lwespOK;
    LWESP_ASSERT("nc != NULL", nc != NULL);

    /*
     * Protection is not needed as it is expected
     * that this function is called only from single
     * thread for single netconn connection,
     * thus it is reentrant in this case
     */

    nc->conn_timeout = timeout;

    return res;
}

/**
 * \brief           Listen on previously binded connection
 * \param[in]       nc: Netconn handle used to listen for new connections
 * \return          \ref lwespOK on success, member of \ref lwespr_t enumeration otherwise
 */
lwespr_t
lwesp_netconn_listen(lwesp_netconn_p nc) {
    return lwesp_netconn_listen_with_max_conn(nc, LWESP_CFG_MAX_CONNS);
}

/**
 * \brief           Listen on previously binded connection with max allowed connections at a time
 * \param[in]       nc: Netconn handle used to listen for new connections
 * \param[in]       max_connections: Maximal number of connections server can accept at a time
 *                      This parameter may not be larger than \ref LWESP_CFG_MAX_CONNS
 * \return          \ref lwespOK on success, member of \ref lwespr_t otherwise
 */
lwespr_t
lwesp_netconn_listen_with_max_conn(lwesp_netconn_p nc, uint16_t max_connections) {
    lwespr_t res;

    LWESP_ASSERT("nc != NULL", nc != NULL);
    LWESP_ASSERT("nc->type must be TCP", nc->type == LWESP_NETCONN_TYPE_TCP);

    /* Enable server on port and set default netconn callback */
    if ((res = lwesp_set_server(1, nc->listen_port,
                              LWESP_U16(LWESP_MIN(max_connections, LWESP_CFG_MAX_CONNS)),
                              nc->conn_timeout, netconn_evt, NULL, NULL, 1)) == lwespOK) {
        lwesp_core_lock();
        listen_api = nc;                        /* Set current main API in listening state */
        lwesp_core_unlock();
    }
    return res;
}

/**
 * \brief           Accept a new connection
 * \param[in]       nc: Netconn handle used as base connection to accept new clients
 * \param[out]      client: Pointer to netconn handle to save new connection to
 * \return          \ref lwespOK on success, member of \ref lwespr_t enumeration otherwise
 */
lwespr_t
lwesp_netconn_accept(lwesp_netconn_p nc, lwesp_netconn_p* client) {
    lwesp_netconn_t* tmp;
    uint32_t time;

    LWESP_ASSERT("nc != NULL", nc != NULL);
    LWESP_ASSERT("client != NULL", client != NULL);
    LWESP_ASSERT("nc->type must be TCP", nc->type == LWESP_NETCONN_TYPE_TCP);
    LWESP_ASSERT("nc == listen_api", nc == listen_api);

    *client = NULL;
    time = lwesp_sys_mbox_get(&nc->mbox_accept, (void**)&tmp, 0);
    if (time == LWESP_SYS_TIMEOUT) {
        return lwespTIMEOUT;
    }
    if ((uint8_t*)tmp == (uint8_t*)&recv_closed) {
        lwesp_core_lock();
        listen_api = NULL;                      /* Disable listening at this point */
        lwesp_core_unlock();
        return lwespERRWIFINOTCONNECTED;        /* Wifi disconnected */
    } else if ((uint8_t*)tmp == (uint8_t*)&recv_not_present) {
        lwesp_core_lock();
        listen_api = NULL;                      /* Disable listening at this point */
        lwesp_core_unlock();
        return lwespERRNODEVICE;                /* Device not present */
    }
    *client = tmp;                              /* Set new pointer */
    return lwespOK;                             /* We have a new connection */
}

/**
 * \brief           Write data to connection output buffers
 * \note            This function may only be used on TCP or SSL connections
 * \param[in]       nc: Netconn handle used to write data to
 * \param[in]       data: Pointer to data to write
 * \param[in]       btw: Number of bytes to write
 * \return          \ref lwespOK on success, member of \ref lwespr_t enumeration otherwise
 */
lwespr_t
lwesp_netconn_write(lwesp_netconn_p nc, const void* data, size_t btw) {
    size_t len, sent;
    const uint8_t* d = data;
    lwespr_t res;

    LWESP_ASSERT("nc != NULL", nc != NULL);
    LWESP_ASSERT("nc->type must be TCP or SSL", nc->type == LWESP_NETCONN_TYPE_TCP || nc->type == LWESP_NETCONN_TYPE_SSL);
    LWESP_ASSERT("nc->conn must be active", lwesp_conn_is_active(nc->conn));

    /*
     * Several steps are done in write process
     *
     * 1. Check if buffer is set and check if there is something to write to it.
     *    1. In case buffer will be full after copy, send it and free memory.
     * 2. Check how many bytes we can write directly without need to copy
     * 3. Try to allocate a new buffer and copy remaining input data to it
     * 4. In case buffer allocation fails, send data directly (may have impact on speed and effectivenes)
     */

    /* Step 1 */
    if (nc->buff.buff != NULL) {                /* Is there a write buffer ready to accept more data? */
        len = LWESP_MIN(nc->buff.len - nc->buff.ptr, btw);  /* Get number of bytes we can write to buffer */
        if (len > 0) {
            LWESP_MEMCPY(&nc->buff.buff[nc->buff.ptr], data, len);  /* Copy memory to temporary write buffer */
            d += len;
            nc->buff.ptr += len;
            btw -= len;
        }

        /* Step 1.1 */
        if (nc->buff.ptr == nc->buff.len) {
            res = lwesp_conn_send(nc->conn, nc->buff.buff, nc->buff.len, &sent, 1);

            lwesp_mem_free_s((void**)&nc->buff.buff);
            if (res != lwespOK) {
                return res;
            }
        } else {
            return lwespOK;                     /* Buffer is not full yet */
        }
    }

    /* Step 2 */
    if (btw >= LWESP_CFG_CONN_MAX_DATA_LEN) {
        size_t rem;
        rem = btw % LWESP_CFG_CONN_MAX_DATA_LEN;/* Get remaining bytes for max data length */
        res = lwesp_conn_send(nc->conn, d, btw - rem, &sent, 1);/* Write data directly */
        if (res != lwespOK) {
            return res;
        }
        d += sent;                              /* Advance in data pointer */
        btw -= sent;                            /* Decrease remaining data to send */
    }

    if (btw == 0) {                             /* Sent everything? */
        return lwespOK;
    }

    /* Step 3 */
    if (nc->buff.buff == NULL) {                /* Check if we should allocate a new buffer */
        nc->buff.buff = lwesp_mem_malloc(sizeof(*nc->buff.buff) * LWESP_CFG_CONN_MAX_DATA_LEN);
        nc->buff.len = LWESP_CFG_CONN_MAX_DATA_LEN; /* Save buffer length */
        nc->buff.ptr = 0;                       /* Save buffer pointer */
    }

    /* Step 4 */
    if (nc->buff.buff != NULL) {                /* Memory available? */
        LWESP_MEMCPY(&nc->buff.buff[nc->buff.ptr], d, btw); /* Copy data to buffer */
        nc->buff.ptr += btw;
    } else {                                    /* Still no memory available? */
        return lwesp_conn_send(nc->conn, data, btw, NULL, 1);   /* Simply send directly blocking */
    }
    return lwespOK;
}

/**
 * \brief           Flush buffered data on netconn TCP/SSL connection
 * \note            This function may only be used on TCP/SSL connection
 * \param[in]       nc: Netconn handle to flush data
 * \return          \ref lwespOK on success, member of \ref lwespr_t enumeration otherwise
 */
lwespr_t
lwesp_netconn_flush(lwesp_netconn_p nc) {
    LWESP_ASSERT("nc != NULL", nc != NULL);
    LWESP_ASSERT("nc->type must be TCP or SSL", nc->type == LWESP_NETCONN_TYPE_TCP || nc->type == LWESP_NETCONN_TYPE_SSL);
    LWESP_ASSERT("nc->conn must be active", lwesp_conn_is_active(nc->conn));

    /*
     * In case we have data in write buffer,
     * flush them out to network
     */
    if (nc->buff.buff != NULL) {                /* Check remaining data */
        if (nc->buff.ptr > 0) {                 /* Do we have data in current buffer? */
            lwesp_conn_send(nc->conn, nc->buff.buff, nc->buff.ptr, NULL, 1);/* Send data */
        }
        lwesp_mem_free_s((void**)&nc->buff.buff);
    }
    return lwespOK;
}

/**
 * \brief           Send data on UDP connection to default IP and port
 * \param[in]       nc: Netconn handle used to send
 * \param[in]       data: Pointer to data to write
 * \param[in]       btw: Number of bytes to write
 * \return          \ref lwespOK on success, member of \ref lwespr_t enumeration otherwise
 */
lwespr_t
lwesp_netconn_send(lwesp_netconn_p nc, const void* data, size_t btw) {
    LWESP_ASSERT("nc != NULL", nc != NULL);
    LWESP_ASSERT("nc->type must be UDP", nc->type == LWESP_NETCONN_TYPE_UDP);
    LWESP_ASSERT("nc->conn must be active", lwesp_conn_is_active(nc->conn));

    return lwesp_conn_send(nc->conn, data, btw, NULL, 1);
}

/**
 * \brief           Send data on UDP connection to specific IP and port
 * \note            Use this function in case of UDP type netconn
 * \param[in]       nc: Netconn handle used to send
 * \param[in]       ip: Pointer to IP address
 * \param[in]       port: Port number used to send data
 * \param[in]       data: Pointer to data to write
 * \param[in]       btw: Number of bytes to write
 * \return          \ref lwespOK on success, member of \ref lwespr_t enumeration otherwise
 */
lwespr_t
lwesp_netconn_sendto(lwesp_netconn_p nc, const lwesp_ip_t* ip, lwesp_port_t port, const void* data, size_t btw) {
    LWESP_ASSERT("nc != NULL", nc != NULL);
    LWESP_ASSERT("nc->type must be UDP", nc->type == LWESP_NETCONN_TYPE_UDP);
    LWESP_ASSERT("nc->conn must be active", lwesp_conn_is_active(nc->conn));

    return lwesp_conn_sendto(nc->conn, ip, port, data, btw, NULL, 1);
}

/**
 * \brief           Receive data from connection
 * \param[in]       nc: Netconn handle used to receive from
 * \param[in]       pbuf: Pointer to pointer to save new receive buffer to.
 *                     When function returns, user must check for valid pbuf value `pbuf != NULL`
 * \return          \ref lwespOK when new data ready
 * \return          \ref lwespCLOSED when connection closed by remote side
 * \return          \ref lwespTIMEOUT when receive timeout occurs
 * \return          Any other member of \ref lwespr_t otherwise
 */
lwespr_t
lwesp_netconn_receive(lwesp_netconn_p nc, lwesp_pbuf_p* pbuf) {
    LWESP_ASSERT("nc != NULL", nc != NULL);
    LWESP_ASSERT("pbuf != NULL", pbuf != NULL);

    *pbuf = NULL;
#if LWESP_CFG_NETCONN_RECEIVE_TIMEOUT
    /*
     * Wait for new received data for up to specific timeout
     * or throw error for timeout notification
     */
    if (nc->rcv_timeout == LWESP_NETCONN_RECEIVE_NO_WAIT) {
        if (!lwesp_sys_mbox_getnow(&nc->mbox_receive, (void**)pbuf)) {
            return lwespTIMEOUT;
        }
    } else if (lwesp_sys_mbox_get(&nc->mbox_receive, (void**)pbuf, nc->rcv_timeout) == LWESP_SYS_TIMEOUT) {
        return lwespTIMEOUT;
    }
#else /* LWESP_CFG_NETCONN_RECEIVE_TIMEOUT */
    /* Forever wait for new receive packet */
    lwesp_sys_mbox_get(&nc->mbox_receive, (void**)pbuf, 0);
#endif /* !LWESP_CFG_NETCONN_RECEIVE_TIMEOUT */

    lwesp_core_lock();
    if (nc->mbox_receive_entries > 0) {
        --nc->mbox_receive_entries;
    }
    lwesp_core_unlock();

    /* Check if connection closed */
    if ((uint8_t*)(*pbuf) == (uint8_t*)&recv_closed) {
        *pbuf = NULL;                           /* Reset pbuf */
        return lwespCLOSED;
    }
#if LWESP_CFG_CONN_MANUAL_TCP_RECEIVE
    else {
        lwesp_core_lock();
        nc->conn->status.f.receive_blocked = 0; /* Resume reading more data */
        lwesp_conn_recved(nc->conn, *pbuf);     /* Notify stack about received data */
        lwesp_core_unlock();
    }
#endif /* LWESP_CFG_CONN_MANUAL_TCP_RECEIVE */
    return lwespOK;                             /* We have data available */
}

/**
 * \brief           Close a netconn connection
 * \param[in]       nc: Netconn handle to close
 * \return          \ref lwespOK on success, member of \ref lwespr_t enumeration otherwise
 */
lwespr_t
lwesp_netconn_close(lwesp_netconn_p nc) {
    lwesp_conn_p conn;

    LWESP_ASSERT("nc != NULL", nc != NULL);
    LWESP_ASSERT("nc->conn != NULL", nc->conn != NULL);
    LWESP_ASSERT("nc->conn must be active", lwesp_conn_is_active(nc->conn));

    lwesp_netconn_flush(nc);                    /* Flush data and ignore result */
    conn = nc->conn;
    nc->conn = NULL;

    lwesp_conn_set_arg(conn, NULL);             /* Reset argument */
    lwesp_conn_close(conn, 1);                  /* Close the connection */
    flush_mboxes(nc, 1);                        /* Flush message queues */
    return lwespOK;
}

/**
 * \brief           Get connection number used for netconn
 * \param[in]       nc: Netconn handle
 * \return          `-1` on failure, connection number between `0` and \ref LWESP_CFG_MAX_CONNS otherwise
 */
int8_t
lwesp_netconn_get_connnum(lwesp_netconn_p nc) {
    if (nc != NULL && nc->conn != NULL) {
        return lwesp_conn_getnum(nc->conn);
    }
    return -1;
}

#if LWESP_CFG_NETCONN_RECEIVE_TIMEOUT || __DOXYGEN__

/**
 * \brief           Set timeout value for receiving data.
 *
 * When enabled, \ref lwesp_netconn_receive will only block for up to
 * `timeout` value and will return if no new data within this time
 *
 * \param[in]       nc: Netconn handle
 * \param[in]       timeout: Timeout in units of milliseconds.
 *                      Set to `0` to disable timeout feature
 *                      Set to `> 0` to set maximum milliseconds to wait before timeout
 *                      Set to \ref LWESP_NETCONN_RECEIVE_NO_WAIT to enable non-blocking receive
 */
void
lwesp_netconn_set_receive_timeout(lwesp_netconn_p nc, uint32_t timeout) {
    nc->rcv_timeout = timeout;
}

/**
 * \brief           Get netconn receive timeout value
 * \param[in]       nc: Netconn handle
 * \return          Timeout in units of milliseconds.
 *                  If value is `0`, timeout is disabled (wait forever)
 */
uint32_t
lwesp_netconn_get_receive_timeout(lwesp_netconn_p nc) {
    return nc->rcv_timeout;
}

#endif /* LWESP_CFG_NETCONN_RECEIVE_TIMEOUT || __DOXYGEN__ */

/**
 * \brief           Get netconn connection handle
 * \param[in]       nc: Netconn handle
 * \return          ESP connection handle
 */
lwesp_conn_p
lwesp_netconn_get_conn(lwesp_netconn_p nc) {
    return nc->conn;
}

#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
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
#include "client.h"
#include "lwesp/lwesp.h"

/* Host parameter */
#define CONN_HOST           "example.com"
#define CONN_PORT           80

static lwespr_t   conn_callback_func(lwesp_evt_t* evt);

/**
 * \brief           Request data for connection
 */
static const
uint8_t req_data[] = ""
                     "GET / HTTP/1.1\r\n"
                     "Host: " CONN_HOST "\r\n"
                     "Connection: close\r\n"
                     "\r\n";

/**
 * \brief           Start a new connection(s) as client
 */
void
client_connect(void) {
    lwespr_t res;

    /* Start a new connection as client in non-blocking mode */
    if ((res = lwesp_conn_start(NULL, LWESP_CONN_TYPE_TCP, "example.com", 80, NULL, conn_callback_func, 0)) == lwespOK) {
        printf("Connection to " CONN_HOST " started...\r\n");
    } else {
        printf("Cannot start connection to " CONN_HOST "!\r\n");
    }

    /* Start 2 more */
    lwesp_conn_start(NULL, LWESP_CONN_TYPE_TCP, CONN_HOST, CONN_PORT, NULL, conn_callback_func, 0);

    /*
     * An example of connection which should fail in connecting.
     * When this is the case, \ref LWESP_EVT_CONN_ERROR event should be triggered
     * in callback function processing
     */
    lwesp_conn_start(NULL, LWESP_CONN_TYPE_TCP, CONN_HOST, 10, NULL, conn_callback_func, 0);
}

/**
 * \brief           Event callback function for connection-only
 * \param[in]       evt: Event information with data
 * \return          \ref lwespOK on success, member of \ref lwespr_t otherwise
 */
static lwespr_t
conn_callback_func(lwesp_evt_t* evt) {
    lwesp_conn_p conn;
    lwespr_t res;
    uint8_t conn_num;

    conn = lwesp_conn_get_from_evt(evt);          /* Get connection handle from event */
    if (conn == NULL) {
        return lwespERR;
    }
    conn_num = lwesp_conn_getnum(conn);           /* Get connection number for identification */
    switch (lwesp_evt_get_type(evt)) {
        case LWESP_EVT_CONN_ACTIVE: {             /* Connection just active */
            printf("Connection %d active!\r\n", (int)conn_num);
            res = lwesp_conn_send(conn, req_data, sizeof(req_data) - 1, NULL, 0); /* Start sending data in non-blocking mode */
            if (res == lwespOK) {
                printf("Sending request data to server...\r\n");
            } else {
                printf("Cannot send request data to server. Closing connection manually...\r\n");
                lwesp_conn_close(conn, 0);        /* Close the connection */
            }
            break;
        }
        case LWESP_EVT_CONN_CLOSE: {              /* Connection closed */
            if (lwesp_evt_conn_close_is_forced(evt)) {
                printf("Connection %d closed by client!\r\n", (int)conn_num);
            } else {
                printf("Connection %d closed by remote side!\r\n", (int)conn_num);
            }
            break;
        }
        case LWESP_EVT_CONN_SEND: {               /* Data send event */
            lwespr_t res = lwesp_evt_conn_send_get_result(evt);
            if (res == lwespOK) {
                printf("Data sent successfully on connection %d...waiting to receive data from remote side...\r\n", (int)conn_num);
            } else {
                printf("Error while sending data on connection %d!\r\n", (int)conn_num);
            }
            break;
        }
        case LWESP_EVT_CONN_RECV: {               /* Data received from remote side */
            lwesp_pbuf_p pbuf = lwesp_evt_conn_recv_get_buff(evt);
            lwesp_conn_recved(conn, pbuf);        /* Notify stack about received pbuf */
            printf("Received %d bytes on connection %d..\r\n", (int)lwesp_pbuf_length(pbuf, 1), (int)conn_num);
            break;
        }
        case LWESP_EVT_CONN_ERROR: {              /* Error connecting to server */
            const char* host = lwesp_evt_conn_error_get_host(evt);
            lwesp_port_t port = lwesp_evt_conn_error_get_port(evt);
            printf("Error connecting to %s:%d\r\n", host, (int)port);
            break;
        }
        default:
            break;
    }
    return lwespOK;
}

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
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
#include "dns.h"
#include "lwesp/lwesp.h"

/* Host to resolve */
#define DNS_HOST1           "example.com"
#define DNS_HOST2           "example.net"

/**
 * \brief           Variable to hold result of DNS resolver
 */
static lwesp_ip_t ip;

/**
 * \brief           Event callback function for API call,
 *                  called when API command finished with execution
 */
static void
dns_resolve_evt(lwespr_t res, void* arg) {
    /* Check result of command */
    if (res == lwespOK) {
        /* DNS resolver has IP address */
        printf("DNS record for %s (from API callback): %d.%d.%d.%d\r\n",
               (const char*)arg, (int)ip.ip[0], (int)ip.ip[1], (int)ip.ip[2], (int)ip.ip[3]);
    }
}

/**
 * \brief           Start DNS resolver
 */
void
dns_start(void) {
    /* Use DNS protocol to get IP address of domain name */

    /* Get IP with non-blocking mode */
    if (lwesp_dns_gethostbyname(DNS_HOST2, &ip, dns_resolve_evt, DNS_HOST2, 0) == lwespOK) {
        printf("Request for DNS record for " DNS_HOST2 " has started\r\n");
    } else {
        printf("Could not start command for DNS\r\n");
    }

    /* Get IP with blocking mode */
    if (lwesp_dns_gethostbyname(DNS_HOST1, &ip, dns_resolve_evt, DNS_HOST1, 1) == lwespOK) {
        printf("DNS record for " DNS_HOST1 " (from lin code): %d.%d.%d.%d\r\n",
               (int)ip.ip[0], (int)ip.ip[1], (int)ip.ip[2], (int)ip.ip[3]);
    } else {
        printf("Could not retrieve IP address for " DNS_HOST1 "\r\n");
    }
}