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:

  • GSM station successfully connected to access point

  • GSM 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 lwgsm_evt_register() function to register a new, custom, event function.

Tip

Implementation of Netconn API leverages lwgsm_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
/**
 * \file            lwgsm_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 LwGSM - Lightweight GSM-AT library.
 *
 * Author:          Tilen MAJERLE <tilen@majerle.eu>
 * Version:         v0.1.0
 */
#include "lwgsm/lwgsm_netconn.h"
#include "lwgsm/lwgsm_private.h"
#include "lwgsm/lwgsm_conn.h"
#include "lwgsm/lwgsm_mem.h"

#if LWGSM_CFG_NETCONN || __DOXYGEN__

/* Check conditions */
#if !LWGSM_CFG_CONN
#error "LWGSM_CFG_CONN must be enabled for NETCONN API!"
#endif /* !LWGSM_CFG_CONN */

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

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

    lwgsm_netconn_type_t type;                  /*!< Netconn type */

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

    lwgsm_sys_mbox_t mbox_receive;              /*!< Message queue for receive mbox */

    lwgsm_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 LWGSM_CFG_NETCONN_RECEIVE_TIMEOUT || __DOXYGEN__
    uint32_t rcv_timeout;                       /*!< Receive timeout in unit of milliseconds */
#endif
} lwgsm_netconn_t;

static uint8_t recv_closed = 0xFF;
static lwgsm_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(lwgsm_netconn_t* nc, uint8_t protect) {
    lwgsm_pbuf_p pbuf;
    if (protect) {
        lwgsm_core_lock();
    }
    if (lwgsm_sys_mbox_isvalid(&nc->mbox_receive)) {
        while (lwgsm_sys_mbox_getnow(&nc->mbox_receive, (void**)&pbuf)) {
            if (pbuf != NULL && (uint8_t*)pbuf != (uint8_t*)&recv_closed) {
                lwgsm_pbuf_free(pbuf);          /* Free received data buffers */
            }
        }
        lwgsm_sys_mbox_delete(&nc->mbox_receive);   /* Delete message queue */
        lwgsm_sys_mbox_invalid(&nc->mbox_receive);  /* Invalid handle */
    }
    if (protect) {
        lwgsm_core_unlock();
    }
}

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

    conn = lwgsm_conn_get_from_evt(evt);        /* Get connection from event */
    switch (lwgsm_evt_get_type(evt)) {
        /*
         * A new connection has been active
         * and should be handled by netconn API
         */
        case LWGSM_EVT_CONN_ACTIVE: {           /* A new connection active is active */
            if (lwgsm_conn_is_client(conn)) {   /* Was connection started by us? */
                nc = lwgsm_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 */
                }
            } else {
                LWGSM_DEBUGF(LWGSM_CFG_DBG_NETCONN | LWGSM_DBG_TYPE_TRACE | LWGSM_DBG_LVL_WARNING,
                           "[NETCONN] Closing connection, it is not in client mode!\r\n");
                close = 1;                      /* Close the connection at this point */
            }

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

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

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

            lwgsm_conn_recved(conn, pbuf);      /* Notify stack about received data */

            lwgsm_pbuf_ref(pbuf);               /* Increase reference counter */
            if (nc == NULL || !lwgsm_sys_mbox_isvalid(&nc->mbox_receive)
                || !lwgsm_sys_mbox_putnow(&nc->mbox_receive, pbuf)) {
                LWGSM_DEBUGF(LWGSM_CFG_DBG_NETCONN,
                           "[NETCONN] Ignoring more data for receive!\r\n");
                lwgsm_pbuf_free(pbuf);          /* Free pbuf */
                return lwgsmOKIGNOREMORE;       /* Return OK to free the memory and ignore further data */
            }
            ++nc->rcv_packets;                  /* Increase number of received packets */
            LWGSM_DEBUGF(LWGSM_CFG_DBG_NETCONN | LWGSM_DBG_TYPE_TRACE,
                       "[NETCONN] Received pbuf contains %d bytes. Handle written to receive mbox\r\n",
                       (int)lwgsm_pbuf_length(pbuf, 0));
            break;
        }

        /* Connection was just closed */
        case LWGSM_EVT_CONN_CLOSE: {
            nc = lwgsm_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 && lwgsm_sys_mbox_isvalid(&nc->mbox_receive)) {
                lwgsm_sys_mbox_putnow(&nc->mbox_receive, (void*)&recv_closed);
            }

            break;
        }
        default:
            return lwgsmERR;
    }
    return lwgsmOK;
}

/**
 * \brief           Global event callback function
 * \param[in]       evt: Callback information and data
 * \return          \ref lwgsmOK on success, member of \ref lwgsmr_t otherwise
 */
static lwgsmr_t
lwgsm_evt(lwgsm_evt_t* evt) {
    switch (lwgsm_evt_get_type(evt)) {
        default:
            break;
    }
    return lwgsmOK;
}

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

    /* Register only once! */
    lwgsm_core_lock();
    if (first) {
        first = 0;
        lwgsm_evt_register(lwgsm_evt);          /* Register global event function */
    }
    lwgsm_core_unlock();
    a = lwgsm_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 (!lwgsm_sys_mbox_create(&a->mbox_receive, LWGSM_CFG_NETCONN_RECEIVE_QUEUE_LEN)) {/* Allocate memory for receiving message box */
            LWGSM_DEBUGF(LWGSM_CFG_DBG_NETCONN | LWGSM_DBG_TYPE_TRACE | LWGSM_DBG_LVL_DANGER,
                       "[NETCONN] Cannot create receive MBOX\r\n");
            goto free_ret;
        }
        lwgsm_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;
        }
        lwgsm_core_unlock();
    }
    return a;
free_ret:
    if (lwgsm_sys_mbox_isvalid(&a->mbox_receive)) {
        lwgsm_sys_mbox_delete(&a->mbox_receive);
        lwgsm_sys_mbox_invalid(&a->mbox_receive);
    }
    if (a != NULL) {
        lwgsm_mem_free_s((void**)&a);
    }
    return NULL;
}

/**
 * \brief           Delete netconn connection
 * \param[in]       nc: Netconn handle
 * \return          \ref lwgsmOK on success, member of \ref lwgsmr_t enumeration otherwise
 */
lwgsmr_t
lwgsm_netconn_delete(lwgsm_netconn_p nc) {
    LWGSM_ASSERT("netconn != NULL", nc != NULL);

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

    /* Remove netconn from linkedlist */
    if (netconn_list == nc) {
        netconn_list = netconn_list->next;      /* Remove first from linked list */
    } else if (netconn_list != NULL) {
        lwgsm_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;
            }
        }
    }
    lwgsm_core_unlock();

    lwgsm_mem_free_s((void**)&nc);
    return lwgsmOK;
}

/**
 * \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 lwgsmOK if successfully connected, member of \ref lwgsmr_t otherwise
 */
lwgsmr_t
lwgsm_netconn_connect(lwgsm_netconn_p nc, const char* host, lwgsm_port_t port) {
    lwgsmr_t res;

    LWGSM_ASSERT("nc != NULL", nc != NULL);
    LWGSM_ASSERT("host != NULL", host != NULL);
    LWGSM_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 = lwgsm_conn_start(NULL, (lwgsm_conn_type_t)nc->type, host, port, nc, netconn_evt, 1);
    return res;
}

/**
 * \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 lwgsmOK on success, member of \ref lwgsmr_t enumeration otherwise
 */
lwgsmr_t
lwgsm_netconn_write(lwgsm_netconn_p nc, const void* data, size_t btw) {
    size_t len, sent;
    const uint8_t* d = data;
    lwgsmr_t res;

    LWGSM_ASSERT("nc != NULL", nc != NULL);
    LWGSM_ASSERT("nc->type must be TCP or SSL", nc->type == LWGSM_NETCONN_TYPE_TCP || nc->type == LWGSM_NETCONN_TYPE_SSL);
    LWGSM_ASSERT("nc->conn must be active", lwgsm_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 affect on speed and effectivenes)
     */

    /* Step 1 */
    if (nc->buff.buff != NULL) {                /* Is there a write buffer ready to accept more data? */
        len = LWGSM_MIN(nc->buff.len - nc->buff.ptr, btw);  /* Get number of bytes we can write to buffer */
        if (len > 0) {
            LWGSM_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 = lwgsm_conn_send(nc->conn, nc->buff.buff, nc->buff.len, &sent, 1);

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

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

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

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

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

/**
 * \brief           Flush buffered data on netconn \e TCP/SSL connection
 * \note            This function may only be used on \e TCP/SSL connection
 * \param[in]       nc: Netconn handle to flush data
 * \return          \ref lwgsmOK on success, member of \ref lwgsmr_t enumeration otherwise
 */
lwgsmr_t
lwgsm_netconn_flush(lwgsm_netconn_p nc) {
    LWGSM_ASSERT("nc != NULL", nc != NULL);
    LWGSM_ASSERT("nc->type must be TCP or SSL", nc->type == LWGSM_NETCONN_TYPE_TCP || nc->type == LWGSM_NETCONN_TYPE_SSL);
    LWGSM_ASSERT("nc->conn must be active", lwgsm_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? */
            lwgsm_conn_send(nc->conn, nc->buff.buff, nc->buff.ptr, NULL, 1);/* Send data */
        }
        lwgsm_mem_free_s((void**)&nc->buff.buff);
    }
    return lwgsmOK;
}

/**
 * \brief           Send data on \e 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 lwgsmOK on success, member of \ref lwgsmr_t enumeration otherwise
 */
lwgsmr_t
lwgsm_netconn_send(lwgsm_netconn_p nc, const void* data, size_t btw) {
    LWGSM_ASSERT("nc != NULL", nc != NULL);
    LWGSM_ASSERT("nc->type must be UDP", nc->type == LWGSM_NETCONN_TYPE_UDP);
    LWGSM_ASSERT("nc->conn must be active", lwgsm_conn_is_active(nc->conn));

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

/**
 * \brief           Send data on \e 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 lwgsmOK on success, member of \ref lwgsmr_t enumeration otherwise
 */
lwgsmr_t
lwgsm_netconn_sendto(lwgsm_netconn_p nc, const lwgsm_ip_t* ip, lwgsm_port_t port, const void* data, size_t btw) {
    LWGSM_ASSERT("nc != NULL", nc != NULL);
    LWGSM_ASSERT("nc->type must be UDP", nc->type == LWGSM_NETCONN_TYPE_UDP);
    LWGSM_ASSERT("nc->conn must be active", lwgsm_conn_is_active(nc->conn));

    return lwgsm_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 lwgsmOK when new data ready,
 * \return          \ref lwgsmCLOSED when connection closed by remote side,
 * \return          \ref lwgsmTIMEOUT when receive timeout occurs
 * \return          Any other member of \ref lwgsmr_t otherwise
 */
lwgsmr_t
lwgsm_netconn_receive(lwgsm_netconn_p nc, lwgsm_pbuf_p* pbuf) {
    LWGSM_ASSERT("nc != NULL", nc != NULL);
    LWGSM_ASSERT("pbuf != NULL", pbuf != NULL);

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

    /* Check if connection closed */
    if ((uint8_t*)(*pbuf) == (uint8_t*)&recv_closed) {
        *pbuf = NULL;                           /* Reset pbuf */
        return lwgsmCLOSED;
    }
    return lwgsmOK;                             /* We have data available */
}

/**
 * \brief           Close a netconn connection
 * \param[in]       nc: Netconn handle to close
 * \return          \ref lwgsmOK on success, member of \ref lwgsmr_t enumeration otherwise
 */
lwgsmr_t
lwgsm_netconn_close(lwgsm_netconn_p nc) {
    lwgsm_conn_p conn;

    LWGSM_ASSERT("nc != NULL", nc != NULL);
    LWGSM_ASSERT("nc->conn != NULL", nc->conn != NULL);
    LWGSM_ASSERT("nc->conn must be active", lwgsm_conn_is_active(nc->conn));

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

    lwgsm_conn_set_arg(conn, NULL);             /* Reset argument */
    lwgsm_conn_close(conn, 1);                  /* Close the connection */
    flush_mboxes(nc, 1);                        /* Flush message queues */
    return lwgsmOK;
}

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

#if LWGSM_CFG_NETCONN_RECEIVE_TIMEOUT || __DOXYGEN__

/**
 * \brief           Set timeout value for receiving data.
 *
 * When enabled, \ref lwgsm_netconn_receive will only block for up to
 * \e 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 for \ref lwgsm_netconn_receive function
 */
void
lwgsm_netconn_set_receive_timeout(lwgsm_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
lwgsm_netconn_get_receive_timeout(lwgsm_netconn_p nc) {
    return nc->rcv_timeout;                     /* Return receive timeout */
}

#endif /* LWGSM_CFG_NETCONN_RECEIVE_TIMEOUT || __DOXYGEN__ */

#endif /* LWGSM_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 LWGSM_EVT_CONN_*, such as LWGSM_EVT_CONN_RECV. Check Event management for list of all connection events.

Connection events callback function is set when client (application starts connection) starts a new connection with lwgsm_conn_start() 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
#include "client.h"
#include "lwgsm/lwgsm.h"
#include "lwgsm/lwgsm_network_api.h"

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

static lwgsmr_t   conn_callback_func(lwgsm_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) {
    lwgsmr_t res;

    /* Attach to GSM network */
    lwgsm_network_request_attach();

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

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

    conn = lwgsm_conn_get_from_evt(evt);          /* Get connection handle from event */
    if (conn == NULL) {
        return lwgsmERR;
    }
    conn_num = lwgsm_conn_getnum(conn);           /* Get connection number for identification */
    switch (lwgsm_evt_get_type(evt)) {
        case LWGSM_EVT_CONN_ACTIVE: {             /* Connection just active */
            printf("Connection %d active!\r\n", (int)conn_num);
            res = lwgsm_conn_send(conn, req_data, sizeof(req_data) - 1, NULL, 0); /* Start sending data in non-blocking mode */
            if (res == lwgsmOK) {
                printf("Sending request data to server...\r\n");
            } else {
                printf("Cannot send request data to server. Closing connection manually...\r\n");
                lwgsm_conn_close(conn, 0);        /* Close the connection */
            }
            break;
        }
        case LWGSM_EVT_CONN_CLOSE: {              /* Connection closed */
            if (lwgsm_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 LWGSM_EVT_CONN_SEND: {               /* Data send event */
            lwgsmr_t res = lwgsm_evt_conn_send_get_result(evt);
            if (res == lwgsmOK) {
                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 LWGSM_EVT_CONN_RECV: {               /* Data received from remote side */
            lwgsm_pbuf_p pbuf = lwgsm_evt_conn_recv_get_buff(evt);
            lwgsm_conn_recved(conn, pbuf);        /* Notify stack about received pbuf */
            printf("Received %d bytes on connection %d..\r\n", (int)lwgsm_pbuf_length(pbuf, 1), (int)conn_num);
            break;
        }
        case LWGSM_EVT_CONN_ERROR: {              /* Error connecting to server */
            const char* host = lwgsm_evt_conn_error_get_host(evt);
            lwgsm_port_t port = lwgsm_evt_conn_error_get_port(evt);
            printf("Error connecting to %s:%d\r\n", host, (int)port);
            break;
        }
        default:
            break;
    }
    return lwgsmOK;
}

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 SMS send. It uses custom API callback function to notify application when command has been executed successfully

Simple example for API call event, using DNS module
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
/* Somewhere in thread function */

/* Get device hostname in blocking mode */
/* Function returns actual result */
if (lwgsm_sms_send("+0123456789", "text", NULL, NULL, 1 /* 1 means blocking call */) == lwgsmOK) {
    /* At this point we have valid result from device */
    printf("SMS sent successfully\r\n");
} else {
    printf("Error trying to send SMS..\r\n");
}