LwCELL documentation

Welcome to the documentation for version .

LwCELL is lightweight, platform independent, cellular modem AT commands parser, targeting (as of today) communicion with SIMCOM based modules SIM800/SIM900 or SIM70xx. Module is written in C11 and is independent from used platform. Its main targets are embedded system devices like ARM Cortex-M, AVR, PIC and others, but can easily work under Windows, Linux or MAC environments.

_images/logo.svg

Features

  • Supports SIM800/SIM900 (2G) and SIM7000/SIM7020 (NB-Iot LTE) modules

  • Platform independent and very easy to port

    • Development of library under Win32 platform

    • Provided examples for ARM Cortex-M or Win32 platforms

  • Written in C language (C11)

  • Allows different configurations to optimize user requirements

  • Supports implementation with operating systems with advanced inter-thread communications

    • Currently only OS mode is supported

    • 2 different threads handling user data and received data

      • First (producer) thread (collects user commands from user threads and starts the command processing)

      • Second (process) thread reads the data from GSM device and does the job accordingly

  • Allows sequential API for connections in client and server mode

  • Includes several applications built on top of library

    • MQTT client for MQTT connection

  • User friendly MIT license

Requirements

  • C compiler

  • Supported GSM Physical device

Contribute

Fresh contributions are always welcome. Simple instructions to proceed:

  1. Fork Github repository

  2. Respect C style & coding rules used by the library

  3. Create a pull request to develop branch with new features or bug fixes

Alternatively you may:

  1. Report a bug

  2. Ask for a feature request

License

MIT License

Copyright (c) 2023 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.

Table of contents

Getting started

Getting started may be the most challenging part of every new library. This guide is describing how to start with the library quickly and effectively

Download library

Library is primarly hosted on Github.

You can get it by:

  • Downloading latest release from releases area on Github

  • Cloning main branch for latest stable version

  • Cloning develop branch for latest development

Download from releases

All releases are available on Github releases area.

Clone from Github
First-time clone

This is used when you do not have yet local copy on your machine.

  • Make sure git is installed.

  • Open console and navigate to path in the system to clone repository to. Use command cd your_path

  • Clone repository with one of available options below

    • Run git clone --recurse-submodules https://github.com/MaJerle/lwcell command to clone entire repository, including submodules

    • Run git clone --recurse-submodules --branch develop https://github.com/MaJerle/lwcell to clone development branch, including submodules

    • Run git clone --recurse-submodules --branch main https://github.com/MaJerle/lwcell to clone latest stable branch, including submodules

  • Navigate to examples directory and run favourite example

Update cloned to latest version
  • Open console and navigate to path in the system where your repository is located. Use command cd your_path

  • Run git pull origin main command to get latest changes on main branch

  • Run git pull origin develop command to get latest changes on develop branch

  • Run git submodule update --init --remote to update submodules to latest version

Note

This is preferred option to use when you want to evaluate library and run prepared examples. Repository consists of multiple submodules which can be automatically downloaded when cloning and pulling changes from root repository.

Add library to project

At this point it is assumed that you have successfully download library, either cloned it or from releases page. Next step is to add the library to the project, by means of source files to compiler inputs and header files in search path

  • Copy lwcell folder to your project, it contains library files

  • Add lwcell/src/include folder to include path of your toolchain. This is where C/C++ compiler can find the files during compilation process. Usually using -I flag

  • Add source files from lwcell/src/ folder to toolchain build. These files are built by C/C++ compiler. CMake configuration comes with the library, allows users to include library in the project as subdirectory and library.

  • Copy lwcell/src/include/lwcell/lwcell_opts_template.h to project folder and rename it to lwcell_opts.h

  • Build the project

Configuration file

Configuration file is used to overwrite default settings defined for the essential use case. Library comes with template config file, which can be modified according to needs. and it should be copied (or simply renamed in-place) and named lwcell_opts.h

Note

Default configuration template file location: lwcell/src/include/lwcell/lwcell_opts_template.h. File must be renamed to lwcell_opts.h first and then copied to the project directory where compiler include paths have access to it by using #include "lwcell_opts.h".

List of configuration options are available in the Configuration section. If any option is about to be modified, it should be done in configuration file

Template configuration file
 1/**
 2 * \file            lwcell_opts_template.h
 3 * \brief           Template config file
 4 */
 5
 6/*
 7 * Copyright (c) 2023 Tilen MAJERLE
 8 *
 9 * Permission is hereby granted, free of charge, to any person
10 * obtaining a copy of this software and associated documentation
11 * files (the "Software"), to deal in the Software without restriction,
12 * including without limitation the rights to use, copy, modify, merge,
13 * publish, distribute, sublicense, and/or sell copies of the Software,
14 * and to permit persons to whom the Software is furnished to do so,
15 * subject to the following conditions:
16 *
17 * The above copyright notice and this permission notice shall be
18 * included in all copies or substantial portions of the Software.
19 *
20 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
21 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
22 * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE
23 * AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
24 * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
25 * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
26 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
27 * OTHER DEALINGS IN THE SOFTWARE.
28 *
29 * This file is part of LwCELL - Lightweight cellular modem AT library.
30 *
31 * Author:          Tilen MAJERLE <tilen@majerle.eu>
32 * Version:         v0.1.1
33 */
34#ifndef LWCELL_OPTS_HDR_H
35#define LWCELL_OPTS_HDR_H
36
37/* Rename this file to "lwcell_opts.h" for your application */
38
39/*
40 * Open "include/lwcell/lwcell_opt.h" and
41 * copy & replace here settings you want to change values
42 */
43
44#endif /* LWCELL_OPTS_HDR_H */

Note

If you prefer to avoid using configuration file, application must define a global symbol LWCELL_IGNORE_USER_OPTS, visible across entire application. This can be achieved with -D compiler option.

User manual

Overview

IoT activity is embedded in today’s application. Almost every device uses some type of communication, from WiFi, BLE, Sub-GHz or NB-IoT/LTE/2G/3G.

LwCELL has been developed to allow customers to:

  • Develop on single (host MCU) architecture at the same time and do not care about device arch

  • Shorten time to market

Customers using LwCELL do not need to take care about proper command for specific task, they can call API functions to execute the task. Library will take the necessary steps in order to send right command to device via low-level driver (usually UART) and process incoming response from device before it will notify application layer if it was successfuly or not.

To summarize:

  • GSM device runs official AT firmware, provided by device vendor

  • Host MCU runs custom application, together with LwCELL library

  • Host MCU communicates with GSM device with UART or similar interface.

Architecture

Architecture of the library consists of 4 layers.

LwCELL layer architecture overview

LwCELL layer architecture overview

Application layer

User layer is the highest layer of the final application. This is the part where API functions are called to execute some command.

Middleware layer

Middleware part is actively developed and shall not be modified by customer by any means. If there is a necessity to do it, often it means that developer of the application uses it wrongly. This part is platform independent and does not use any specific compiler features for proper operation.

Note

There is no compiler specific features implemented in this layer.

System & low-level layer

Application needs to fully implement this part and resolve it with care. Functions are related to actual implementation with GSM device and are highly architecture oriented. Some examples for WIN32 and ARM Cortex-M are included with library.

Tip

Check Porting guide for detailed instructions and examples.

System functions

System functions are bridge between operating system running on embedded system and LwCELL middleware. Functions need to provide:

  • Thread management

  • Binary semaphore management

  • Recursive mutex management

  • Message queue management

  • Current time status information

Tip

System function prototypes are available in System functions section.

Low-level implementation

Low-Level, or LWCELL_LL, is part, dedicated for communication between LwCELL middleware and GSM physical device. Application needs to implement output function to send necessary AT command instruction aswell as implement input module to send received data from GSM device to LwCELL middleware.

Application must also assure memory assignment for Memory manager when default allocation is used.

Tip

Low level, input module & memory function prototypes are available in Low-Level functions, Input module and Memory manager respectfully.

GSM physical device

Inter thread communication

LwCELL is only available with operating system. For successful resources management, it uses 2 threads within library and allows multiple application threads to post new command to be processed.

Inter-thread architecture block diagram

Inter-thread architecture block diagram

Producing and Processing threads are part of library, its implementation is in lwcell_threads.c file.

Processing thread

Processing thread is in charge of processing each and every received character from GSM device. It can process URC messages which are received from GSM device without any command request. Some of them are:

  • +RECEIVE indicating new data packet received from remote side on active connection

  • RING indicating new call to be processed by GSM

  • and more others

Note

Received messages without any command (URC messages) are sent to application layer using events, where they can be processed and used in further steps

This thread also checks and processes specific received messages based on active command. As an example, when application tries to make a new connection to remote server, it starts command with AT+CIPSTART message. Thread understands that active command is to connect to remote side and will wait for potential 0, CONNECT OK message, indicating connection status. it will also wait for OK or ERROR, indicating command finished status before it unlocks sync_sem to unblock producing thread.

Tip

When thread tries to unlock sync_sem, it first checks if it has been locked by producing thread.

Producing thread

Producing thread waits for command messages posted from application thread. When new message has been received, it sends initial AT message over AT port.

  • It checks if command is valid and if it has corresponding initial AT sequence, such as AT+CIPSTART

  • It locks sync_sem semaphore and waits for processing thread to unlock it

    • Processing thread is in charge to read respone from GSM and react accordingly. See previous section for details.

  • If application uses blocking mode, it unlocks command sem semaphore and returns response

  • If application uses non-blocking mode, it frees memory for message and sends event with response message

Application thread

Application thread is considered any thread which calls API functions and therefore writes new messages to producing message queue, later processed by producing thread.

A new message memory is allocated in this thread and type of command is assigned to it, together with required input data for command. It also sets blocking or non-blocking mode, how command shall be executed.

When application tries to execute command in blocking mode, it creates new sync semaphore sem, locks it, writes message to producing queue and waits for sem to get unlocked. This effectively puts thread to blocked state by operating system and removes it from scheduler until semaphore is unlocked again. Semaphore sem gets unlocked in producing thread when response has been received for specific command.

Tip

sem semaphore is unlocked in producing thread after sync_sem is unlocked in processing thread

Note

Every command message uses its own sem semaphore to sync multiple application threads at the same time.

If message is to be executed in non-blocking mode, sem is not created as there is no need to block application thread. When this is the case, application thread will only write message command to producing queue and return status of writing to application.

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

Tip

Implementation of Netconn API leverages lwcell_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            lwcell_netconn.c
  3 * \brief           API functions for sequential calls
  4 */
  5
  6/*
  7 * Copyright (c) 2023 Tilen MAJERLE
  8 *
  9 * Permission is hereby granted, free of charge, to any person
 10 * obtaining a copy of this software and associated documentation
 11 * files (the "Software"), to deal in the Software without restriction,
 12 * including without limitation the rights to use, copy, modify, merge,
 13 * publish, distribute, sublicense, and/or sell copies of the Software,
 14 * and to permit persons to whom the Software is furnished to do so,
 15 * subject to the following conditions:
 16 *
 17 * The above copyright notice and this permission notice shall be
 18 * included in all copies or substantial portions of the Software.
 19 *
 20 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 21 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
 22 * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE
 23 * AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
 24 * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
 25 * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 26 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
 27 * OTHER DEALINGS IN THE SOFTWARE.
 28 *
 29 * This file is part of LwCELL - Lightweight cellular modem AT library.
 30 *
 31 * Author:          Tilen MAJERLE <tilen@majerle.eu>
 32 * Version:         v0.1.1
 33 */
 34#include "lwcell/lwcell_netconn.h"
 35#include "lwcell/lwcell_conn.h"
 36#include "lwcell/lwcell_mem.h"
 37#include "lwcell/lwcell_private.h"
 38
 39#if LWCELL_CFG_NETCONN || __DOXYGEN__
 40
 41/* Check conditions */
 42#if !LWCELL_CFG_CONN
 43#error "LWCELL_CFG_CONN must be enabled for NETCONN API!"
 44#endif /* !LWCELL_CFG_CONN */
 45
 46#if LWCELL_CFG_NETCONN_RECEIVE_QUEUE_LEN < 2
 47#error "LWCELL_CFG_NETCONN_RECEIVE_QUEUE_LEN must be greater or equal to 2"
 48#endif /* LWCELL_CFG_NETCONN_RECEIVE_QUEUE_LEN < 2 */
 49
 50/**
 51 * \brief           Sequential API structure
 52 */
 53typedef struct lwcell_netconn {
 54    struct lwcell_netconn* next;    /*!< Linked list entry */
 55
 56    lwcell_netconn_type_t type;     /*!< Netconn type */
 57
 58    size_t rcv_packets;            /*!< Number of received packets so far on this connection */
 59    lwcell_conn_p conn;             /*!< Pointer to actual connection */
 60
 61    lwcell_sys_mbox_t mbox_receive; /*!< Message queue for receive mbox */
 62
 63    lwcell_linbuff_t buff;          /*!< Linear buffer structure */
 64
 65    uint16_t conn_timeout;         /*!< Connection timeout in units of seconds when
 66                                                    netconn is in server (listen) mode.
 67                                                    Connection will be automatically closed if there is no
 68                                                    data exchange in time. Set to `0` when timeout feature is disabled. */
 69
 70#if LWCELL_CFG_NETCONN_RECEIVE_TIMEOUT || __DOXYGEN__
 71    uint32_t rcv_timeout; /*!< Receive timeout in unit of milliseconds */
 72#endif
 73} lwcell_netconn_t;
 74
 75static uint8_t recv_closed = 0xFF;
 76static lwcell_netconn_t* netconn_list; /*!< Linked list of netconn entries */
 77
 78/**
 79 * \brief           Flush all mboxes and clear possible used memories
 80 * \param[in]       nc: Pointer to netconn to flush
 81 * \param[in]       protect: Set to 1 to protect against multi-thread access
 82 */
 83static void
 84flush_mboxes(lwcell_netconn_t* nc, uint8_t protect) {
 85    lwcell_pbuf_p pbuf;
 86    if (protect) {
 87        lwcell_core_lock();
 88    }
 89    if (lwcell_sys_mbox_isvalid(&nc->mbox_receive)) {
 90        while (lwcell_sys_mbox_getnow(&nc->mbox_receive, (void**)&pbuf)) {
 91            if (pbuf != NULL && (uint8_t*)pbuf != (uint8_t*)&recv_closed) {
 92                lwcell_pbuf_free_s(&pbuf); /* Free received data buffers */
 93            }
 94        }
 95        lwcell_sys_mbox_delete(&nc->mbox_receive);  /* Delete message queue */
 96        lwcell_sys_mbox_invalid(&nc->mbox_receive); /* Invalid handle */
 97    }
 98    if (protect) {
 99        lwcell_core_unlock();
100    }
101}
102
103/**
104 * \brief           Callback function for every server connection
105 * \param[in]       evt: Pointer to callback structure
106 * \return          Member of \ref lwcellr_t enumeration
107 */
108static lwcellr_t
109netconn_evt(lwcell_evt_t* evt) {
110    lwcell_conn_p conn;
111    lwcell_netconn_t* nc = NULL;
112    uint8_t close = 0;
113
114    conn = lwcell_conn_get_from_evt(evt); /* Get connection from event */
115    switch (lwcell_evt_get_type(evt)) {
116        /*
117         * A new connection has been active
118         * and should be handled by netconn API
119         */
120        case LWCELL_EVT_CONN_ACTIVE: {          /* A new connection active is active */
121            if (lwcell_conn_is_client(conn)) {  /* Was connection started by us? */
122                nc = lwcell_conn_get_arg(conn); /* Argument should be already set */
123                if (nc != NULL) {
124                    nc->conn = conn;           /* Save actual connection */
125                } else {
126                    close = 1;                 /* Close this connection, invalid netconn */
127                }
128            } else {
129                LWCELL_DEBUGF(LWCELL_CFG_DBG_NETCONN | LWCELL_DBG_TYPE_TRACE | LWCELL_DBG_LVL_WARNING,
130                             "[LWCELL NETCONN] Closing connection, it is not in client mode!\r\n");
131                close = 1; /* Close the connection at this point */
132            }
133
134            /* Decide if some events want to close the connection */
135            if (close) {
136                if (nc != NULL) {
137                    lwcell_conn_set_arg(conn, NULL); /* Reset argument */
138                    lwcell_netconn_delete(nc);       /* Free memory for API */
139                }
140                lwcell_conn_close(conn, 0);          /* Close the connection */
141                close = 0;
142            }
143            break;
144        }
145
146        /*
147         * We have a new data received which
148         * should have netconn structure as argument
149         */
150        case LWCELL_EVT_CONN_RECV: {
151            lwcell_pbuf_p pbuf;
152
153            nc = lwcell_conn_get_arg(conn);            /* Get API from connection */
154            pbuf = lwcell_evt_conn_recv_get_buff(evt); /* Get received buff */
155
156            lwcell_conn_recved(conn, pbuf);            /* Notify stack about received data */
157
158            lwcell_pbuf_ref(pbuf);                     /* Increase reference counter */
159            if (nc == NULL || !lwcell_sys_mbox_isvalid(&nc->mbox_receive)
160                || !lwcell_sys_mbox_putnow(&nc->mbox_receive, pbuf)) {
161                LWCELL_DEBUGF(LWCELL_CFG_DBG_NETCONN, "[LWCELL NETCONN] Ignoring more data for receive!\r\n");
162                lwcell_pbuf_free_s(&pbuf); /* Free pbuf */
163                return lwcellOKIGNOREMORE; /* Return OK to free the memory and ignore further data */
164            }
165            ++nc->rcv_packets;            /* Increase number of received packets */
166            LWCELL_DEBUGF(LWCELL_CFG_DBG_NETCONN | LWCELL_DBG_TYPE_TRACE,
167                         "[LWCELL NETCONN] Received pbuf contains %d bytes. Handle written to receive mbox\r\n",
168                         (int)lwcell_pbuf_length(pbuf, 0));
169            break;
170        }
171
172        /* Connection was just closed */
173        case LWCELL_EVT_CONN_CLOSE: {
174            nc = lwcell_conn_get_arg(conn); /* Get API from connection */
175
176            /*
177             * In case we have a netconn available,
178             * simply write pointer to received variable to indicate closed state
179             */
180            if (nc != NULL && lwcell_sys_mbox_isvalid(&nc->mbox_receive)) {
181                lwcell_sys_mbox_putnow(&nc->mbox_receive, (void*)&recv_closed);
182            }
183
184            break;
185        }
186        default: return lwcellERR;
187    }
188    return lwcellOK;
189}
190
191/**
192 * \brief           Global event callback function
193 * \param[in]       evt: Callback information and data
194 * \return          \ref lwcellOK on success, member of \ref lwcellr_t otherwise
195 */
196static lwcellr_t
197lwcell_evt(lwcell_evt_t* evt) {
198    switch (lwcell_evt_get_type(evt)) {
199        default: break;
200    }
201    return lwcellOK;
202}
203
204/**
205 * \brief           Create new netconn connection
206 * \param[in]       type: Netconn connection type
207 * \return          New netconn connection on success, `NULL` otherwise
208 */
209lwcell_netconn_p
210lwcell_netconn_new(lwcell_netconn_type_t type) {
211    lwcell_netconn_t* a;
212    static uint8_t first = 1;
213
214    /* Register only once! */
215    lwcell_core_lock();
216    if (first) {
217        first = 0;
218        lwcell_evt_register(lwcell_evt); /* Register global event function */
219    }
220    lwcell_core_unlock();
221    a = lwcell_mem_calloc(1, sizeof(*a)); /* Allocate memory for core object */
222    if (a != NULL) {
223        a->type = type;                  /* Save netconn type */
224        a->conn_timeout = 0;             /* Default connection timeout */
225        if (!lwcell_sys_mbox_create(
226                &a->mbox_receive,
227                LWCELL_CFG_NETCONN_RECEIVE_QUEUE_LEN)) { /* Allocate memory for receiving message box */
228            LWCELL_DEBUGF(LWCELL_CFG_DBG_NETCONN | LWCELL_DBG_TYPE_TRACE | LWCELL_DBG_LVL_DANGER,
229                         "[LWCELL NETCONN] Cannot create receive MBOX\r\n");
230            goto free_ret;
231        }
232        lwcell_core_lock();
233        if (netconn_list == NULL) { /* Add new netconn to the existing list */
234            netconn_list = a;
235        } else {
236            a->next = netconn_list; /* Add it to beginning of the list */
237            netconn_list = a;
238        }
239        lwcell_core_unlock();
240    }
241    return a;
242free_ret:
243    if (lwcell_sys_mbox_isvalid(&a->mbox_receive)) {
244        lwcell_sys_mbox_delete(&a->mbox_receive);
245        lwcell_sys_mbox_invalid(&a->mbox_receive);
246    }
247    if (a != NULL) {
248        lwcell_mem_free_s((void**)&a);
249    }
250    return NULL;
251}
252
253/**
254 * \brief           Delete netconn connection
255 * \param[in]       nc: Netconn handle
256 * \return          \ref lwcellOK on success, member of \ref lwcellr_t enumeration otherwise
257 */
258lwcellr_t
259lwcell_netconn_delete(lwcell_netconn_p nc) {
260    LWCELL_ASSERT(nc != NULL);
261
262    lwcell_core_lock();
263    flush_mboxes(nc, 0); /* Clear mboxes */
264
265    /* Remove netconn from linkedlist */
266    if (netconn_list == nc) {
267        netconn_list = netconn_list->next; /* Remove first from linked list */
268    } else if (netconn_list != NULL) {
269        lwcell_netconn_p tmp, prev;
270        /* Find element on the list */
271        for (prev = netconn_list, tmp = netconn_list->next; tmp != NULL; prev = tmp, tmp = tmp->next) {
272            if (nc == tmp) {
273                prev->next = tmp->next; /* Remove tmp from linked list */
274                break;
275            }
276        }
277    }
278    lwcell_core_unlock();
279
280    lwcell_mem_free_s((void**)&nc);
281    return lwcellOK;
282}
283
284/**
285 * \brief           Connect to server as client
286 * \param[in]       nc: Netconn handle
287 * \param[in]       host: Pointer to host, such as domain name or IP address in string format
288 * \param[in]       port: Target port to use
289 * \return          \ref lwcellOK if successfully connected, member of \ref lwcellr_t otherwise
290 */
291lwcellr_t
292lwcell_netconn_connect(lwcell_netconn_p nc, const char* host, lwcell_port_t port) {
293    lwcellr_t res;
294
295    LWCELL_ASSERT(nc != NULL);
296    LWCELL_ASSERT(host != NULL);
297    LWCELL_ASSERT(port > 0);
298
299    /*
300     * Start a new connection as client and:
301     *
302     *  - Set current netconn structure as argument
303     *  - Set netconn callback function for connection management
304     *  - Start connection in blocking mode
305     */
306    res = lwcell_conn_start(NULL, (lwcell_conn_type_t)nc->type, host, port, nc, netconn_evt, 1);
307    return res;
308}
309
310/**
311 * \brief           Write data to connection output buffers
312 * \note            This function may only be used on TCP or SSL connections
313 * \param[in]       nc: Netconn handle used to write data to
314 * \param[in]       data: Pointer to data to write
315 * \param[in]       btw: Number of bytes to write
316 * \return          \ref lwcellOK on success, member of \ref lwcellr_t enumeration otherwise
317 */
318lwcellr_t
319lwcell_netconn_write(lwcell_netconn_p nc, const void* data, size_t btw) {
320    size_t len, sent;
321    const uint8_t* d = data;
322    lwcellr_t res;
323
324    LWCELL_ASSERT(nc != NULL);
325    LWCELL_ASSERT(nc->type == LWCELL_NETCONN_TYPE_TCP || nc->type == LWCELL_NETCONN_TYPE_SSL);
326    LWCELL_ASSERT(lwcell_conn_is_active(nc->conn));
327
328    /*
329     * Several steps are done in write process
330     *
331     * 1. Check if buffer is set and check if there is something to write to it.
332     *    1. In case buffer will be full after copy, send it and free memory.
333     * 2. Check how many bytes we can write directly without need to copy
334     * 3. Try to allocate a new buffer and copy remaining input data to it
335     * 4. In case buffer allocation fails, send data directly (may affect on speed and effectivenes)
336     */
337
338    /* Step 1 */
339    if (nc->buff.buff != NULL) {                           /* Is there a write buffer ready to accept more data? */
340        len = LWCELL_MIN(nc->buff.len - nc->buff.ptr, btw); /* Get number of bytes we can write to buffer */
341        if (len > 0) {
342            LWCELL_MEMCPY(&nc->buff.buff[nc->buff.ptr], data, len); /* Copy memory to temporary write buffer */
343            d += len;
344            nc->buff.ptr += len;
345            btw -= len;
346        }
347
348        /* Step 1.1 */
349        if (nc->buff.ptr == nc->buff.len) {
350            res = lwcell_conn_send(nc->conn, nc->buff.buff, nc->buff.len, &sent, 1);
351
352            lwcell_mem_free_s((void**)&nc->buff.buff);
353            if (res != lwcellOK) {
354                return res;
355            }
356        } else {
357            return lwcellOK; /* Buffer is not yet full yet */
358        }
359    }
360
361    /* Step 2 */
362    if (btw >= LWCELL_CFG_CONN_MAX_DATA_LEN) {
363        size_t rem;
364        rem = btw % LWCELL_CFG_CONN_MAX_DATA_LEN;                 /* Get remaining bytes for max data length */
365        res = lwcell_conn_send(nc->conn, d, btw - rem, &sent, 1); /* Write data directly */
366        if (res != lwcellOK) {
367            return res;
368        }
369        d += sent;   /* Advance in data pointer */
370        btw -= sent; /* Decrease remaining data to send */
371    }
372
373    if (btw == 0) { /* Sent everything? */
374        return lwcellOK;
375    }
376
377    /* Step 3 */
378    if (nc->buff.buff == NULL) {                    /* Check if we should allocate a new buffer */
379        nc->buff.buff = lwcell_mem_malloc(sizeof(*nc->buff.buff) * LWCELL_CFG_CONN_MAX_DATA_LEN);
380        nc->buff.len = LWCELL_CFG_CONN_MAX_DATA_LEN; /* Save buffer length */
381        nc->buff.ptr = 0;                           /* Save buffer pointer */
382    }
383
384    /* Step 4 */
385    if (nc->buff.buff != NULL) {                              /* Memory available? */
386        LWCELL_MEMCPY(&nc->buff.buff[nc->buff.ptr], d, btw);   /* Copy data to buffer */
387        nc->buff.ptr += btw;
388    } else {                                                  /* Still no memory available? */
389        return lwcell_conn_send(nc->conn, data, btw, NULL, 1); /* Simply send directly blocking */
390    }
391    return lwcellOK;
392}
393
394/**
395 * \brief           Extended version of \ref lwcell_netconn_write with additional
396 *                  option to set custom flags.
397 * 
398 * \note            It is recommended to use this for full features support 
399 * 
400 * \param[in]       nc: Netconn handle used to write data to
401 * \param[in]       data: Pointer to data to write
402 * \param[in]       btw: Number of bytes to write
403 * \param           flags: Bitwise-ORed set of flags for netconn.
404 *                      Flags start with \ref LWCELL_NETCONN_FLAG_xxx
405 * \return          \ref lwcellOK on success, member of \ref lwcellr_t enumeration otherwise
406 */
407lwcellr_t
408lwcell_netconn_write_ex(lwcell_netconn_p nc, const void* data, size_t btw, uint16_t flags) {
409    lwcellr_t res = lwcell_netconn_write(nc, data, btw);
410    if (res == lwcellOK) {
411        if (flags & LWCELL_NETCONN_FLAG_FLUSH) {
412            res = lwcell_netconn_flush(nc);
413        }
414    }
415    return res;
416}
417
418/**
419 * \brief           Flush buffered data on netconn \e TCP/SSL connection
420 * \note            This function may only be used on \e TCP/SSL connection
421 * \param[in]       nc: Netconn handle to flush data
422 * \return          \ref lwcellOK on success, member of \ref lwcellr_t enumeration otherwise
423 */
424lwcellr_t
425lwcell_netconn_flush(lwcell_netconn_p nc) {
426    LWCELL_ASSERT(nc != NULL);
427    LWCELL_ASSERT(nc->type == LWCELL_NETCONN_TYPE_TCP || nc->type == LWCELL_NETCONN_TYPE_SSL);
428    LWCELL_ASSERT(lwcell_conn_is_active(nc->conn));
429
430    /*
431     * In case we have data in write buffer,
432     * flush them out to network
433     */
434    if (nc->buff.buff != NULL) {                                             /* Check remaining data */
435        if (nc->buff.ptr > 0) {                                              /* Do we have data in current buffer? */
436            lwcell_conn_send(nc->conn, nc->buff.buff, nc->buff.ptr, NULL, 1); /* Send data */
437        }
438        lwcell_mem_free_s((void**)&nc->buff.buff);
439    }
440    return lwcellOK;
441}
442
443/**
444 * \brief           Send data on \e UDP connection to default IP and port
445 * \param[in]       nc: Netconn handle used to send
446 * \param[in]       data: Pointer to data to write
447 * \param[in]       btw: Number of bytes to write
448 * \return          \ref lwcellOK on success, member of \ref lwcellr_t enumeration otherwise
449 */
450lwcellr_t
451lwcell_netconn_send(lwcell_netconn_p nc, const void* data, size_t btw) {
452    LWCELL_ASSERT(nc != NULL);
453    LWCELL_ASSERT(nc->type == LWCELL_NETCONN_TYPE_UDP);
454    LWCELL_ASSERT(lwcell_conn_is_active(nc->conn));
455
456    return lwcell_conn_send(nc->conn, data, btw, NULL, 1);
457}
458
459/**
460 * \brief           Send data on \e UDP connection to specific IP and port
461 * \note            Use this function in case of UDP type netconn
462 * \param[in]       nc: Netconn handle used to send
463 * \param[in]       ip: Pointer to IP address
464 * \param[in]       port: Port number used to send data
465 * \param[in]       data: Pointer to data to write
466 * \param[in]       btw: Number of bytes to write
467 * \return          \ref lwcellOK on success, member of \ref lwcellr_t enumeration otherwise
468 */
469lwcellr_t
470lwcell_netconn_sendto(lwcell_netconn_p nc, const lwcell_ip_t* ip, lwcell_port_t port, const void* data, size_t btw) {
471    LWCELL_ASSERT(nc != NULL);
472    LWCELL_ASSERT(nc->type == LWCELL_NETCONN_TYPE_UDP);
473    LWCELL_ASSERT(lwcell_conn_is_active(nc->conn));
474
475    return lwcell_conn_sendto(nc->conn, ip, port, data, btw, NULL, 1);
476}
477
478/**
479 * \brief           Receive data from connection
480 * \param[in]       nc: Netconn handle used to receive from
481 * \param[in]       pbuf: Pointer to pointer to save new receive buffer to.
482 *                     When function returns, user must check for valid pbuf value `pbuf != NULL`
483 * \return          \ref lwcellOK when new data ready,
484 * \return          \ref lwcellCLOSED when connection closed by remote side,
485 * \return          \ref lwcellTIMEOUT when receive timeout occurs
486 * \return          Any other member of \ref lwcellr_t otherwise
487 */
488lwcellr_t
489lwcell_netconn_receive(lwcell_netconn_p nc, lwcell_pbuf_p* pbuf) {
490    LWCELL_ASSERT(nc != NULL);
491    LWCELL_ASSERT(pbuf != NULL);
492
493    *pbuf = NULL;
494#if LWCELL_CFG_NETCONN_RECEIVE_TIMEOUT
495    /*
496     * Wait for new received data for up to specific timeout
497     * or throw error for timeout notification
498     */
499    if (nc->rcv_timeout == LWCELL_NETCONN_RECEIVE_NO_WAIT) {
500        if (!lwcell_sys_mbox_getnow(&nc->mbox_receive, (void**)pbuf)) {
501            return lwcellTIMEOUT;
502        }
503    } else if (lwcell_sys_mbox_get(&nc->mbox_receive, (void**)pbuf, nc->rcv_timeout) == LWCELL_SYS_TIMEOUT) {
504        return lwcellTIMEOUT;
505    }
506#else  /* LWCELL_CFG_NETCONN_RECEIVE_TIMEOUT */
507    /* Forever wait for new receive packet */
508    lwcell_sys_mbox_get(&nc->mbox_receive, (void**)pbuf, 0);
509#endif /* !LWCELL_CFG_NETCONN_RECEIVE_TIMEOUT */
510
511    /* Check if connection closed */
512    if ((uint8_t*)(*pbuf) == (uint8_t*)&recv_closed) {
513        *pbuf = NULL; /* Reset pbuf */
514        return lwcellCLOSED;
515    }
516    return lwcellOK; /* We have data available */
517}
518
519/**
520 * \brief           Close a netconn connection
521 * \param[in]       nc: Netconn handle to close
522 * \return          \ref lwcellOK on success, member of \ref lwcellr_t enumeration otherwise
523 */
524lwcellr_t
525lwcell_netconn_close(lwcell_netconn_p nc) {
526    lwcell_conn_p conn;
527
528    LWCELL_ASSERT(nc != NULL);
529    LWCELL_ASSERT(nc->conn != NULL);
530    LWCELL_ASSERT(lwcell_conn_is_active(nc->conn));
531
532    lwcell_netconn_flush(nc); /* Flush data and ignore result */
533    conn = nc->conn;
534    nc->conn = NULL;
535
536    lwcell_conn_set_arg(conn, NULL); /* Reset argument */
537    lwcell_conn_close(conn, 1);      /* Close the connection */
538    flush_mboxes(nc, 1);            /* Flush message queues */
539    return lwcellOK;
540}
541
542/**
543 * \brief           Get connection number used for netconn
544 * \param[in]       nc: Netconn handle
545 * \return          `-1` on failure, connection number between `0` and \ref LWCELL_CFG_MAX_CONNS otherwise
546 */
547int8_t
548lwcell_netconn_getconnnum(lwcell_netconn_p nc) {
549    if (nc != NULL && nc->conn != NULL) {
550        return lwcell_conn_getnum(nc->conn);
551    }
552    return -1;
553}
554
555#if LWCELL_CFG_NETCONN_RECEIVE_TIMEOUT || __DOXYGEN__
556
557/**
558 * \brief           Set timeout value for receiving data.
559 *
560 * When enabled, \ref lwcell_netconn_receive will only block for up to
561 * \e timeout value and will return if no new data within this time
562 *
563 * \param[in]       nc: Netconn handle
564 * \param[in]       timeout: Timeout in units of milliseconds.
565 *                      Set to `0` to disable timeout feature. Function blocks until data receive or connection closed
566 *                      Set to `> 0` to set maximum milliseconds to wait before timeout
567 *                      Set to \ref LWCELL_NETCONN_RECEIVE_NO_WAIT to enable non-blocking receive
568 */
569void
570lwcell_netconn_set_receive_timeout(lwcell_netconn_p nc, uint32_t timeout) {
571    nc->rcv_timeout = timeout;
572}
573
574/**
575 * \brief           Get netconn receive timeout value
576 * \param[in]       nc: Netconn handle
577 * \return          Timeout in units of milliseconds.
578 *                  If value is `0`, timeout is disabled (wait forever)
579 */
580uint32_t
581lwcell_netconn_get_receive_timeout(lwcell_netconn_p nc) {
582    return nc->rcv_timeout; /* Return receive timeout */
583}
584
585#endif /* LWCELL_CFG_NETCONN_RECEIVE_TIMEOUT || __DOXYGEN__ */
586
587#endif /* LWCELL_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 LWCELL_EVT_CONN_*, such as LWCELL_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 lwcell_conn_start() function

An example of client with its dedicated event callback function
  1#include "client.h"
  2#include "lwcell/lwcell.h"
  3#include "lwcell/lwcell_network_api.h"
  4
  5/* Host parameter */
  6#define CONN_HOST           "example.com"
  7#define CONN_PORT           80
  8
  9static lwcellr_t   conn_callback_func(lwcell_evt_t* evt);
 10
 11/**
 12 * \brief           Request data for connection
 13 */
 14static const
 15uint8_t req_data[] = ""
 16                     "GET / HTTP/1.1\r\n"
 17                     "Host: " CONN_HOST "\r\n"
 18                     "Connection: close\r\n"
 19                     "\r\n";
 20
 21/**
 22 * \brief           Start a new connection(s) as client
 23 */
 24void
 25client_connect(void) {
 26    lwcellr_t res;
 27
 28    /* Attach to GSM network */
 29    lwcell_network_request_attach();
 30
 31    /* Start a new connection as client in non-blocking mode */
 32    if ((res = lwcell_conn_start(NULL, LWCELL_CONN_TYPE_TCP, "example.com", 80, NULL, conn_callback_func, 0)) == lwcellOK) {
 33        printf("Connection to " CONN_HOST " started...\r\n");
 34    } else {
 35        printf("Cannot start connection to " CONN_HOST "!\r\n");
 36    }
 37}
 38
 39/**
 40 * \brief           Event callback function for connection-only
 41 * \param[in]       evt: Event information with data
 42 * \return          \ref lwcellOK on success, member of \ref lwcellr_t otherwise
 43 */
 44static lwcellr_t
 45conn_callback_func(lwcell_evt_t* evt) {
 46    lwcell_conn_p conn;
 47    lwcellr_t res;
 48    uint8_t conn_num;
 49
 50    conn = lwcell_conn_get_from_evt(evt);          /* Get connection handle from event */
 51    if (conn == NULL) {
 52        return lwcellERR;
 53    }
 54    conn_num = lwcell_conn_getnum(conn);           /* Get connection number for identification */
 55    switch (lwcell_evt_get_type(evt)) {
 56        case LWCELL_EVT_CONN_ACTIVE: {             /* Connection just active */
 57            printf("Connection %d active!\r\n", (int)conn_num);
 58            res = lwcell_conn_send(conn, req_data, sizeof(req_data) - 1, NULL, 0); /* Start sending data in non-blocking mode */
 59            if (res == lwcellOK) {
 60                printf("Sending request data to server...\r\n");
 61            } else {
 62                printf("Cannot send request data to server. Closing connection manually...\r\n");
 63                lwcell_conn_close(conn, 0);        /* Close the connection */
 64            }
 65            break;
 66        }
 67        case LWCELL_EVT_CONN_CLOSE: {              /* Connection closed */
 68            if (lwcell_evt_conn_close_is_forced(evt)) {
 69                printf("Connection %d closed by client!\r\n", (int)conn_num);
 70            } else {
 71                printf("Connection %d closed by remote side!\r\n", (int)conn_num);
 72            }
 73            break;
 74        }
 75        case LWCELL_EVT_CONN_SEND: {               /* Data send event */
 76            lwcellr_t res = lwcell_evt_conn_send_get_result(evt);
 77            if (res == lwcellOK) {
 78                printf("Data sent successfully on connection %d...waiting to receive data from remote side...\r\n", (int)conn_num);
 79            } else {
 80                printf("Error while sending data on connection %d!\r\n", (int)conn_num);
 81            }
 82            break;
 83        }
 84        case LWCELL_EVT_CONN_RECV: {               /* Data received from remote side */
 85            lwcell_pbuf_p pbuf = lwcell_evt_conn_recv_get_buff(evt);
 86            lwcell_conn_recved(conn, pbuf);        /* Notify stack about received pbuf */
 87            printf("Received %d bytes on connection %d..\r\n", (int)lwcell_pbuf_length(pbuf, 1), (int)conn_num);
 88            break;
 89        }
 90        case LWCELL_EVT_CONN_ERROR: {              /* Error connecting to server */
 91            const char* host = lwcell_evt_conn_error_get_host(evt);
 92            lwcell_port_t port = lwcell_evt_conn_error_get_port(evt);
 93            printf("Error connecting to %s:%d\r\n", host, (int)port);
 94            break;
 95        }
 96        default:
 97            break;
 98    }
 99    return lwcellOK;
100}
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/* Somewhere in thread function */
 2
 3/* Get device hostname in blocking mode */
 4/* Function returns actual result */
 5if (lwcell_sms_send("+0123456789", "text", NULL, NULL, 1 /* 1 means blocking call */) == lwcellOK) {
 6    /* At this point we have valid result from device */
 7    printf("SMS sent successfully\r\n");
 8} else {
 9    printf("Error trying to send SMS..\r\n");
10}

Blocking or non-blocking API calls

API functions often allow application to set blocking parameter indicating if function shall be blocking or non-blocking.

Blocking mode

When the function is called in blocking mode blocking = 1, application thread gets suspended until response from GSM device is received. If there is a queue of multiple commands, thread may wait a while before receiving data.

When API function returns, application has valid response data and can react immediately.

  • Linear programming model may be used

  • Application may use multiple threads for real-time execution to prevent system stalling when running function call

Warning

Due to internal architecture, it is not allowed to call API functions in blocking mode from events or callbacks. Any attempt not to do so will result in function returning error.

Example code:

Blocking command example
 1/* Somewhere in thread function */
 2
 3/* Get device hostname in blocking mode */
 4/* Function returns actual result */
 5if (lwcell_sms_send("+0123456789", "text", NULL, NULL, 1 /* 1 means blocking call */) == lwcellOK) {
 6    /* At this point we have valid result from device */
 7    printf("SMS sent successfully\r\n");
 8} else {
 9    printf("Error trying to send SMS..\r\n");
10}
Non-blocking mode

If the API function is called in non-blocking mode, function will return immediately with status indicating if command request has been successfully sent to internal command queue. Response has to be processed in event callback function.

Warning

Due to internal architecture, it is only allowed to call API functions in non-blocking mode from events or callbacks. Any attempt to do so will result in function returning error.

Example code:

Non-blocking command example
 1/* Hostname event function, called when lwcell_sms_send() function finishes */
 2void
 3sms_send_fn(lwcellr_t res, void* arg) {
 4    /* Check actual result from device */
 5    if (res == lwcellOK) {
 6        printf("SMS sent successfully\r\n");
 7    } else {
 8        printf("Error trying to send SMS\r\n");
 9    }
10}
11
12/* Somewhere in thread and/or other GSM event function */
13
14/* Send SMS in non-blocking mode */
15/* Function now returns if command has been sent to internal message queue */
16if (lwcell_sms_send("number", "text message", sms_send_fn, NULL, 0 /* 0 means non-blocking call */) == lwcellOK) {
17    /* At this point we only know that command has been sent to queue */
18    printf("SMS send message command sent to queue.\r\n");
19} else {
20    /* Error writing message to queue */
21    printf("Cannot send SMS send message command to queue. Maybe out of memory? Check result from function\r\n");
22}

Warning

When using non-blocking API calls, do not use local variables as parameter. This may introduce undefined behavior and memory corruption if application function returns before command is executed.

Example of a bad code:

Example of bad usage of non-blocking command
 1/* Hostname event function, called when lwcell_sms_send() function finishes */
 2void
 3sms_send_fn(lwcellr_t res, void* arg) {
 4    /* Check actual result from device */
 5    if (res == lwcellOK) {
 6        printf("SMS sent successfully\r\n");
 7    } else {
 8        printf("Error trying to send SMS\r\n");
 9    }
10}
11
12/* Check hostname */
13void
14check_hostname(void) {
15    char message[] = "text message";
16
17    /* Send SMS in non-blocking mode */
18    /* Function now returns if command has been sent to internal message queue */
19    /* It uses pointer to local data but w/o blocking command */
20    if (lwcell_sms_send("number", message, sms_send_fn, NULL, 0 /* 0 means non-blocking call */) == lwcellOK) {
21        /* At this point we only know that command has been sent to queue */
22        printf("SMS send message command sent to queue.\r\n");
23    } else {
24        /* Error writing message to queue */
25        printf("Cannot send SMS send message command to queue. Maybe out of memory? Check result from function\r\n");
26    }
27}

Porting guide

High level of LwCELL library is platform independent, written in C (C11), however there is an important part where middleware needs to communicate with target GSM device and it must work under different optional operating systems selected by final customer.

Porting consists of:

  • Implementation of low-level part, for actual communication between host device and GSM device

  • Implementation of system functions, link between target operating system and middleware functions

  • Assignment of memory for allocation manager

Implement low-level driver

To successfully prepare all parts of low-level driver, application must take care of:

  • Implementing lwcell_ll_init() and lwcell_ll_deinit() callback functions

  • Implement and assign send data and optional hardware reset function callbacks

  • Assign memory for allocation manager when using default allocator or use custom allocator

  • Process received data from ESP device and send it to input module for further processing

Tip

Port examples are available for STM32 and WIN32 architectures. Both actual working and up-to-date implementations are available within the library.

Note

Check Input module for more information about direct & indirect input processing.

Implement system functions

System functions are bridge between operating system calls and GSM middleware. GSM library relies on stable operating system features and its implementation and does not require any special features which do not normally come with operating systems.

Operating system must support:

  • Thread management functions

  • Mutex management functions

  • Binary semaphores only functions, no need for counting semaphores

  • Message queue management functions

Warning

If any of the features are not available within targeted operating system, customer needs to resolve it with care. As an example, message queue is not available in WIN32 OS API therefore custom message queue has been implemented using binary semaphores

Application needs to implement all system call functions, starting with lwcell_sys_. It must also prepare header file for standard types in order to support OS types within GSM middleware.

An example code is provided latter section of this page for WIN32 and STM32.

Note

Check System functions for function prototypes.

Steps to follow
  • Copy lwcell/src/system/lwcell_sys_template.c to the same folder and rename it to application port, eg. lwcell_sys_win32.c

  • Open newly created file and implement all system functions

  • Copy folder lwcell/src/include/system/port/template/* to the same folder and rename folder name to application port, eg. cmsis_os

  • Open lwcell_sys_port.h file from newly created folder and implement all typedefs and macros for specific target

  • Add source file to compiler sources and add path to header file to include paths in compiler options

Note

Check System functions for function prototypes.

Example: Low-level driver for WIN32

Example code for low-level porting on WIN32 platform. It uses native Windows features to open COM port and read/write from/to it.

Notes:

Actual implementation of low-level driver for WIN32
  1/**
  2 * \file            lwcell_ll_win32.c
  3 * \brief           Low-level communication with GSM device for WIN32
  4 */
  5
  6/*
  7 * Copyright (c) 2023 Tilen MAJERLE
  8 *
  9 * Permission is hereby granted, free of charge, to any person
 10 * obtaining a copy of this software and associated documentation
 11 * files (the "Software"), to deal in the Software without restriction,
 12 * including without limitation the rights to use, copy, modify, merge,
 13 * publish, distribute, sublicense, and/or sell copies of the Software,
 14 * and to permit persons to whom the Software is furnished to do so,
 15 * subject to the following conditions:
 16 *
 17 * The above copyright notice and this permission notice shall be
 18 * included in all copies or substantial portions of the Software.
 19 *
 20 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 21 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
 22 * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE
 23 * AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
 24 * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
 25 * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 26 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
 27 * OTHER DEALINGS IN THE SOFTWARE.
 28 *
 29 * This file is part of LwCELL - Lightweight cellular modem AT library.
 30 *
 31 * Author:          Tilen MAJERLE <tilen@majerle.eu>
 32 * Version:         v0.1.1
 33 */
 34#include "lwcell/lwcell_input.h"
 35#include "lwcell/lwcell_mem.h"
 36#include "lwcell/lwcell_types.h"
 37#include "lwcell/lwcell_utils.h"
 38#include "system/lwcell_ll.h"
 39#include "system/lwcell_sys.h"
 40#include "windows.h"
 41
 42#if !__DOXYGEN__
 43
 44static uint8_t initialized = 0;
 45static HANDLE thread_handle;
 46static volatile HANDLE com_port;    /*!< COM port handle */
 47static uint8_t data_buffer[0x1000]; /*!< Received data array */
 48
 49static void uart_thread(void* param);
 50
 51/**
 52 * \brief           Send data to GSM device, function called from GSM stack when we have data to send
 53 * \param[in]       data: Pointer to data to send
 54 * \param[in]       len: Number of bytes to send
 55 * \return          Number of bytes sent
 56 */
 57static size_t
 58send_data(const void* data, size_t len) {
 59    DWORD written;
 60    if (com_port != NULL) {
 61#if !LWCELL_CFG_AT_ECHO
 62        const uint8_t* d = data;
 63        HANDLE hConsole;
 64
 65        hConsole = GetStdHandle(STD_OUTPUT_HANDLE);
 66        SetConsoleTextAttribute(hConsole, FOREGROUND_RED);
 67        for (DWORD i = 0; i < len; ++i) {
 68            printf("%c", d[i]);
 69        }
 70        SetConsoleTextAttribute(hConsole, FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE);
 71#endif /* !LWCELL_CFG_AT_ECHO */
 72
 73        /* Write data to AT port */
 74        WriteFile(com_port, data, len, &written, NULL);
 75        FlushFileBuffers(com_port);
 76        return written;
 77    }
 78    return 0;
 79}
 80
 81/**
 82 * \brief           Configure UART (USB to UART)
 83 */
 84static uint8_t
 85configure_uart(uint32_t baudrate) {
 86    DCB dcb = {0};
 87    dcb.DCBlength = sizeof(dcb);
 88
 89    /*
 90     * On first call,
 91     * create virtual file on selected COM port and open it
 92     * as generic read and write
 93     */
 94    if (!initialized) {
 95        static const char* com_ports[] = {"\\\\.\\COM23", "\\\\.\\COM12", "\\\\.\\COM9", "\\\\.\\COM8", "\\\\.\\COM4"};
 96        for (size_t i = 0; i < sizeof(com_ports) / sizeof(com_ports[0]); ++i) {
 97            com_port = CreateFileA(com_ports[i], GENERIC_READ | GENERIC_WRITE, 0, 0, OPEN_EXISTING, 0, NULL);
 98            if (GetCommState(com_port, &dcb)) {
 99                printf("COM PORT %s opened!\r\n", (const char*)com_ports[i]);
100                break;
101            }
102        }
103    }
104
105    /* Configure COM port parameters */
106    if (GetCommState(com_port, &dcb)) {
107        COMMTIMEOUTS timeouts;
108
109        dcb.BaudRate = baudrate;
110        dcb.ByteSize = 8;
111        dcb.Parity = NOPARITY;
112        dcb.StopBits = ONESTOPBIT;
113
114        if (!SetCommState(com_port, &dcb)) {
115            printf("Cannot set COM PORT info\r\n");
116            return 0;
117        }
118        if (GetCommTimeouts(com_port, &timeouts)) {
119            /* Set timeout to return immediately from ReadFile function */
120            timeouts.ReadIntervalTimeout = MAXDWORD;
121            timeouts.ReadTotalTimeoutConstant = 0;
122            timeouts.ReadTotalTimeoutMultiplier = 0;
123            if (!SetCommTimeouts(com_port, &timeouts)) {
124                printf("Cannot set COM PORT timeouts\r\n");
125            }
126            GetCommTimeouts(com_port, &timeouts);
127        } else {
128            printf("Cannot get COM PORT timeouts\r\n");
129            return 0;
130        }
131    } else {
132        printf("Cannot get COM PORT info\r\n");
133        return 0;
134    }
135
136    /* On first function call, create a thread to read data from COM port */
137    if (!initialized) {
138        lwcell_sys_thread_create(&thread_handle, "lwcell_ll_thread", uart_thread, NULL, 0, 0);
139    }
140    return 1;
141}
142
143/**
144 * \brief            UART thread
145 */
146static void
147uart_thread(void* param) {
148    DWORD bytes_read;
149    lwcell_sys_sem_t sem;
150    FILE* file = NULL;
151
152    LWCELL_UNUSED(param);
153
154    lwcell_sys_sem_create(&sem, 0); /* Create semaphore for delay functions */
155
156    while (com_port == NULL) {
157        lwcell_sys_sem_wait(&sem, 1); /* Add some delay with yield */
158    }
159
160    fopen_s(&file, "log_file.txt", "w+"); /* Open debug file in write mode */
161    while (1) {
162        /*
163         * Try to read data from COM port
164         * and send it to upper layer for processing
165         */
166        do {
167            ReadFile(com_port, data_buffer, sizeof(data_buffer), &bytes_read, NULL);
168            if (bytes_read > 0) {
169                HANDLE hConsole;
170                hConsole = GetStdHandle(STD_OUTPUT_HANDLE);
171                SetConsoleTextAttribute(hConsole, FOREGROUND_GREEN);
172                for (DWORD i = 0; i < bytes_read; ++i) {
173                    printf("%c", data_buffer[i]);
174                }
175                SetConsoleTextAttribute(hConsole, FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE);
176
177                /* Send received data to input processing module */
178#if LWCELL_CFG_INPUT_USE_PROCESS
179                lwcell_input_process(data_buffer, (size_t)bytes_read);
180#else  /* LWCELL_CFG_INPUT_USE_PROCESS */
181                lwcell_input(data_buffer, (size_t)bytes_read);
182#endif /* !LWCELL_CFG_INPUT_USE_PROCESS */
183
184                /* Write received data to output debug file */
185                if (file != NULL) {
186                    fwrite(data_buffer, 1, bytes_read, file);
187                    fflush(file);
188                }
189            }
190        } while (bytes_read == (DWORD)sizeof(data_buffer));
191
192        /* Implement delay to allow other tasks processing */
193        lwcell_sys_sem_wait(&sem, 1);
194    }
195}
196
197/**
198 * \brief           Callback function called from initialization process
199 *
200 * \note            This function may be called multiple times if AT baudrate is changed from application.
201 *                  It is important that every configuration except AT baudrate is configured only once!
202 *
203 * \note            This function may be called from different threads in GSM stack when using OS.
204 *                  When \ref LWCELL_CFG_INPUT_USE_PROCESS is set to 1, this function may be called from user UART thread.
205 *
206 * \param[in,out]   ll: Pointer to \ref lwcell_ll_t structure to fill data for communication functions
207 * \return          \ref lwcellOK on success, member of \ref lwcellr_t enumeration otherwise
208 */
209lwcellr_t
210lwcell_ll_init(lwcell_ll_t* ll) {
211#if !LWCELL_CFG_MEM_CUSTOM
212    /* Step 1: Configure memory for dynamic allocations */
213    static uint8_t memory[0x10000]; /* Create memory for dynamic allocations with specific size */
214
215    /*
216     * Create memory region(s) of memory.
217     * If device has internal/external memory available,
218     * multiple memories may be used
219     */
220    lwcell_mem_region_t mem_regions[] = {{memory, sizeof(memory)}};
221    if (!initialized) {
222        lwcell_mem_assignmemory(mem_regions,
223                               LWCELL_ARRAYSIZE(mem_regions)); /* Assign memory for allocations to GSM library */
224    }
225#endif /* !LWCELL_CFG_MEM_CUSTOM */
226
227    /* Step 2: Set AT port send function to use when we have data to transmit */
228    if (!initialized) {
229        ll->send_fn = send_data; /* Set callback function to send data */
230    }
231
232    /* Step 3: Configure AT port to be able to send/receive data to/from GSM device */
233    if (!configure_uart(ll->uart.baudrate)) { /* Initialize UART for communication */
234        return lwcellERR;
235    }
236    initialized = 1;
237    return lwcellOK;
238}
239
240#endif /* !__DOXYGEN__ */
Example: Low-level driver for STM32

Example code for low-level porting on STM32 platform. It uses CMSIS-OS based application layer functions for implementing threads & other OS dependent features.

Notes:

  • It uses separate thread for received data processing. It uses lwcell_input_process() function to directly process received data without using intermediate receive buffer

  • Memory manager has been assigned to 1 region of LWCELL_MEM_SIZE size

  • It sets send and reset callback functions for LwCELL library

Actual implementation of low-level driver for STM32
  1/**
  2 * \file            lwcell_ll_stm32.c
  3 * \brief           Generic STM32 driver, included in various STM32 driver variants
  4 */
  5
  6/*
  7 * Copyright (c) 2023 Tilen MAJERLE
  8 *
  9 * Permission is hereby granted, free of charge, to any person
 10 * obtaining a copy of this software and associated documentation
 11 * files (the "Software"), to deal in the Software without restriction,
 12 * including without limitation the rights to use, copy, modify, merge,
 13 * publish, distribute, sublicense, and/or sell copies of the Software,
 14 * and to permit persons to whom the Software is furnished to do so,
 15 * subject to the following conditions:
 16 *
 17 * The above copyright notice and this permission notice shall be
 18 * included in all copies or substantial portions of the Software.
 19 *
 20 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 21 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
 22 * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE
 23 * AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
 24 * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
 25 * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 26 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
 27 * OTHER DEALINGS IN THE SOFTWARE.
 28 *
 29 * This file is part of LwCELL - Lightweight cellular modem AT library.
 30 *
 31 * Author:          Tilen MAJERLE <tilen@majerle.eu>
 32 * Version:         v0.1.1
 33 */
 34
 35/*
 36 * How it works
 37 *
 38 * On first call to \ref lwcell_ll_init, new thread is created and processed in usart_ll_thread function.
 39 * USART is configured in RX DMA mode and any incoming bytes are processed inside thread function.
 40 * DMA and USART implement interrupt handlers to notify main thread about new data ready to send to upper layer.
 41 *
 42 * More about UART + RX DMA: https://github.com/MaJerle/stm32-usart-dma-rx-tx
 43 *
 44 * \ref LWCELL_CFG_INPUT_USE_PROCESS must be enabled in `lwcell_config.h` to use this driver.
 45 */
 46#include "lwcell/lwcell_input.h"
 47#include "lwcell/lwcell_mem.h"
 48#include "lwcell/lwcell_types.h"
 49#include "system/lwcell_ll.h"
 50
 51#if !__DOXYGEN__
 52
 53#if !LWCELL_CFG_INPUT_USE_PROCESS
 54#error "LWCELL_CFG_INPUT_USE_PROCESS must be enabled in `lwcell_config.h` to use this driver."
 55#endif /* LWCELL_CFG_INPUT_USE_PROCESS */
 56
 57#if !defined(LWCELL_USART_DMA_RX_BUFF_SIZE)
 58#define LWCELL_USART_DMA_RX_BUFF_SIZE 0x1000
 59#endif /* !defined(LWCELL_USART_DMA_RX_BUFF_SIZE) */
 60
 61#if !defined(LWCELL_MEM_SIZE)
 62#define LWCELL_MEM_SIZE 0x1000
 63#endif /* !defined(LWCELL_MEM_SIZE) */
 64
 65#if !defined(LWCELL_USART_RDR_NAME)
 66#define LWCELL_USART_RDR_NAME RDR
 67#endif /* !defined(LWCELL_USART_RDR_NAME) */
 68
 69/* USART memory */
 70static uint8_t usart_mem[LWCELL_USART_DMA_RX_BUFF_SIZE];
 71static uint8_t is_running, initialized;
 72static size_t old_pos;
 73
 74/* USART thread */
 75static void usart_ll_thread(void* arg);
 76static osThreadId_t usart_ll_thread_id;
 77
 78/* Message queue */
 79static osMessageQueueId_t usart_ll_mbox_id;
 80
 81/**
 82 * \brief           USART data processing
 83 */
 84static void
 85usart_ll_thread(void* arg) {
 86    size_t pos;
 87
 88    LWCELL_UNUSED(arg);
 89
 90    while (1) {
 91        void* d;
 92        /* Wait for the event message from DMA or USART */
 93        osMessageQueueGet(usart_ll_mbox_id, &d, NULL, osWaitForever);
 94
 95        /* Read data */
 96#if defined(LWCELL_USART_DMA_RX_STREAM)
 97        pos = sizeof(usart_mem) - LL_DMA_GetDataLength(LWCELL_USART_DMA, LWCELL_USART_DMA_RX_STREAM);
 98#else
 99        pos = sizeof(usart_mem) - LL_DMA_GetDataLength(LWCELL_USART_DMA, LWCELL_USART_DMA_RX_CH);
100#endif /* defined(LWCELL_USART_DMA_RX_STREAM) */
101        if (pos != old_pos && is_running) {
102            if (pos > old_pos) {
103                lwcell_input_process(&usart_mem[old_pos], pos - old_pos);
104            } else {
105                lwcell_input_process(&usart_mem[old_pos], sizeof(usart_mem) - old_pos);
106                if (pos > 0) {
107                    lwcell_input_process(&usart_mem[0], pos);
108                }
109            }
110            old_pos = pos;
111            if (old_pos == sizeof(usart_mem)) {
112                old_pos = 0;
113            }
114        }
115    }
116}
117
118/**
119 * \brief           Configure UART using DMA for receive in double buffer mode and IDLE line detection
120 */
121static void
122configure_uart(uint32_t baudrate) {
123    static LL_USART_InitTypeDef usart_init;
124    static LL_DMA_InitTypeDef dma_init;
125    LL_GPIO_InitTypeDef gpio_init;
126
127    if (!initialized) {
128        /* Enable peripheral clocks */
129        LWCELL_USART_CLK;
130        LWCELL_USART_DMA_CLK;
131        LWCELL_USART_TX_PORT_CLK;
132        LWCELL_USART_RX_PORT_CLK;
133
134#if defined(LWCELL_RESET_PIN)
135        LWCELL_RESET_PORT_CLK;
136#endif /* defined(LWCELL_RESET_PIN) */
137
138        /* Global pin configuration */
139        LL_GPIO_StructInit(&gpio_init);
140        gpio_init.OutputType = LL_GPIO_OUTPUT_PUSHPULL;
141        gpio_init.Pull = LL_GPIO_PULL_UP;
142        gpio_init.Speed = LL_GPIO_SPEED_FREQ_VERY_HIGH;
143        gpio_init.Mode = LL_GPIO_MODE_OUTPUT;
144
145#if defined(LWCELL_RESET_PIN)
146        /* Configure RESET pin */
147        gpio_init.Pin = LWCELL_RESET_PIN;
148        LL_GPIO_Init(LWCELL_RESET_PORT, &gpio_init);
149#endif /* defined(LWCELL_RESET_PIN) */
150
151        /* Configure USART pins */
152        gpio_init.Mode = LL_GPIO_MODE_ALTERNATE;
153
154        /* TX PIN */
155        gpio_init.Alternate = LWCELL_USART_TX_PIN_AF;
156        gpio_init.Pin = LWCELL_USART_TX_PIN;
157        LL_GPIO_Init(LWCELL_USART_TX_PORT, &gpio_init);
158
159        /* RX PIN */
160        gpio_init.Alternate = LWCELL_USART_RX_PIN_AF;
161        gpio_init.Pin = LWCELL_USART_RX_PIN;
162        LL_GPIO_Init(LWCELL_USART_RX_PORT, &gpio_init);
163
164        /* Configure UART */
165        LL_USART_DeInit(LWCELL_USART);
166        LL_USART_StructInit(&usart_init);
167        usart_init.BaudRate = baudrate;
168        usart_init.DataWidth = LL_USART_DATAWIDTH_8B;
169        usart_init.HardwareFlowControl = LL_USART_HWCONTROL_NONE;
170        usart_init.OverSampling = LL_USART_OVERSAMPLING_16;
171        usart_init.Parity = LL_USART_PARITY_NONE;
172        usart_init.StopBits = LL_USART_STOPBITS_1;
173        usart_init.TransferDirection = LL_USART_DIRECTION_TX_RX;
174        LL_USART_Init(LWCELL_USART, &usart_init);
175
176        /* Enable USART interrupts and DMA request */
177        LL_USART_EnableIT_IDLE(LWCELL_USART);
178        LL_USART_EnableIT_PE(LWCELL_USART);
179        LL_USART_EnableIT_ERROR(LWCELL_USART);
180        LL_USART_EnableDMAReq_RX(LWCELL_USART);
181
182        /* Enable USART interrupts */
183        NVIC_SetPriority(LWCELL_USART_IRQ, NVIC_EncodePriority(NVIC_GetPriorityGrouping(), 0x07, 0x00));
184        NVIC_EnableIRQ(LWCELL_USART_IRQ);
185
186        /* Configure DMA */
187        is_running = 0;
188#if defined(LWCELL_USART_DMA_RX_STREAM)
189        LL_DMA_DeInit(LWCELL_USART_DMA, LWCELL_USART_DMA_RX_STREAM);
190        dma_init.Channel = LWCELL_USART_DMA_RX_CH;
191#else
192        LL_DMA_DeInit(LWCELL_USART_DMA, LWCELL_USART_DMA_RX_CH);
193        dma_init.PeriphRequest = LWCELL_USART_DMA_RX_REQ_NUM;
194#endif /* defined(LWCELL_USART_DMA_RX_STREAM) */
195        dma_init.PeriphOrM2MSrcAddress = (uint32_t)&LWCELL_USART->LWCELL_USART_RDR_NAME;
196        dma_init.MemoryOrM2MDstAddress = (uint32_t)usart_mem;
197        dma_init.Direction = LL_DMA_DIRECTION_PERIPH_TO_MEMORY;
198        dma_init.Mode = LL_DMA_MODE_CIRCULAR;
199        dma_init.PeriphOrM2MSrcIncMode = LL_DMA_PERIPH_NOINCREMENT;
200        dma_init.MemoryOrM2MDstIncMode = LL_DMA_MEMORY_INCREMENT;
201        dma_init.PeriphOrM2MSrcDataSize = LL_DMA_PDATAALIGN_BYTE;
202        dma_init.MemoryOrM2MDstDataSize = LL_DMA_MDATAALIGN_BYTE;
203        dma_init.NbData = sizeof(usart_mem);
204        dma_init.Priority = LL_DMA_PRIORITY_MEDIUM;
205#if defined(LWCELL_USART_DMA_RX_STREAM)
206        LL_DMA_Init(LWCELL_USART_DMA, LWCELL_USART_DMA_RX_STREAM, &dma_init);
207#else
208        LL_DMA_Init(LWCELL_USART_DMA, LWCELL_USART_DMA_RX_CH, &dma_init);
209#endif /* defined(LWCELL_USART_DMA_RX_STREAM) */
210
211        /* Enable DMA interrupts */
212#if defined(LWCELL_USART_DMA_RX_STREAM)
213        LL_DMA_EnableIT_HT(LWCELL_USART_DMA, LWCELL_USART_DMA_RX_STREAM);
214        LL_DMA_EnableIT_TC(LWCELL_USART_DMA, LWCELL_USART_DMA_RX_STREAM);
215        LL_DMA_EnableIT_TE(LWCELL_USART_DMA, LWCELL_USART_DMA_RX_STREAM);
216        LL_DMA_EnableIT_FE(LWCELL_USART_DMA, LWCELL_USART_DMA_RX_STREAM);
217        LL_DMA_EnableIT_DME(LWCELL_USART_DMA, LWCELL_USART_DMA_RX_STREAM);
218#else
219        LL_DMA_EnableIT_HT(LWCELL_USART_DMA, LWCELL_USART_DMA_RX_CH);
220        LL_DMA_EnableIT_TC(LWCELL_USART_DMA, LWCELL_USART_DMA_RX_CH);
221        LL_DMA_EnableIT_TE(LWCELL_USART_DMA, LWCELL_USART_DMA_RX_CH);
222#endif /* defined(LWCELL_USART_DMA_RX_STREAM) */
223
224        /* Enable DMA interrupts */
225        NVIC_SetPriority(LWCELL_USART_DMA_RX_IRQ, NVIC_EncodePriority(NVIC_GetPriorityGrouping(), 0x07, 0x00));
226        NVIC_EnableIRQ(LWCELL_USART_DMA_RX_IRQ);
227
228        old_pos = 0;
229        is_running = 1;
230
231        /* Start DMA and USART */
232#if defined(LWCELL_USART_DMA_RX_STREAM)
233        LL_DMA_EnableStream(LWCELL_USART_DMA, LWCELL_USART_DMA_RX_STREAM);
234#else
235        LL_DMA_EnableChannel(LWCELL_USART_DMA, LWCELL_USART_DMA_RX_CH);
236#endif /* defined(LWCELL_USART_DMA_RX_STREAM) */
237        LL_USART_Enable(LWCELL_USART);
238    } else {
239        osDelay(10);
240        LL_USART_Disable(LWCELL_USART);
241        usart_init.BaudRate = baudrate;
242        LL_USART_Init(LWCELL_USART, &usart_init);
243        LL_USART_Enable(LWCELL_USART);
244    }
245
246    /* Create mbox and start thread */
247    if (usart_ll_mbox_id == NULL) {
248        usart_ll_mbox_id = osMessageQueueNew(10, sizeof(void*), NULL);
249    }
250    if (usart_ll_thread_id == NULL) {
251        const osThreadAttr_t attr = {.stack_size = 1024};
252        usart_ll_thread_id = osThreadNew(usart_ll_thread, usart_ll_mbox_id, &attr);
253    }
254}
255
256#if defined(LWCELL_RESET_PIN)
257/**
258 * \brief           Hardware reset callback
259 */
260static uint8_t
261reset_device(uint8_t state) {
262    if (state) { /* Activate reset line */
263        LL_GPIO_ResetOutputPin(LWCELL_RESET_PORT, LWCELL_RESET_PIN);
264    } else {
265        LL_GPIO_SetOutputPin(LWCELL_RESET_PORT, LWCELL_RESET_PIN);
266    }
267    return 1;
268}
269#endif /* defined(LWCELL_RESET_PIN) */
270
271/**
272 * \brief           Send data to GSM device
273 * \param[in]       data: Pointer to data to send
274 * \param[in]       len: Number of bytes to send
275 * \return          Number of bytes sent
276 */
277static size_t
278send_data(const void* data, size_t len) {
279    const uint8_t* d = data;
280
281    for (size_t i = 0; i < len; ++i, ++d) {
282        LL_USART_TransmitData8(LWCELL_USART, *d);
283        while (!LL_USART_IsActiveFlag_TXE(LWCELL_USART)) {}
284    }
285    return len;
286}
287
288/**
289 * \brief           Callback function called from initialization process
290 * \note            This function may be called multiple times if AT baudrate is changed from application
291 * \param[in,out]   ll: Pointer to \ref lwcell_ll_t structure to fill data for communication functions
292 * \param[in]       baudrate: Baudrate to use on AT port
293 * \return          Member of \ref lwcellr_t enumeration
294 */
295lwcellr_t
296lwcell_ll_init(lwcell_ll_t* ll) {
297#if !LWCELL_CFG_MEM_CUSTOM
298    static uint8_t memory[LWCELL_MEM_SIZE];
299    lwcell_mem_region_t mem_regions[] = {{memory, sizeof(memory)}};
300
301    if (!initialized) {
302        lwcell_mem_assignmemory(mem_regions, LWCELL_ARRAYSIZE(mem_regions)); /* Assign memory for allocations */
303    }
304#endif /* !LWCELL_CFG_MEM_CUSTOM */
305
306    if (!initialized) {
307        ll->send_fn = send_data; /* Set callback function to send data */
308#if defined(LWCELL_RESET_PIN)
309        ll->reset_fn = reset_device; /* Set callback for hardware reset */
310#endif                               /* defined(LWCELL_RESET_PIN) */
311    }
312
313    configure_uart(ll->uart.baudrate); /* Initialize UART for communication */
314    initialized = 1;
315    return lwcellOK;
316}
317
318/**
319 * \brief           Callback function to de-init low-level communication part
320 * \param[in,out]   ll: Pointer to \ref lwcell_ll_t structure to fill data for communication functions
321 * \return          \ref lwcellOK on success, member of \ref lwcellr_t enumeration otherwise
322 */
323lwcellr_t
324lwcell_ll_deinit(lwcell_ll_t* ll) {
325    if (usart_ll_mbox_id != NULL) {
326        osMessageQueueId_t tmp = usart_ll_mbox_id;
327        usart_ll_mbox_id = NULL;
328        osMessageQueueDelete(tmp);
329    }
330    if (usart_ll_thread_id != NULL) {
331        osThreadId_t tmp = usart_ll_thread_id;
332        usart_ll_thread_id = NULL;
333        osThreadTerminate(tmp);
334    }
335    initialized = 0;
336    LWCELL_UNUSED(ll);
337    return lwcellOK;
338}
339
340/**
341 * \brief           UART global interrupt handler
342 */
343void
344LWCELL_USART_IRQHANDLER(void) {
345    LL_USART_ClearFlag_IDLE(LWCELL_USART);
346    LL_USART_ClearFlag_PE(LWCELL_USART);
347    LL_USART_ClearFlag_FE(LWCELL_USART);
348    LL_USART_ClearFlag_ORE(LWCELL_USART);
349    LL_USART_ClearFlag_NE(LWCELL_USART);
350
351    if (usart_ll_mbox_id != NULL) {
352        void* d = (void*)1;
353        osMessageQueuePut(usart_ll_mbox_id, &d, 0, 0);
354    }
355}
356
357/**
358 * \brief           UART DMA stream/channel handler
359 */
360void
361LWCELL_USART_DMA_RX_IRQHANDLER(void) {
362    LWCELL_USART_DMA_RX_CLEAR_TC;
363    LWCELL_USART_DMA_RX_CLEAR_HT;
364
365    if (usart_ll_mbox_id != NULL) {
366        void* d = (void*)1;
367        osMessageQueuePut(usart_ll_mbox_id, &d, 0, 0);
368    }
369}
370
371#endif /* !__DOXYGEN__ */
Example: System functions for WIN32
Actual header implementation of system functions for WIN32
 1/**
 2 * \file            lwcell_sys_port.h
 3 * \brief           WIN32 based system file implementation
 4 */
 5
 6/*
 7 * Copyright (c) 2023 Tilen MAJERLE
 8 *
 9 * Permission is hereby granted, free of charge, to any person
10 * obtaining a copy of this software and associated documentation
11 * files (the "Software"), to deal in the Software without restriction,
12 * including without limitation the rights to use, copy, modify, merge,
13 * publish, distribute, sublicense, and/or sell copies of the Software,
14 * and to permit persons to whom the Software is furnished to do so,
15 * subject to the following conditions:
16 *
17 * The above copyright notice and this permission notice shall be
18 * included in all copies or substantial portions of the Software.
19 *
20 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
21 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
22 * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE
23 * AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
24 * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
25 * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
26 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
27 * OTHER DEALINGS IN THE SOFTWARE.
28 *
29 * This file is part of LwCELL - Lightweight cellular modem AT library.
30 *
31 * Author:          Tilen MAJERLE <tilen@majerle.eu>
32 * Version:         v0.1.1
33 */
34#ifndef LWCELL_SYSTEM_PORT_HDR_H
35#define LWCELL_SYSTEM_PORT_HDR_H
36
37#include <stdint.h>
38#include <stdlib.h>
39#include "lwcell/lwcell_opt.h"
40#include "windows.h"
41
42#ifdef __cplusplus
43extern "C" {
44#endif /* __cplusplus */
45
46#if LWCELL_CFG_OS && !__DOXYGEN__
47
48typedef HANDLE lwcell_sys_mutex_t;
49typedef HANDLE lwcell_sys_sem_t;
50typedef HANDLE lwcell_sys_mbox_t;
51typedef HANDLE lwcell_sys_thread_t;
52typedef int lwcell_sys_thread_prio_t;
53
54#define LWCELL_SYS_MUTEX_NULL  ((HANDLE)0)
55#define LWCELL_SYS_SEM_NULL    ((HANDLE)0)
56#define LWCELL_SYS_MBOX_NULL   ((HANDLE)0)
57#define LWCELL_SYS_TIMEOUT     (INFINITE)
58#define LWCELL_SYS_THREAD_PRIO (0)
59#define LWCELL_SYS_THREAD_SS   (4096)
60
61#endif /* LWCELL_CFG_OS && !__DOXYGEN__ */
62
63#ifdef __cplusplus
64}
65#endif /* __cplusplus */
66
67#endif /* LWCELL_SYSTEM_PORT_HDR_H */
Actual implementation of system functions for WIN32
  1/**
  2 * \file            lwcell_sys_win32.c
  3 * \brief           System dependant functions for WIN32
  4 */
  5
  6/*
  7 * Copyright (c) 2023 Tilen MAJERLE
  8 *
  9 * Permission is hereby granted, free of charge, to any person
 10 * obtaining a copy of this software and associated documentation
 11 * files (the "Software"), to deal in the Software without restriction,
 12 * including without limitation the rights to use, copy, modify, merge,
 13 * publish, distribute, sublicense, and/or sell copies of the Software,
 14 * and to permit persons to whom the Software is furnished to do so,
 15 * subject to the following conditions:
 16 *
 17 * The above copyright notice and this permission notice shall be
 18 * included in all copies or substantial portions of the Software.
 19 *
 20 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 21 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
 22 * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE
 23 * AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
 24 * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
 25 * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 26 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
 27 * OTHER DEALINGS IN THE SOFTWARE.
 28 *
 29 * This file is part of LwCELL - Lightweight cellular modem AT library.
 30 *
 31 * Author:          Tilen MAJERLE <tilen@majerle.eu>
 32 * Version:         v0.1.1
 33 */
 34#include <stdlib.h>
 35#include <string.h>
 36#include "lwcell/lwcell_private.h"
 37#include "system/lwcell_sys.h"
 38
 39#if !__DOXYGEN__
 40
 41/**
 42 * \brief           Custom message queue implementation for WIN32
 43 */
 44typedef struct {
 45    lwcell_sys_sem_t sem_not_empty; /*!< Semaphore indicates not empty */
 46    lwcell_sys_sem_t sem_not_full;  /*!< Semaphore indicates not full */
 47    lwcell_sys_sem_t sem;           /*!< Semaphore to lock access */
 48    size_t in, out, size;
 49    void* entries[1];
 50} win32_mbox_t;
 51
 52static LARGE_INTEGER freq, sys_start_time;
 53static lwcell_sys_mutex_t sys_mutex; /* Mutex ID for main protection */
 54
 55static uint8_t
 56mbox_is_full(win32_mbox_t* m) {
 57    size_t size = 0;
 58    if (m->in > m->out) {
 59        size = (m->in - m->out);
 60    } else if (m->out > m->in) {
 61        size = m->size - m->out + m->in;
 62    }
 63    return size == m->size - 1;
 64}
 65
 66static uint8_t
 67mbox_is_empty(win32_mbox_t* m) {
 68    return m->in == m->out;
 69}
 70
 71static uint32_t
 72osKernelSysTick(void) {
 73    LONGLONG ret;
 74    LARGE_INTEGER now;
 75
 76    QueryPerformanceFrequency(&freq); /* Get frequency */
 77    QueryPerformanceCounter(&now);    /* Get current time */
 78    ret = now.QuadPart - sys_start_time.QuadPart;
 79    return (uint32_t)(((ret)*1000) / freq.QuadPart);
 80}
 81
 82uint8_t
 83lwcell_sys_init(void) {
 84    QueryPerformanceFrequency(&freq);
 85    QueryPerformanceCounter(&sys_start_time);
 86
 87    lwcell_sys_mutex_create(&sys_mutex);
 88    return 1;
 89}
 90
 91uint32_t
 92lwcell_sys_now(void) {
 93    return osKernelSysTick();
 94}
 95
 96uint8_t
 97lwcell_sys_protect(void) {
 98    lwcell_sys_mutex_lock(&sys_mutex);
 99    return 1;
100}
101
102uint8_t
103lwcell_sys_unprotect(void) {
104    lwcell_sys_mutex_unlock(&sys_mutex);
105    return 1;
106}
107
108uint8_t
109lwcell_sys_mutex_create(lwcell_sys_mutex_t* p) {
110    *p = CreateMutex(NULL, FALSE, NULL);
111    return *p != NULL;
112}
113
114uint8_t
115lwcell_sys_mutex_delete(lwcell_sys_mutex_t* p) {
116    return CloseHandle(*p);
117}
118
119uint8_t
120lwcell_sys_mutex_lock(lwcell_sys_mutex_t* p) {
121    DWORD ret;
122    ret = WaitForSingleObject(*p, INFINITE);
123    if (ret != WAIT_OBJECT_0) {
124        return 0;
125    }
126    return 1;
127}
128
129uint8_t
130lwcell_sys_mutex_unlock(lwcell_sys_mutex_t* p) {
131    return (uint8_t)ReleaseMutex(*p);
132}
133
134uint8_t
135lwcell_sys_mutex_isvalid(lwcell_sys_mutex_t* p) {
136    return p != NULL && *p != NULL;
137}
138
139uint8_t
140lwcell_sys_mutex_invalid(lwcell_sys_mutex_t* p) {
141    *p = LWCELL_SYS_MUTEX_NULL;
142    return 1;
143}
144
145uint8_t
146lwcell_sys_sem_create(lwcell_sys_sem_t* p, uint8_t cnt) {
147    HANDLE h;
148    h = CreateSemaphore(NULL, !!cnt, 1, NULL);
149    *p = h;
150    return *p != NULL;
151}
152
153uint8_t
154lwcell_sys_sem_delete(lwcell_sys_sem_t* p) {
155    return CloseHandle(*p);
156}
157
158uint32_t
159lwcell_sys_sem_wait(lwcell_sys_sem_t* p, uint32_t timeout) {
160    DWORD ret;
161
162    if (timeout == 0) {
163        ret = WaitForSingleObject(*p, INFINITE);
164        return 1;
165    } else {
166        ret = WaitForSingleObject(*p, timeout);
167        if (ret == WAIT_OBJECT_0) {
168            return 1;
169        } else {
170            return LWCELL_SYS_TIMEOUT;
171        }
172    }
173}
174
175uint8_t
176lwcell_sys_sem_release(lwcell_sys_sem_t* p) {
177    return ReleaseSemaphore(*p, 1, NULL);
178}
179
180uint8_t
181lwcell_sys_sem_isvalid(lwcell_sys_sem_t* p) {
182    return p != NULL && *p != NULL;
183}
184
185uint8_t
186lwcell_sys_sem_invalid(lwcell_sys_sem_t* p) {
187    *p = LWCELL_SYS_SEM_NULL;
188    return 1;
189}
190
191uint8_t
192lwcell_sys_mbox_create(lwcell_sys_mbox_t* b, size_t size) {
193    win32_mbox_t* mbox;
194
195    *b = NULL;
196
197    mbox = malloc(sizeof(*mbox) + size * sizeof(void*));
198    if (mbox != NULL) {
199        memset(mbox, 0x00, sizeof(*mbox));
200        mbox->size = size + 1; /* Set it to 1 more as cyclic buffer has only one less than size */
201        lwcell_sys_sem_create(&mbox->sem, 1);
202        lwcell_sys_sem_create(&mbox->sem_not_empty, 0);
203        lwcell_sys_sem_create(&mbox->sem_not_full, 0);
204        *b = mbox;
205    }
206    return *b != NULL;
207}
208
209uint8_t
210lwcell_sys_mbox_delete(lwcell_sys_mbox_t* b) {
211    win32_mbox_t* mbox = *b;
212    lwcell_sys_sem_delete(&mbox->sem);
213    lwcell_sys_sem_delete(&mbox->sem_not_full);
214    lwcell_sys_sem_delete(&mbox->sem_not_empty);
215    free(mbox);
216    return 1;
217}
218
219uint32_t
220lwcell_sys_mbox_put(lwcell_sys_mbox_t* b, void* m) {
221    win32_mbox_t* mbox = *b;
222    uint32_t time = osKernelSysTick(); /* Get start time */
223
224    lwcell_sys_sem_wait(&mbox->sem, 0); /* Wait for access */
225
226    /*
227     * Since function is blocking until ready to write something to queue,
228     * wait and release the semaphores to allow other threads
229     * to process the queue before we can write new value.
230     */
231    while (mbox_is_full(mbox)) {
232        lwcell_sys_sem_release(&mbox->sem);          /* Release semaphore */
233        lwcell_sys_sem_wait(&mbox->sem_not_full, 0); /* Wait for semaphore indicating not full */
234        lwcell_sys_sem_wait(&mbox->sem, 0);          /* Wait availability again */
235    }
236    mbox->entries[mbox->in] = m;
237    if (++mbox->in >= mbox->size) {
238        mbox->in = 0;
239    }
240    lwcell_sys_sem_release(&mbox->sem_not_empty); /* Signal non-empty state */
241    lwcell_sys_sem_release(&mbox->sem);           /* Release access for other threads */
242    return osKernelSysTick() - time;
243}
244
245uint32_t
246lwcell_sys_mbox_get(lwcell_sys_mbox_t* b, void** m, uint32_t timeout) {
247    win32_mbox_t* mbox = *b;
248    uint32_t time;
249
250    time = osKernelSysTick();
251
252    /* Get exclusive access to message queue */
253    if (lwcell_sys_sem_wait(&mbox->sem, timeout) == LWCELL_SYS_TIMEOUT) {
254        return LWCELL_SYS_TIMEOUT;
255    }
256    while (mbox_is_empty(mbox)) {
257        lwcell_sys_sem_release(&mbox->sem);
258        if (lwcell_sys_sem_wait(&mbox->sem_not_empty, timeout) == LWCELL_SYS_TIMEOUT) {
259            return LWCELL_SYS_TIMEOUT;
260        }
261        lwcell_sys_sem_wait(&mbox->sem, timeout);
262    }
263    *m = mbox->entries[mbox->out];
264    if (++mbox->out >= mbox->size) {
265        mbox->out = 0;
266    }
267    lwcell_sys_sem_release(&mbox->sem_not_full);
268    lwcell_sys_sem_release(&mbox->sem);
269
270    return osKernelSysTick() - time;
271}
272
273uint8_t
274lwcell_sys_mbox_putnow(lwcell_sys_mbox_t* b, void* m) {
275    win32_mbox_t* mbox = *b;
276
277    lwcell_sys_sem_wait(&mbox->sem, 0);
278    if (mbox_is_full(mbox)) {
279        lwcell_sys_sem_release(&mbox->sem);
280        return 0;
281    }
282    mbox->entries[mbox->in] = m;
283    if (mbox->in == mbox->out) {
284        lwcell_sys_sem_release(&mbox->sem_not_empty);
285    }
286    if (++mbox->in >= mbox->size) {
287        mbox->in = 0;
288    }
289    lwcell_sys_sem_release(&mbox->sem);
290    return 1;
291}
292
293uint8_t
294lwcell_sys_mbox_getnow(lwcell_sys_mbox_t* b, void** m) {
295    win32_mbox_t* mbox = *b;
296
297    lwcell_sys_sem_wait(&mbox->sem, 0); /* Wait exclusive access */
298    if (mbox->in == mbox->out) {
299        lwcell_sys_sem_release(&mbox->sem); /* Release access */
300        return 0;
301    }
302
303    *m = mbox->entries[mbox->out];
304    if (++mbox->out >= mbox->size) {
305        mbox->out = 0;
306    }
307    lwcell_sys_sem_release(&mbox->sem_not_full); /* Queue not full anymore */
308    lwcell_sys_sem_release(&mbox->sem);          /* Release semaphore */
309    return 1;
310}
311
312uint8_t
313lwcell_sys_mbox_isvalid(lwcell_sys_mbox_t* b) {
314    return b != NULL && *b != NULL; /* Return status if message box is valid */
315}
316
317uint8_t
318lwcell_sys_mbox_invalid(lwcell_sys_mbox_t* b) {
319    *b = LWCELL_SYS_MBOX_NULL; /* Invalidate message box */
320    return 1;
321}
322
323uint8_t
324lwcell_sys_thread_create(lwcell_sys_thread_t* t, const char* name, lwcell_sys_thread_fn thread_func, void* const arg,
325                        size_t stack_size, lwcell_sys_thread_prio_t prio) {
326    HANDLE h;
327    DWORD id;
328
329    LWCELL_UNUSED(name);
330    LWCELL_UNUSED(stack_size);
331    LWCELL_UNUSED(prio);
332
333    h = CreateThread(0, 0, (LPTHREAD_START_ROUTINE)thread_func, arg, 0, &id);
334    if (t != NULL) {
335        *t = h;
336    }
337    return h != NULL;
338}
339
340uint8_t
341lwcell_sys_thread_terminate(lwcell_sys_thread_t* t) {
342    if (t == NULL) { /* Shall we terminate ourself? */
343        ExitThread(0);
344    } else {
345        /* We have known thread, find handle by looking at ID */
346        TerminateThread(*t, 0);
347    }
348    return 1;
349}
350
351uint8_t
352lwcell_sys_thread_yield(void) {
353    /* Not implemented */
354    return 1;
355}
356
357#endif /* !__DOXYGEN__ */
Example: System functions for CMSIS-OS
Actual header implementation of system functions for CMSIS-OS based operating systems
 1/**
 2 * \file            lwcell_sys_port.h
 3 * \brief           System dependent functions for CMSIS-OS based operating system
 4 */
 5
 6/*
 7 * Copyright (c) 2023 Tilen MAJERLE
 8 *
 9 * Permission is hereby granted, free of charge, to any person
10 * obtaining a copy of this software and associated documentation
11 * files (the "Software"), to deal in the Software without restriction,
12 * including without limitation the rights to use, copy, modify, merge,
13 * publish, distribute, sublicense, and/or sell copies of the Software,
14 * and to permit persons to whom the Software is furnished to do so,
15 * subject to the following conditions:
16 *
17 * The above copyright notice and this permission notice shall be
18 * included in all copies or substantial portions of the Software.
19 *
20 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
21 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
22 * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE
23 * AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
24 * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
25 * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
26 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
27 * OTHER DEALINGS IN THE SOFTWARE.
28 *
29 * This file is part of LwCELL - Lightweight cellular modem AT library.
30 *
31 * Author:          Tilen MAJERLE <tilen@majerle.eu>
32 * Version:         v0.1.1
33 */
34#ifndef LWCELL_SYSTEM_PORT_HDR_H
35#define LWCELL_SYSTEM_PORT_HDR_H
36
37#include <stdint.h>
38#include <stdlib.h>
39#include "cmsis_os.h"
40#include "lwcell/lwcell_opt.h"
41
42#ifdef __cplusplus
43extern "C" {
44#endif /* __cplusplus */
45
46#if LWCELL_CFG_OS && !__DOXYGEN__
47
48typedef osMutexId_t lwcell_sys_mutex_t;
49typedef osSemaphoreId_t lwcell_sys_sem_t;
50typedef osMessageQueueId_t lwcell_sys_mbox_t;
51typedef osThreadId_t lwcell_sys_thread_t;
52typedef osPriority_t lwcell_sys_thread_prio_t;
53
54#define LWCELL_SYS_MUTEX_NULL  ((lwcell_sys_mutex_t)0)
55#define LWCELL_SYS_SEM_NULL    ((lwcell_sys_sem_t)0)
56#define LWCELL_SYS_MBOX_NULL   ((lwcell_sys_mbox_t)0)
57#define LWCELL_SYS_TIMEOUT     ((uint32_t)osWaitForever)
58#define LWCELL_SYS_THREAD_PRIO (osPriorityNormal)
59#define LWCELL_SYS_THREAD_SS   (512)
60
61#endif /* LWCELL_CFG_OS && !__DOXYGEN__ */
62
63#ifdef __cplusplus
64}
65#endif /* __cplusplus */
66
67#endif /* LWCELL_SYSTEM_PORT_HDR_H */
Actual implementation of system functions for CMSIS-OS based operating systems
  1/**
  2 * \file            lwcell_sys_cmsis_os.c
  3 * \brief           System dependent functions for CMSIS-OS based operating system
  4 */
  5
  6/*
  7 * Copyright (c) 2023 Tilen MAJERLE
  8 *
  9 * Permission is hereby granted, free of charge, to any person
 10 * obtaining a copy of this software and associated documentation
 11 * files (the "Software"), to deal in the Software without restriction,
 12 * including without limitation the rights to use, copy, modify, merge,
 13 * publish, distribute, sublicense, and/or sell copies of the Software,
 14 * and to permit persons to whom the Software is furnished to do so,
 15 * subject to the following conditions:
 16 *
 17 * The above copyright notice and this permission notice shall be
 18 * included in all copies or substantial portions of the Software.
 19 *
 20 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 21 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
 22 * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE
 23 * AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
 24 * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
 25 * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 26 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
 27 * OTHER DEALINGS IN THE SOFTWARE.
 28 *
 29 * This file is part of LwCELL - Lightweight cellular modem AT library.
 30 *
 31 * Author:          Tilen MAJERLE <tilen@majerle.eu>
 32 * Version:         v0.1.1
 33 */
 34#include "cmsis_os.h"
 35#include "system/lwcell_sys.h"
 36
 37#if !__DOXYGEN__
 38
 39static osMutexId_t sys_mutex;
 40
 41uint8_t
 42lwcell_sys_init(void) {
 43    lwcell_sys_mutex_create(&sys_mutex);
 44    return 1;
 45}
 46
 47uint32_t
 48lwcell_sys_now(void) {
 49    return osKernelGetTickCount();
 50}
 51
 52uint8_t
 53lwcell_sys_protect(void) {
 54    lwcell_sys_mutex_lock(&sys_mutex);
 55    return 1;
 56}
 57
 58uint8_t
 59lwcell_sys_unprotect(void) {
 60    lwcell_sys_mutex_unlock(&sys_mutex);
 61    return 1;
 62}
 63
 64uint8_t
 65lwcell_sys_mutex_create(lwcell_sys_mutex_t* p) {
 66    const osMutexAttr_t attr = {
 67        .attr_bits = osMutexRecursive,
 68        .name = "lwcell_mutex",
 69    };
 70    return (*p = osMutexNew(&attr)) != NULL;
 71}
 72
 73uint8_t
 74lwcell_sys_mutex_delete(lwcell_sys_mutex_t* p) {
 75    return osMutexDelete(*p) == osOK;
 76}
 77
 78uint8_t
 79lwcell_sys_mutex_lock(lwcell_sys_mutex_t* p) {
 80    return osMutexAcquire(*p, osWaitForever) == osOK;
 81}
 82
 83uint8_t
 84lwcell_sys_mutex_unlock(lwcell_sys_mutex_t* p) {
 85    return osMutexRelease(*p) == osOK;
 86}
 87
 88uint8_t
 89lwcell_sys_mutex_isvalid(lwcell_sys_mutex_t* p) {
 90    return p != NULL && *p != NULL;
 91}
 92
 93uint8_t
 94lwcell_sys_mutex_invalid(lwcell_sys_mutex_t* p) {
 95    *p = LWCELL_SYS_MUTEX_NULL;
 96    return 1;
 97}
 98
 99uint8_t
100lwcell_sys_sem_create(lwcell_sys_sem_t* p, uint8_t cnt) {
101    const osSemaphoreAttr_t attr = {
102        .name = "lwcell_sem",
103    };
104    return (*p = osSemaphoreNew(1, cnt > 0 ? 1 : 0, &attr)) != NULL;
105}
106
107uint8_t
108lwcell_sys_sem_delete(lwcell_sys_sem_t* p) {
109    return osSemaphoreDelete(*p) == osOK;
110}
111
112uint32_t
113lwcell_sys_sem_wait(lwcell_sys_sem_t* p, uint32_t timeout) {
114    uint32_t tick = osKernelSysTick();
115    return (osSemaphoreAcquire(*p, timeout == 0 ? osWaitForever : timeout) == osOK) ? (osKernelSysTick() - tick)
116                                                                                    : LWCELL_SYS_TIMEOUT;
117}
118
119uint8_t
120lwcell_sys_sem_release(lwcell_sys_sem_t* p) {
121    return osSemaphoreRelease(*p) == osOK;
122}
123
124uint8_t
125lwcell_sys_sem_isvalid(lwcell_sys_sem_t* p) {
126    return p != NULL && *p != NULL;
127}
128
129uint8_t
130lwcell_sys_sem_invalid(lwcell_sys_sem_t* p) {
131    *p = LWCELL_SYS_SEM_NULL;
132    return 1;
133}
134
135uint8_t
136lwcell_sys_mbox_create(lwcell_sys_mbox_t* b, size_t size) {
137    const osMessageQueueAttr_t attr = {
138        .name = "lwcell_mbox",
139    };
140    return (*b = osMessageQueueNew(size, sizeof(void*), &attr)) != NULL;
141}
142
143uint8_t
144lwcell_sys_mbox_delete(lwcell_sys_mbox_t* b) {
145    if (osMessageQueueGetCount(*b) > 0) {
146        return 0;
147    }
148    return osMessageQueueDelete(*b) == osOK;
149}
150
151uint32_t
152lwcell_sys_mbox_put(lwcell_sys_mbox_t* b, void* m) {
153    uint32_t tick = osKernelSysTick();
154    return osMessageQueuePut(*b, &m, 0, osWaitForever) == osOK ? (osKernelSysTick() - tick) : LWCELL_SYS_TIMEOUT;
155}
156
157uint32_t
158lwcell_sys_mbox_get(lwcell_sys_mbox_t* b, void** m, uint32_t timeout) {
159    uint32_t tick = osKernelSysTick();
160    return osMessageQueueGet(*b, m, NULL, timeout == 0 ? osWaitForever : timeout) == osOK ? (osKernelSysTick() - tick)
161                                                                                          : LWCELL_SYS_TIMEOUT;
162}
163
164uint8_t
165lwcell_sys_mbox_putnow(lwcell_sys_mbox_t* b, void* m) {
166    return osMessageQueuePut(*b, &m, 0, 0) == osOK;
167}
168
169uint8_t
170lwcell_sys_mbox_getnow(lwcell_sys_mbox_t* b, void** m) {
171    return osMessageQueueGet(*b, m, NULL, 0) == osOK;
172}
173
174uint8_t
175lwcell_sys_mbox_isvalid(lwcell_sys_mbox_t* b) {
176    return b != NULL && *b != NULL;
177}
178
179uint8_t
180lwcell_sys_mbox_invalid(lwcell_sys_mbox_t* b) {
181    *b = LWCELL_SYS_MBOX_NULL;
182    return 1;
183}
184
185uint8_t
186lwcell_sys_thread_create(lwcell_sys_thread_t* t, const char* name, lwcell_sys_thread_fn thread_func, void* const arg,
187                        size_t stack_size, lwcell_sys_thread_prio_t prio) {
188    lwcell_sys_thread_t id;
189    const osThreadAttr_t thread_attr = {.name = (char*)name,
190                                        .priority = (osPriority)prio,
191                                        .stack_size = stack_size > 0 ? stack_size : LWCELL_SYS_THREAD_SS};
192
193    id = osThreadNew(thread_func, arg, &thread_attr);
194    if (t != NULL) {
195        *t = id;
196    }
197    return id != NULL;
198}
199
200uint8_t
201lwcell_sys_thread_terminate(lwcell_sys_thread_t* t) {
202    if (t != NULL) {
203        osThreadTerminate(*t);
204    } else {
205        osThreadExit();
206    }
207    return 1;
208}
209
210uint8_t
211lwcell_sys_thread_yield(void) {
212    osThreadYield();
213    return 1;
214}
215
216#endif /* !__DOXYGEN__ */

API reference

List of all the modules:

Configuration

This is the default configuration of the middleware. When any of the settings shall be modified, it shall be done in dedicated application config lwcell_opts.h file.

Note

Check Getting started for guidelines on how to create and use configuration file.

group LWCELL_OPT

LwCELL options.

Defines

LWCELL_CFG_OS

Enables 1 or disables 0 operating system support for GSM library.

Note

Value must be set to 1 in the current revision

Note

Check OS configuration group for more configuration related to operating system

LWCELL_CFG_MEM_CUSTOM

Enables 1 or disables 0 custom memory management functions.

When set to 1, Memory manager block must be provided manually. This includes implementation of functions lwcell_mem_malloc, lwcell_mem_calloc, lwcell_mem_realloc and lwcell_mem_free

Note

Function declaration follows standard C functions malloc, calloc, realloc, free. Declaration is available in lwcell/lwcell_mem.h file. Include this file to final implementation file

Note

When implementing custom memory allocation, it is necessary to take care of multiple threads accessing same resource for custom allocator

LWCELL_CFG_MEM_ALIGNMENT

Memory alignment for dynamic memory allocations.

Note

Some CPUs can work faster if memory is aligned, usually to 4 or 8 bytes. To speed up this possibilities, you can set memory alignment and library will try to allocate memory on aligned boundaries.

Note

Some CPUs such ARM Cortex-M0 don’t support unaligned memory access. This CPUs must have set correct memory alignment value.

Note

This value must be power of 2

LWCELL_CFG_USE_API_FUNC_EVT

Enables 1 or disables 0 callback function and custom parameter for API functions.

When enabled, 2 additional parameters are available in API functions. When command is executed, callback function with its parameter could be called when not set to NULL.

LWCELL_CFG_AT_PORT_BAUDRATE

Default baudrate used for AT port.

Note

Later, user may call API function to change to desired baudrate if necessary

LWCELL_CFG_RCV_BUFF_SIZE

Buffer size for received data waiting to be processed.

Note

When server mode is active and a lot of connections are in queue this should be set high otherwise your buffer may overflow

Note

Buffer size also depends on TX user driver if it uses DMA or blocking mode In case of DMA (CPU can work other tasks), buffer may be smaller as CPU will have more time to process all the incoming bytes

Note

This parameter has no meaning when LWCELL_CFG_INPUT_USE_PROCESS is enabled

LWCELL_CFG_RESET_ON_INIT

Enables 1 or disables 0 reset sequence after lwcell_init call.

Note

When this functionality is disabled, user must manually call lwcell_reset to send reset sequence to GSM device.

LWCELL_CFG_RESET_ON_DEVICE_PRESENT

Enables 1 or disables 0 reset sequence after lwcell_device_set_present call.

Note

When this functionality is disabled, user must manually call lwcell_reset to send reset sequence to GSM device.

LWCELL_CFG_RESET_DELAY_DEFAULT

Default delay (milliseconds unit) before sending first AT command on reset sequence.

LWCELL_CFG_RESET_DELAY_AFTER

Default delay (milliseconds unit) after reset sequence.

LWCELL_CFG_KEEP_ALIVE

Enables 1 or disables 0 periodic keep-alive events to registered callbacks.

LWCELL_CFG_KEEP_ALIVE_TIMEOUT

Timeout periodic time to trigger keep alive events to registered callbacks.

Feature must be enabled with LWCELL_CFG_KEEP_ALIVE

LWCELL_CFG_CONN_POLL_INTERVAL

Poll interval for connections in units of milliseconds.

Value indicates interval time to call poll event on active connections.

Note

Single poll interval applies for all connections

Connection settings.

Defines

LWCELL_CFG_MAX_CONNS

Maximal number of connections AT software can support on GSM device.

LWCELL_CFG_CONN_MAX_DATA_LEN

Maximal number of bytes we can send at single command to GSM.

Note

Value can not exceed 1460 bytes or no data will be ever send

Note

This is limitation of GSM AT commands and on systems where RAM is not an issue, it should be set to maximal value (1460) to optimize data transfer speed performance

LWCELL_CFG_CONN_MIN_DATA_LEN

Minimal buffer in bytes for connection receive allocation.

Allocation will always start with (up to) LWCELL_CFG_CONN_MAX_DATA_LEN and will continue with trial down to this setting up until allocating is successful.

LWCELL_CFG_MAX_SEND_RETRIES

Set number of retries for send data command.

Sometimes it may happen that AT+SEND command fails due to different problems. Trying to send the same data multiple times can raise chances we are successful.

Debugging configurations.

Defines

LWCELL_CFG_DBG

Set global debug support.

Possible values are LWCELL_DBG_ON or LWCELL_DBG_OFF

Note

Set to LWCELL_DBG_OFF to globally disable all debugs

LWCELL_CFG_DBG_OUT(fmt, ...)

Debugging output function.

Called with format and optional parameters for printf style debug

LWCELL_CFG_DBG_LVL_MIN

Minimal debug level.

Check LWCELL_DBG_LVL for possible values

LWCELL_CFG_DBG_TYPES_ON

Enabled debug types.

When debug is globally enabled with LWCELL_CFG_DBG parameter, user must enable debug types such as TRACE or STATE messages.

Check LWCELL_DBG_TYPE for possible options. Separate values with bitwise OR operator

LWCELL_CFG_DBG_INIT

Set debug level for init function.

Possible values are LWCELL_DBG_ON or LWCELL_DBG_OFF

LWCELL_CFG_DBG_MEM

Set debug level for memory manager.

Possible values are LWCELL_DBG_ON or LWCELL_DBG_OFF

LWCELL_CFG_DBG_INPUT

Set debug level for input module.

Possible values are LWCELL_DBG_ON or LWCELL_DBG_OFF

LWCELL_CFG_DBG_THREAD

Set debug level for GSM threads.

Possible values are LWCELL_DBG_ON or LWCELL_DBG_OFF

LWCELL_CFG_DBG_ASSERT

Set debug level for asserting of input variables.

Possible values are LWCELL_DBG_ON or LWCELL_DBG_OFF

LWCELL_CFG_DBG_IPD

Set debug level for incoming data received from device.

Possible values are LWCELL_DBG_ON or LWCELL_DBG_OFF

LWCELL_CFG_DBG_PBUF

Set debug level for packet buffer manager.

Possible values are LWCELL_DBG_ON or LWCELL_DBG_OFF

LWCELL_CFG_DBG_CONN

Set debug level for connections.

Possible values are LWCELL_DBG_ON or LWCELL_DBG_OFF

LWCELL_CFG_DBG_VAR

Set debug level for dynamic variable allocations.

Possible values are LWCELL_DBG_ON or LWCELL_DBG_OFF

LWCELL_CFG_DBG_NETCONN

Set debug level for netconn sequential API.

Possible values are LWCELL_DBG_ON or LWCELL_DBG_OFF

LWCELL_CFG_AT_ECHO

Enables 1 or disables 0 echo mode on AT commands sent to GSM device.

Note

This mode is useful when debugging GSM communication

Operating system dependant configuration.

Defines

LWCELL_CFG_THREAD_PRODUCER_MBOX_SIZE

Set number of message queue entries for procuder thread.

Message queue is used for storing memory address to command data

LWCELL_CFG_THREAD_PROCESS_MBOX_SIZE

Set number of message queue entries for processing thread.

Message queue is used to notify processing thread about new received data on AT port

LWCELL_CFG_INPUT_USE_PROCESS

Enables 1 or disables 0 direct support for processing input data.

When this mode is enabled, no overhead is included for copying data to receive buffer because bytes are processed directly.

Note

This mode can only be used when LWCELL_CFG_OS is enabled

Note

When using this mode, separate thread must be dedicated only for reading data on AT port

Note

Best case for using this mode is if DMA receive is supported by host device

LWCELL_THREAD_PRODUCER_HOOK()

Producer thread hook, called each time thread wakes-up and does the processing.

It can be used to check if thread is alive.

LWCELL_THREAD_PROCESS_HOOK()

Process thread hook, called each time thread wakes-up and does the processing.

It can be used to check if thread is alive.

LWCELL_CFG_THREADX_CUSTOM_MEM_BYTE_POOL

Enables 1 or disables 0 custom memory byte pool extension for ThreadX port.

When enabled, user must manually set byte pool at run-time, before lwcell_init is called

LWCELL_CFG_THREADX_IDLE_THREAD_EXTENSION

Enables 1 or disables 0 idle thread extensions feature of ThreadX.

When enabled, user must manually configure idle thread and setup additional thread handle extension fields. By default ThreadX doesn’t support self-thread cleanup when thread memory is dynamically allocated & thread terminated, hence another thread is mandatory to do the cleanup process instead.

This configuration does not create idle-thread, rather only sets additional TX_THREAD fields, indicating thread handle and thread stack are dynamically allocated.

Have a look at System-ThreadX port for implementation

Configuration of specific modules.

Defines

LWCELL_CFG_NETWORK

Enables 1 or disables 0 network functionality used for TCP/IP communication.

Network must be enabled to use all GPRS/LTE functions such as connection API, FTP, HTTP, etc.

LWCELL_CFG_NETWORK_IGNORE_CGACT_RESULT

Ignores 1 or not 0 result from AT+CGACT command.

Note

This may be used for data-only SIM cards where command might fail when trying to attach to network for data transfer

LWCELL_CFG_CONN

Enables 1 or disables 0 connection API.

Note

LWCELL_CFG_NETWORK must be enabled to use connection feature

LWCELL_CFG_SMS

Enables 1 or disables 0 SMS API.

LWCELL_CFG_CALL

Enables 1 or disables 0 call API.

LWCELL_CFG_PHONEBOOK

Enables 1 or disables 0 phonebook API.

LWCELL_CFG_HTTP

Enables 1 or disables 0 HTTP API.

Note

LWCELL_CFG_NETWORK must be enabled to use connection feature

LWCELL_CFG_FTP

Enables 1 or disables 0 FTP API.

Note

LWCELL_CFG_NETWORK must be enabled to use connection feature

LWCELL_CFG_PING

Enables 1 or disables 0 PING API.

Note

LWCELL_CFG_NETWORK must be enabled to use connection feature

LWCELL_CFG_USSD

Enables 1 or disables 0 USSD API.

Configuration of netconn API module.

Defines

LWCELL_CFG_NETCONN

Enables 1 or disables 0 NETCONN sequential API support for OS systems.

See also

LWCELL_CFG_OS

Note

To use this feature, OS support is mandatory.

LWCELL_CFG_NETCONN_RECEIVE_TIMEOUT

Enables 1 or disables 0 receive timeout feature.

When this option is enabled, user will get an option to set timeout value for receive data on netconn, before function returns timeout error.

Note

Even if this option is enabled, user must still manually set timeout, by default time will be set to 0 which means no timeout.

LWCELL_CFG_NETCONN_ACCEPT_QUEUE_LEN

Accept queue length for new client when netconn server is used.

Defines number of maximal clients waiting in accept queue of server connection

LWCELL_CFG_NETCONN_RECEIVE_QUEUE_LEN

Receive queue length for pbuf entries.

Defines maximal number of pbuf data packet references for receive

Configuration of MQTT and MQTT API client modules.

Defines

LWCELL_CFG_MQTT_MAX_REQUESTS

Maximal number of open MQTT requests at a time.

LWCELL_CFG_MQTT_API_MBOX_SIZE

Size of MQTT API message queue for received messages.

LWCELL_CFG_DBG_MQTT

Set debug level for MQTT client module.

Possible values are LWCELL_DBG_ON or LWCELL_DBG_OFF

LWCELL_CFG_DBG_MQTT_API

Set debug level for MQTT API client module.

Possible values are LWCELL_DBG_ON or LWCELL_DBG_OFF

Standard C library configuration.

Configuration allows you to overwrite default C language function in case of better implementation with hardware (for example DMA for data copy).

Defines

LWCELL_MEMCPY(dst, src, len)

Memory copy function declaration.

User is able to change the memory function, in case hardware supports copy operation, it may implement its own

Function prototype must be similar to:

void *  my_memcpy(void* dst, const void* src, size_t len);
Parameters
  • dst[in] Destination memory start address

  • src[in] Source memory start address

  • len[in] Number of bytes to copy

Returns

Destination memory start address

LWCELL_MEMSET(dst, b, len)

Memory set function declaration.

Function prototype must be similar to:

void *  my_memset(void* dst, int b, size_t len);
Parameters
  • dst[in] Destination memory start address

  • b[in] Value (byte) to set in memory

  • len[in] Number of bytes to set

Returns

Destination memory start address

Platform specific

List of all the modules:

Low-Level functions

Low-level module consists of callback-only functions, which are called by middleware and must be implemented by final application.

Tip

Check Porting guide for actual implementation

group LWCELL_LL

Low-level communication functions.

Typedefs

typedef size_t (*lwcell_ll_send_fn)(const void *data, size_t len)

Function prototype for AT output data.

Param data

[in] Pointer to data to send. This parameter can be set to NULL, indicating to the low-level that (if used) DMA could be started to transmit data to the device

Param len

[in] Number of bytes to send. This parameter can be set to 0 to indicate that internal buffer can be flushed to stream. This is implementation defined and feature might be ignored

Return

Number of bytes sent

typedef uint8_t (*lwcell_ll_reset_fn)(uint8_t state)

Function prototype for hardware reset of GSM device.

Param state

[in] State indicating reset. When set to 1, reset must be active (usually pin active low), or set to 0 when reset is cleared

Return

1 on successful action, 0 otherwise

Functions

lwcellr_t lwcell_ll_init(lwcell_ll_t *ll)

Callback function called from initialization process.

Note

This function may be called multiple times if AT baudrate is changed from application. It is important that every configuration except AT baudrate is configured only once!

Note

This function may be called from different threads in GSM stack when using OS. When LWCELL_CFG_INPUT_USE_PROCESS is set to 1, this function may be called from user UART thread.

Parameters

ll[inout] Pointer to lwcell_ll_t structure to fill data for communication functions

Returns

lwcellOK on success, member of lwcellr_t enumeration otherwise

lwcellr_t lwcell_ll_deinit(lwcell_ll_t *ll)

Callback function to de-init low-level communication part.

Parameters

ll[inout] Pointer to lwcell_ll_t structure to fill data for communication functions

Returns

lwcellOK on success, member of lwcellr_t enumeration otherwise

struct lwcell_ll_t
#include <lwcell_types.h>

Low level user specific functions.

Public Members

lwcell_ll_send_fn send_fn

Callback function to transmit data

lwcell_ll_reset_fn reset_fn

Reset callback function

uint32_t baudrate

UART baudrate value

struct lwcell_ll_t::[anonymous] uart

UART communication parameters

System functions

System functions are bridge between operating system system calls and middleware system calls. Middleware is tightly coupled with operating system features hence it is important to include OS features directly.

It includes support for:

  • Thread management, to start/stop threads

  • Mutex management for recursive mutexes

  • Semaphore management for binary-only semaphores

  • Message queues for thread-safe data exchange between threads

  • Core system protection for mutual exclusion to access shared resources

Tip

Check Porting guide for actual implementation guidelines.

group LWCELL_SYS

System based function for OS management, timings, etc.

Main

uint8_t lwcell_sys_init(void)

Init system dependant parameters.

After this function is called, all other system functions must be fully ready.

Returns

1 on success, 0 otherwise

uint32_t lwcell_sys_now(void)

Get current time in units of milliseconds.

Returns

Current time in units of milliseconds

uint8_t lwcell_sys_protect(void)

Protect middleware core.

Stack protection must support recursive mode. This function may be called multiple times, even if access has been granted before.

Note

Most operating systems support recursive mutexes.

Returns

1 on success, 0 otherwise

uint8_t lwcell_sys_unprotect(void)

Unprotect middleware core.

This function must follow number of calls of lwcell_sys_protect and unlock access only when counter reached back zero.

Note

Most operating systems support recursive mutexes.

Returns

1 on success, 0 otherwise

Mutex

uint8_t lwcell_sys_mutex_create(lwcell_sys_mutex_t *p)

Create new recursive mutex.

Note

Recursive mutex has to be created as it may be locked multiple times before unlocked

Parameters

p[out] Pointer to mutex structure to allocate

Returns

1 on success, 0 otherwise

uint8_t lwcell_sys_mutex_delete(lwcell_sys_mutex_t *p)

Delete recursive mutex from system.

Parameters

p[in] Pointer to mutex structure

Returns

1 on success, 0 otherwise

uint8_t lwcell_sys_mutex_lock(lwcell_sys_mutex_t *p)

Lock recursive mutex, wait forever to lock.

Parameters

p[in] Pointer to mutex structure

Returns

1 on success, 0 otherwise

uint8_t lwcell_sys_mutex_unlock(lwcell_sys_mutex_t *p)

Unlock recursive mutex.

Parameters

p[in] Pointer to mutex structure

Returns

1 on success, 0 otherwise

uint8_t lwcell_sys_mutex_isvalid(lwcell_sys_mutex_t *p)

Check if mutex structure is valid system.

Parameters

p[in] Pointer to mutex structure

Returns

1 on success, 0 otherwise

uint8_t lwcell_sys_mutex_invalid(lwcell_sys_mutex_t *p)

Set recursive mutex structure as invalid.

Parameters

p[in] Pointer to mutex structure

Returns

1 on success, 0 otherwise

Semaphores

uint8_t lwcell_sys_sem_create(lwcell_sys_sem_t *p, uint8_t cnt)

Create a new binary semaphore and set initial state.

Note

Semaphore may only have 1 token available

Parameters
  • p[out] Pointer to semaphore structure to fill with result

  • cnt[in] Count indicating default semaphore state: 0: Take semaphore token immediately 1: Keep token available

Returns

1 on success, 0 otherwise

uint8_t lwcell_sys_sem_delete(lwcell_sys_sem_t *p)

Delete binary semaphore.

Parameters

p[in] Pointer to semaphore structure

Returns

1 on success, 0 otherwise

uint32_t lwcell_sys_sem_wait(lwcell_sys_sem_t *p, uint32_t timeout)

Wait for semaphore to be available.

Parameters
  • p[in] Pointer to semaphore structure

  • timeout[in] Timeout to wait in milliseconds. When 0 is applied, wait forever

Returns

Number of milliseconds waited for semaphore to become available or LWCELL_SYS_TIMEOUT if not available within given time

uint8_t lwcell_sys_sem_release(lwcell_sys_sem_t *p)

Release semaphore.

Parameters

p[in] Pointer to semaphore structure

Returns

1 on success, 0 otherwise

uint8_t lwcell_sys_sem_isvalid(lwcell_sys_sem_t *p)

Check if semaphore is valid.

Parameters

p[in] Pointer to semaphore structure

Returns

1 on success, 0 otherwise

uint8_t lwcell_sys_sem_invalid(lwcell_sys_sem_t *p)

Invalid semaphore.

Parameters

p[in] Pointer to semaphore structure

Returns

1 on success, 0 otherwise

Message queues

uint8_t lwcell_sys_mbox_create(lwcell_sys_mbox_t *b, size_t size)

Create a new message queue with entry type of void *

Parameters
  • b[out] Pointer to message queue structure

  • size[in] Number of entries for message queue to hold

Returns

1 on success, 0 otherwise

uint8_t lwcell_sys_mbox_delete(lwcell_sys_mbox_t *b)

Delete message queue.

Parameters

b[in] Pointer to message queue structure

Returns

1 on success, 0 otherwise

uint32_t lwcell_sys_mbox_put(lwcell_sys_mbox_t *b, void *m)

Put a new entry to message queue and wait until memory available.

Parameters
  • b[in] Pointer to message queue structure

  • m[in] Pointer to entry to insert to message queue

Returns

Time in units of milliseconds needed to put a message to queue

uint32_t lwcell_sys_mbox_get(lwcell_sys_mbox_t *b, void **m, uint32_t timeout)

Get a new entry from message queue with timeout.

Parameters
  • b[in] Pointer to message queue structure

  • m[in] Pointer to pointer to result to save value from message queue to

  • timeout[in] Maximal timeout to wait for new message. When 0 is applied, wait for unlimited time

Returns

Time in units of milliseconds needed to put a message to queue or LWCELL_SYS_TIMEOUT if it was not successful

uint8_t lwcell_sys_mbox_putnow(lwcell_sys_mbox_t *b, void *m)

Put a new entry to message queue without timeout (now or fail)

Parameters
  • b[in] Pointer to message queue structure

  • m[in] Pointer to message to save to queue

Returns

1 on success, 0 otherwise

uint8_t lwcell_sys_mbox_getnow(lwcell_sys_mbox_t *b, void **m)

Get an entry from message queue immediately.

Parameters
  • b[in] Pointer to message queue structure

  • m[in] Pointer to pointer to result to save value from message queue to

Returns

1 on success, 0 otherwise

uint8_t lwcell_sys_mbox_isvalid(lwcell_sys_mbox_t *b)

Check if message queue is valid.

Parameters

b[in] Pointer to message queue structure

Returns

1 on success, 0 otherwise

uint8_t lwcell_sys_mbox_invalid(lwcell_sys_mbox_t *b)

Invalid message queue.

Parameters

b[in] Pointer to message queue structure

Returns

1 on success, 0 otherwise

Threads

uint8_t lwcell_sys_thread_create(lwcell_sys_thread_t *t, const char *name, lwcell_sys_thread_fn thread_func, void *const arg, size_t stack_size, lwcell_sys_thread_prio_t prio)

Create a new thread.

Parameters
  • t[out] Pointer to thread identifier if create was successful. It may be set to NULL

  • name[in] Name of a new thread

  • thread_func[in] Thread function to use as thread body

  • arg[in] Thread function argument

  • stack_size[in] Size of thread stack in uints of bytes. If set to 0, reserve default stack size

  • prio[in] Thread priority

Returns

1 on success, 0 otherwise

uint8_t lwcell_sys_thread_terminate(lwcell_sys_thread_t *t)

Terminate thread (shut it down and remove)

Parameters

t[in] Pointer to thread handle to terminate. If set to NULL, terminate current thread (thread from where function is called)

Returns

1 on success, 0 otherwise

uint8_t lwcell_sys_thread_yield(void)

Yield current thread.

Returns

1 on success, 0 otherwise

Defines

LWCELL_SYS_MUTEX_NULL

Mutex invalid value.

Value assigned to lwcell_sys_mutex_t type when it is not valid.

LWCELL_SYS_SEM_NULL

Semaphore invalid value.

Value assigned to lwcell_sys_sem_t type when it is not valid.

LWCELL_SYS_MBOX_NULL

Message box invalid value.

Value assigned to lwcell_sys_mbox_t type when it is not valid.

LWCELL_SYS_TIMEOUT

OS timeout value.

Value returned by operating system functions (mutex wait, sem wait, mbox wait) when it returns timeout and does not give valid value to application

LWCELL_SYS_THREAD_PRIO

Default thread priority value used by middleware to start built-in threads.

Threads can well operate with normal (default) priority and do not require any special feature in terms of priority for proper operation.

lwcell_sys_THREAD_SS

Stack size in units of bytes for system threads.

It is used as default stack size for all built-in threads.

Typedefs

typedef void (*lwcell_sys_thread_fn)(void*)

Thread function prototype.

typedef void *lwcell_sys_lwcell_sys_mutex_t

System mutex type.

It is used by middleware as base type of mutex.

typedef void *lwcell_sys_sem_t

System semaphore type.

It is used by middleware as base type of mutex.

typedef void *lwcell_sys_mbox_t

System message queue type.

It is used by middleware as base type of mutex.

typedef void *lwcell_sys_thread_t

System thread ID type.

typedef int lwcell_sys_thread_prio_t

System thread priority type.

It is used as priority type for system function, to start new threads by middleware.

Applications

MQTT Client

MQTT client v3.1.1 implementation, based on callback (non-netconn) connection API.

group LWCELL_APP_MQTT_CLIENT

MQTT client.

Typedefs

typedef struct lwcell_mqtt_client *lwcell_mqtt_client_p

Pointer to lwcell_mqtt_client_t structure.

typedef void (*lwcell_mqtt_evt_fn)(lwcell_mqtt_client_p client, lwcell_mqtt_evt_t *evt)

MQTT event callback function.

Param client

[in] MQTT client

Param evt

[in] MQTT event with type and related data

Enums

enum lwcell_mqtt_qos_t

Quality of service enumeration.

Values:

enumerator LWCELL_MQTT_QOS_AT_MOST_ONCE = 0x00

Delivery is not guaranteed to arrive, but can arrive up to 1 time = non-critical packets where losses are allowed

enumerator LWCELL_MQTT_QOS_AT_LEAST_ONCE = 0x01

Delivery is quaranteed at least once, but it may be delivered multiple times with the same content

enumerator LWCELL_MQTT_QOS_EXACTLY_ONCE = 0x02

Delivery is quaranteed exactly once = very critical packets such as billing informations or similar

enum lwcell_mqtt_state_t

State of MQTT client.

Values:

enumerator LWCELL_MQTT_CONN_DISCONNECTED = 0x00

Connection with server is not established

enumerator LWCELL_MQTT_CONN_CONNECTING

Client is connecting to server

enumerator LWCELL_MQTT_CONN_DISCONNECTING

Client connection is disconnecting from server

enumerator LWCELL_MQTT_CONNECTING

MQTT client is connecting… CONNECT command has been sent to server

enumerator LWCELL_MQTT_CONNECTED

MQTT is fully connected and ready to send data on topics

enum lwcell_mqtt_evt_type_t

MQTT event types.

Values:

enumerator LWCELL_MQTT_EVT_CONNECT

MQTT client connect event

enumerator LWCELL_MQTT_EVT_SUBSCRIBE

MQTT client subscribed to specific topic

enumerator LWCELL_MQTT_EVT_UNSUBSCRIBE

MQTT client unsubscribed from specific topic

enumerator LWCELL_MQTT_EVT_PUBLISH

MQTT client publish message to server event.

Note

When publishing packet with quality of service LWCELL_MQTT_QOS_AT_MOST_ONCE, you may not receive event, even if packet was successfully sent, thus do not rely on this event for packet with qos = LWCELL_MQTT_QOS_AT_MOST_ONCE

enumerator LWCELL_MQTT_EVT_PUBLISH_RECV

MQTT client received a publish message from server

enumerator LWCELL_MQTT_EVT_DISCONNECT

MQTT client disconnected from MQTT server

enumerator LWCELL_MQTT_EVT_KEEP_ALIVE

MQTT keep-alive sent to server and reply received

enum lwcell_mqtt_conn_status_t

List of possible results from MQTT server when executing connect command.

Values:

enumerator LWCELL_MQTT_CONN_STATUS_ACCEPTED = 0x00

Connection accepted and ready to use

enumerator LWCELL_MQTT_CONN_STATUS_REFUSED_PROTOCOL_VERSION = 0x01

Connection Refused, unacceptable protocol version

enumerator LWCELL_MQTT_CONN_STATUS_REFUSED_ID = 0x02

Connection refused, identifier rejected

enumerator LWCELL_MQTT_CONN_STATUS_REFUSED_SERVER = 0x03

Connection refused, server unavailable

enumerator LWCELL_MQTT_CONN_STATUS_REFUSED_USER_PASS = 0x04

Connection refused, bad user name or password

enumerator LWCELL_MQTT_CONN_STATUS_REFUSED_NOT_AUTHORIZED = 0x05

Connection refused, not authorized

enumerator LWCELL_MQTT_CONN_STATUS_TCP_FAILED = 0x100

TCP connection to server was not successful

Functions

lwcell_mqtt_client_p lwcell_mqtt_client_new(size_t tx_buff_len, size_t rx_buff_len)

Allocate a new MQTT client structure.

Parameters
  • tx_buff_len[in] Length of raw data output buffer

  • rx_buff_len[in] Length of raw data input buffer

Returns

Pointer to new allocated MQTT client structure or NULL on failure

void lwcell_mqtt_client_delete(lwcell_mqtt_client_p client)

Delete MQTT client structure.

Note

MQTT client must be disconnected first

Parameters

client[in] MQTT client

lwcellr_t lwcell_mqtt_client_connect(lwcell_mqtt_client_p client, const char *host, lwcell_port_t port, lwcell_mqtt_evt_fn evt_fn, const lwcell_mqtt_client_info_t *info)

Connect to MQTT server.

Note

After TCP connection is established, CONNECT packet is automatically sent to server

Parameters
  • client[in] MQTT client

  • host[in] Host address for server

  • port[in] Host port number

  • evt_fn[in] Callback function for all events on this MQTT client

  • info[in] Information structure for connection

Returns

lwcellOK on success, member of lwcellr_t enumeration otherwise

lwcellr_t lwcell_mqtt_client_disconnect(lwcell_mqtt_client_p client)

Disconnect from MQTT server.

Parameters

client[in] MQTT client

Returns

lwcellOK if request sent to queue or member of lwcellr_t otherwise

uint8_t lwcell_mqtt_client_is_connected(lwcell_mqtt_client_p client)

Test if client is connected to server and accepted to MQTT protocol.

Note

Function will return error if TCP is connected but MQTT not accepted

Parameters

client[in] MQTT client

Returns

1 on success, 0 otherwise

lwcellr_t lwcell_mqtt_client_subscribe(lwcell_mqtt_client_p client, const char *topic, lwcell_mqtt_qos_t qos, void *arg)

Subscribe to MQTT topic.

Parameters
  • client[in] MQTT client

  • topic[in] Topic name to subscribe to

  • qos[in] Quality of service. This parameter can be a value of lwcell_mqtt_qos_t

  • arg[in] User custom argument used in callback

Returns

lwcellOK on success, member of lwcellr_t enumeration otherwise

lwcellr_t lwcell_mqtt_client_unsubscribe(lwcell_mqtt_client_p client, const char *topic, void *arg)

Unsubscribe from MQTT topic.

Parameters
  • client[in] MQTT client

  • topic[in] Topic name to unsubscribe from

  • arg[in] User custom argument used in callback

Returns

lwcellOK on success, member of lwcellr_t enumeration otherwise

lwcellr_t lwcell_mqtt_client_publish(lwcell_mqtt_client_p client, const char *topic, const void *payload, uint16_t len, lwcell_mqtt_qos_t qos, uint8_t retain, void *arg)

Publish a new message on specific topic.

Parameters
  • client[in] MQTT client

  • topic[in] Topic to send message to

  • payload[in] Message data

  • payload_len[in] Length of payload data

  • qos[in] Quality of service. This parameter can be a value of lwcell_mqtt_qos_t enumeration

  • retain[in] Retian parameter value

  • arg[in] User custom argument used in callback

Returns

lwcellOK on success, member of lwcellr_t enumeration otherwise

void *lwcell_mqtt_client_get_arg(lwcell_mqtt_client_p client)

Get user argument on client.

Parameters

client[in] MQTT client handle

Returns

User argument

void lwcell_mqtt_client_set_arg(lwcell_mqtt_client_p client, void *arg)

Set user argument on client.

Parameters
  • client[in] MQTT client handle

  • arg[in] User argument

struct lwcell_mqtt_client_info_t
#include <lwcell_mqtt_client.h>

MQTT client information structure.

Public Members

const char *id

Client unique identifier. It is required and must be set by user

const char *user

Authentication username. Set to NULL if not required

const char *pass

Authentication password, set to NULL if not required

uint16_t keep_alive

Keep-alive parameter in units of seconds. When set to 0, functionality is disabled (not recommended)

const char *will_topic

Will topic

const char *will_message

Will message

lwcell_mqtt_qos_t will_qos

Will topic quality of service

struct lwcell_mqtt_request_t
#include <lwcell_mqtt_client.h>

MQTT request object.

Public Members

uint8_t status

Entry status flag for in use or pending bit

uint16_t packet_id

Packet ID generated by client on publish

void *arg

User defined argument

uint32_t expected_sent_len

Number of total bytes which must be sent on connection before we can say “packet was sent”.

uint32_t timeout_start_time

Timeout start time in units of milliseconds

struct lwcell_mqtt_evt_t
#include <lwcell_mqtt_client.h>

MQTT event structure for callback function.

Public Members

lwcell_mqtt_evt_type_t type

Event type

lwcell_mqtt_conn_status_t status

Connection status with MQTT

struct lwcell_mqtt_evt_t::[anonymous]::[anonymous] connect

Event for connecting to server

uint8_t is_accepted

Status if client was accepted to MQTT prior disconnect event

struct lwcell_mqtt_evt_t::[anonymous]::[anonymous] disconnect

Event for disconnecting from server

void *arg

User argument for callback function

lwcellr_t res

Response status

struct lwcell_mqtt_evt_t::[anonymous]::[anonymous] sub_unsub_scribed

Event for (un)subscribe to/from topics

struct lwcell_mqtt_evt_t::[anonymous]::[anonymous] publish

Published event

const uint8_t *topic

Pointer to topic identifier

size_t topic_len

Length of topic

const void *payload

Topic payload

size_t payload_len

Length of topic payload

uint8_t dup

Duplicate flag if message was sent again

lwcell_mqtt_qos_t qos

Received packet quality of service

struct lwcell_mqtt_evt_t::[anonymous]::[anonymous] publish_recv

Publish received event

union lwcell_mqtt_evt_t::[anonymous] evt

Event data parameters

group LWCELL_APP_MQTT_CLIENT_EVT

Event helper functions.

Connect event

Note

Use these functions on LWCELL_MQTT_EVT_CONNECT event

lwcell_mqtt_client_evt_connect_get_status(client, evt)

Get connection status.

Parameters
  • client[in] MQTT client

  • evt[in] Event handle

Returns

Connection status. Member of lwcell_mqtt_conn_status_t

Disconnect event

Note

Use these functions on LWCELL_MQTT_EVT_DISCONNECT event

lwcell_mqtt_client_evt_disconnect_is_accepted(client, evt)

Check if MQTT client was accepted by server when disconnect event occurred.

Parameters
  • client[in] MQTT client

  • evt[in] Event handle

Returns

1 on success, 0 otherwise

Subscribe/unsubscribe event

Note

Use these functions on LWCELL_MQTT_EVT_SUBSCRIBE or LWCELL_MQTT_EVT_UNSUBSCRIBE events

lwcell_mqtt_client_evt_subscribe_get_argument(client, evt)

Get user argument used on lwcell_mqtt_client_subscribe.

Parameters
  • client[in] MQTT client

  • evt[in] Event handle

Returns

User argument

lwcell_mqtt_client_evt_subscribe_get_result(client, evt)

Get result of subscribe event.

Parameters
  • client[in] MQTT client

  • evt[in] Event handle

Returns

lwcellOK on success, member of lwcellr_t otherwise

lwcell_mqtt_client_evt_unsubscribe_get_argument(client, evt)

Get user argument used on lwcell_mqtt_client_unsubscribe.

Parameters
  • client[in] MQTT client

  • evt[in] Event handle

Returns

User argument

lwcell_mqtt_client_evt_unsubscribe_get_result(client, evt)

Get result of unsubscribe event.

Parameters
  • client[in] MQTT client

  • evt[in] Event handle

Returns

lwcellOK on success, member of lwcellr_t otherwise

Publish receive event

Note

Use these functions on LWCELL_MQTT_EVT_PUBLISH_RECV event

lwcell_mqtt_client_evt_publish_recv_get_topic(client, evt)

Get topic from received publish packet.

Parameters
  • client[in] MQTT client

  • evt[in] Event handle

Returns

Topic name

lwcell_mqtt_client_evt_publish_recv_get_topic_len(client, evt)

Get topic length from received publish packet.

Parameters
  • client[in] MQTT client

  • evt[in] Event handle

Returns

Topic length

lwcell_mqtt_client_evt_publish_recv_get_payload(client, evt)

Get payload from received publish packet.

Parameters
  • client[in] MQTT client

  • evt[in] Event handle

Returns

Packet payload

lwcell_mqtt_client_evt_publish_recv_get_payload_len(client, evt)

Get payload length from received publish packet.

Parameters
  • client[in] MQTT client

  • evt[in] Event handle

Returns

Payload length

lwcell_mqtt_client_evt_publish_recv_is_duplicate(client, evt)

Check if packet is duplicated.

Parameters
  • client[in] MQTT client

  • evt[in] Event handle

Returns

1 if duplicated, 0 otherwise

lwcell_mqtt_client_evt_publish_recv_get_qos(client, evt)

Get received quality of service.

Parameters
  • client[in] MQTT client

  • evt[in] Event handle

Returns

Member of lwcell_mqtt_qos_t enumeration

Publish event

Note

Use these functions on LWCELL_MQTT_EVT_PUBLISH event

lwcell_mqtt_client_evt_publish_get_argument(client, evt)

Get user argument used on lwcell_mqtt_client_publish.

Parameters
  • client[in] MQTT client

  • evt[in] Event handle

Returns

User argument

lwcell_mqtt_client_evt_publish_get_result(client, evt)

Get result of publish event.

Parameters
  • client[in] MQTT client

  • evt[in] Event handle

Returns

lwcellOK on success, member of lwcellr_t otherwise

Defines

lwcell_mqtt_client_evt_get_type(client, evt)

Get MQTT event type.

Parameters
  • client[in] MQTT client

  • evt[in] Event handle

Returns

MQTT Event type, value of lwcell_mqtt_evt_type_t enumeration

MQTT Client API

MQTT Client API provides sequential API built on top of MQTT Client.

MQTT API application example code
  1/*
  2 * MQTT client API example with GSM device.
  3 *
  4 * Once device is connected to network,
  5 * it will try to connect to mosquitto test server and start the MQTT.
  6 *
  7 * If successfully connected, it will publish data to "lwcell_mqtt_topic" topic every x seconds.
  8 *
  9 * To check if data are sent, you can use mqtt-spy PC software to inspect
 10 * test.mosquitto.org server and subscribe to publishing topic
 11 */
 12
 13#include "mqtt_client_api.h"
 14#include "lwcell/apps/lwcell_mqtt_client_api.h"
 15#include "lwcell/lwcell.h"
 16#include "lwcell/lwcell_mem.h"
 17#include "lwcell/lwcell_network_api.h"
 18
 19/**
 20 * \brief           Connection information for MQTT CONNECT packet
 21 */
 22static const lwcell_mqtt_client_info_t mqtt_client_info = {
 23    .keep_alive = 10,
 24
 25    /* Server login data */
 26    .user = "8a215f70-a644-11e8-ac49-e932ed599553",
 27    .pass = "26aa943f702e5e780f015cd048a91e8fb54cca28",
 28
 29    /* Device identifier address */
 30    .id = "2c3573a0-0176-11e9-a056-c5cffe7f75f9",
 31};
 32
 33/**
 34 * \brief           Memory for temporary topic
 35 */
 36static char mqtt_topic_str[256];
 37
 38/**
 39 * \brief           Generate random number and write it to string
 40 * \param[out]      str: Output string with new number
 41 */
 42void
 43generate_random(char* str) {
 44    static uint32_t random_beg = 0x8916;
 45    random_beg = random_beg * 0x00123455 + 0x85654321;
 46    sprintf(str, "%u", (unsigned)((random_beg >> 8) & 0xFFFF));
 47}
 48
 49/**
 50 * \brief           MQTT client API thread
 51 */
 52void
 53mqtt_client_api_thread(void const* arg) {
 54    lwcell_mqtt_client_api_p client;
 55    lwcell_mqtt_conn_status_t conn_status;
 56    lwcell_mqtt_client_api_buf_p buf;
 57    lwcellr_t res;
 58    char random_str[10];
 59
 60    LWCELL_UNUSED(arg);
 61
 62    /* Request network attach */
 63    while (lwcell_network_request_attach() != lwcellOK) {
 64        lwcell_delay(1000);
 65    }
 66
 67    /* Create new MQTT API */
 68    if ((client = lwcell_mqtt_client_api_new(256, 128)) == NULL) {
 69        goto terminate;
 70    }
 71
 72    while (1) {
 73        /* Make a connection */
 74        printf("Joining MQTT server\r\n");
 75
 76        /* Try to join */
 77        conn_status = lwcell_mqtt_client_api_connect(client, "mqtt.mydevices.com", 1883, &mqtt_client_info);
 78        if (conn_status == LWCELL_MQTT_CONN_STATUS_ACCEPTED) {
 79            printf("Connected and accepted!\r\n");
 80            printf("Client is ready to subscribe and publish to new messages\r\n");
 81        } else {
 82            printf("Connect API response: %d\r\n", (int)conn_status);
 83            lwcell_delay(5000);
 84            continue;
 85        }
 86
 87        /* Subscribe to topics */
 88        sprintf(mqtt_topic_str, "v1/%s/things/%s/cmd/#", mqtt_client_info.user, mqtt_client_info.id);
 89        if (lwcell_mqtt_client_api_subscribe(client, mqtt_topic_str, LWCELL_MQTT_QOS_AT_LEAST_ONCE) == lwcellOK) {
 90            printf("Subscribed to topic\r\n");
 91        } else {
 92            printf("Problem subscribing to topic!\r\n");
 93        }
 94
 95        while (1) {
 96            /* Receive MQTT packet with timeout */
 97            if ((res = lwcell_mqtt_client_api_receive(client, &buf, 5000)) == lwcellOK) {
 98                if (buf != NULL) {
 99                    printf("Publish received!\r\n");
100                    printf("Topic: %s, payload: %s\r\n", buf->topic, buf->payload);
101                    lwcell_mqtt_client_api_buf_free(buf);
102                    buf = NULL;
103                }
104            } else if (res == lwcellCLOSED) {
105                printf("MQTT connection closed!\r\n");
106                break;
107            } else if (res == lwcellTIMEOUT) {
108                printf("Timeout on MQTT receive function. Manually publishing.\r\n");
109
110                /* Publish data on channel 1 */
111                generate_random(random_str);
112                sprintf(mqtt_topic_str, "v1/%s/things/%s/data/1", mqtt_client_info.user, mqtt_client_info.id);
113                lwcell_mqtt_client_api_publish(client, mqtt_topic_str, random_str, strlen(random_str),
114                                              LWCELL_MQTT_QOS_AT_LEAST_ONCE, 0);
115            }
116        }
117        goto terminate;
118    }
119
120terminate:
121    lwcell_mqtt_client_api_delete(client);
122    lwcell_network_request_detach();
123    printf("MQTT client thread terminate\r\n");
124    lwcell_sys_thread_terminate(NULL);
125}
group LWCELL_APP_MQTT_CLIENT_API

Sequential, single thread MQTT client API.

Typedefs

typedef struct lwcell_mqtt_client_api_buf *lwcell_mqtt_client_api_buf_p

Pointer to lwcell_mqtt_client_api_buf_t structure.

Functions

lwcell_mqtt_client_api_p lwcell_mqtt_client_api_new(size_t tx_buff_len, size_t rx_buff_len)

Create new MQTT client API.

Parameters
  • tx_buff_len[in] Maximal TX buffer for maximal packet length

  • rx_buff_len[in] Maximal RX buffer

Returns

Client handle on success, NULL otherwise

void lwcell_mqtt_client_api_delete(lwcell_mqtt_client_api_p client)

Delete client from memory.

Parameters

client[in] MQTT API client handle

lwcell_mqtt_conn_status_t lwcell_mqtt_client_api_connect(lwcell_mqtt_client_api_p client, const char *host, lwcell_port_t port, const lwcell_mqtt_client_info_t *info)

Connect to MQTT broker.

Parameters
  • client[in] MQTT API client handle

  • host[in] TCP host

  • port[in] TCP port

  • info[in] MQTT client info

Returns

LWCELL_MQTT_CONN_STATUS_ACCEPTED on success, member of lwcell_mqtt_conn_status_t otherwise

lwcellr_t lwcell_mqtt_client_api_close(lwcell_mqtt_client_api_p client)

Close MQTT connection.

Parameters

client[in] MQTT API client handle

Returns

lwcellOK on success, member of lwcellr_t otherwise

lwcellr_t lwcell_mqtt_client_api_subscribe(lwcell_mqtt_client_api_p client, const char *topic, lwcell_mqtt_qos_t qos)

Subscribe to topic.

Parameters
  • client[in] MQTT API client handle

  • topic[in] Topic to subscribe on

  • qos[in] Quality of service. This parameter can be a value of lwcell_mqtt_qos_t

Returns

lwcellOK on success, member of lwcellr_t otherwise

lwcellr_t lwcell_mqtt_client_api_unsubscribe(lwcell_mqtt_client_api_p client, const char *topic)

Unsubscribe from topic.

Parameters
  • client[in] MQTT API client handle

  • topic[in] Topic to unsubscribe from

Returns

lwcellOK on success, member of lwcellr_t otherwise

lwcellr_t lwcell_mqtt_client_api_publish(lwcell_mqtt_client_api_p client, const char *topic, const void *data, size_t btw, lwcell_mqtt_qos_t qos, uint8_t retain)

Publish new packet to MQTT network.

Parameters
  • client[in] MQTT API client handle

  • topic[in] Topic to publish on

  • data[in] Data to send

  • btw[in] Number of bytes to send for data parameter

  • qos[in] Quality of service. This parameter can be a value of lwcell_mqtt_qos_t

  • retain[in] Set to 1 for retain flag, 0 otherwise

Returns

lwcellOK on success, member of lwcellr_t otherwise

uint8_t lwcell_mqtt_client_api_is_connected(lwcell_mqtt_client_api_p client)

Check if client MQTT connection is active.

Parameters

client[in] MQTT API client handle

Returns

1 on success, 0 otherwise

lwcellr_t lwcell_mqtt_client_api_receive(lwcell_mqtt_client_api_p client, lwcell_mqtt_client_api_buf_p *p, uint32_t timeout)

Receive next packet in specific timeout time.

Note

This function can be called from separate thread than the rest of API function, which allows you to handle receive data separated with custom timeout

Parameters
  • client[in] MQTT API client handle

  • p[in] Pointer to output buffer

  • timeout[in] Maximal time to wait before function returns timeout

Returns

lwcellOK on success, lwcellCLOSED if MQTT is closed, lwcellTIMEOUT on timeout

void lwcell_mqtt_client_api_buf_free(lwcell_mqtt_client_api_buf_p p)

Free buffer memory after usage.

Parameters

p[in] Buffer to free

struct lwcell_mqtt_client_api_buf_t
#include <lwcell_mqtt_client_api.h>

MQTT API RX buffer.

Public Members

char *topic

Topic data

size_t topic_len

Topic length

uint8_t *payload

Payload data

size_t payload_len

Payload length

lwcell_mqtt_qos_t qos

Quality of service

Netconn API

Netconn API is addon on top of existing connection module and allows sending and receiving data with sequential API calls, similar to POSIX socket API.

It can operate in client mode and uses operating system features, such as message queues and semaphore to link non-blocking callback API for connections with sequential API for application thread.

Note

Connection API does not directly allow receiving data with sequential and linear code execution. All is based on connection event system. Netconn adds this functionality as it is implemented on top of regular connection API.

Warning

Netconn API are designed to be called from application threads ONLY. It is not allowed to call any of netconn API functions from within interrupt or callback event functions.

Netconn client
Netconn API client block diagram

Netconn API client block diagram

Above block diagram shows basic architecture of netconn client application. There is always one application thread (in green) which calls netconn API functions to interact with connection API in synchronous mode.

Every netconn connection uses dedicated structure to handle message queue for data received packet buffers. Each time new packet is received (red block, data received event), reference to it is written to message queue of netconn structure, while application thread reads new entries from the same queue to get packets.

Netconn client example
  1#include "netconn_client.h"
  2#include "lwcell/lwcell.h"
  3#include "lwcell/lwcell_network_api.h"
  4
  5#if LWCELL_CFG_NETCONN
  6
  7/**
  8 * \brief           Host and port settings
  9 */
 10#define NETCONN_HOST "example.com"
 11#define NETCONN_PORT 80
 12
 13/**
 14 * \brief           Request header to send on successful connection
 15 */
 16static const char request_header[] = ""
 17                                     "GET / HTTP/1.1\r\n"
 18                                     "Host: " NETCONN_HOST "\r\n"
 19                                     "Connection: close\r\n"
 20                                     "\r\n";
 21
 22/**
 23 * \brief           Netconn client thread implementation
 24 * \param[in]       arg: User argument
 25 */
 26void
 27netconn_client_thread(void const* arg) {
 28    lwcellr_t res;
 29    lwcell_pbuf_p pbuf;
 30    lwcell_netconn_p client;
 31    lwcell_sys_sem_t* sem = (void*)arg;
 32
 33    /* Request attach to network */
 34    while (lwcell_network_request_attach() != lwcellOK) {
 35        lwcell_delay(1000);
 36    }
 37
 38    /*
 39     * First create a new instance of netconn
 40     * connection and initialize system message boxes
 41     * to accept received packet buffers
 42     */
 43    if ((client = lwcell_netconn_new(LWCELL_NETCONN_TYPE_TCP)) != NULL) {
 44        /*
 45         * Connect to external server as client
 46         * with custom NETCONN_CONN_HOST and CONN_PORT values
 47         *
 48         * Function will block thread until we are successfully connected (or not) to server
 49         */
 50        if ((res = lwcell_netconn_connect(client, NETCONN_HOST, NETCONN_PORT)) == lwcellOK) {
 51            printf("Connected to %s\r\n", NETCONN_HOST);
 52
 53            /* Send data to server */
 54            if ((res = lwcell_netconn_write(client, request_header, sizeof(request_header) - 1)) == lwcellOK) {
 55                res = lwcell_netconn_flush(client); /* Flush data to output */
 56            }
 57            if (res == lwcellOK) { /* Were data sent? */
 58                printf("Data were successfully sent to server\r\n");
 59
 60                /*
 61                 * Since we sent HTTP request,
 62                 * we are expecting some data from server
 63                 * or at least forced connection close from remote side
 64                 */
 65                do {
 66                    /*
 67                     * Receive single packet of data
 68                     *
 69                     * Function will block thread until new packet
 70                     * is ready to be read from remote side
 71                     *
 72                     * After function returns, don't forgot the check value.
 73                     * Returned status will give you info in case connection
 74                     * was closed too early from remote side
 75                     */
 76                    res = lwcell_netconn_receive(client, &pbuf);
 77                    if (res
 78                        == lwcellCLOSED) { /* Was the connection closed? This can be checked by return status of receive function */
 79                        printf("Connection closed by remote side...\r\n");
 80                        break;
 81                    } else if (res == lwcellTIMEOUT) {
 82                        printf("Netconn timeout while receiving data. You may try multiple readings before deciding to "
 83                               "close manually\r\n");
 84                    }
 85
 86                    if (res == lwcellOK && pbuf != NULL) { /* Make sure we have valid packet buffer */
 87                        /*
 88                         * At this point read and manipulate
 89                         * with received buffer and check if you expect more data
 90                         *
 91                         * After you are done using it, it is important
 92                         * you free the memory otherwise memory leaks will appear
 93                         */
 94                        printf("Received new data packet of %d bytes\r\n", (int)lwcell_pbuf_length(pbuf, 1));
 95                        lwcell_pbuf_free_s(&pbuf); /* Free the memory after usage */
 96                    }
 97                } while (1);
 98            } else {
 99                printf("Error writing data to remote host!\r\n");
100            }
101
102            /*
103             * Check if connection was closed by remote server
104             * and in case it wasn't, close it manually
105             */
106            if (res != lwcellCLOSED) {
107                lwcell_netconn_close(client);
108            }
109        } else {
110            printf("Cannot connect to remote host %s:%d!\r\n", NETCONN_HOST, NETCONN_PORT);
111        }
112        lwcell_netconn_delete(client); /* Delete netconn structure */
113    }
114    lwcell_network_request_detach(); /* Detach from network */
115
116    if (lwcell_sys_sem_isvalid(sem)) {
117        lwcell_sys_sem_release(sem);
118    }
119    lwcell_sys_thread_terminate(NULL); /* Terminate current thread */
120}
121
122#endif /* LWCELL_CFG_NETCONN */
Non-blocking receive

By default, netconn API is written to only work in separate application thread, dedicated for network connection processing. Because of that, by default every function is fully blocking. It will wait until result is ready to be used by application.

It is, however, possible to enable timeout feature for receiving data only. When this feature is enabled, lwcell_netconn_receive() will block for maximal timeout set with lwcell_netconn_set_receive_timeout() function.

When enabled, if there is no received data for timeout amount of time, function will return with timeout status and application needs to process it accordingly.

Tip

LWCELL_CFG_NETCONN_RECEIVE_TIMEOUT must be set to 1 to use this feature.

group LWCELL_NETCONN

Network connection.

Defines

LWCELL_NETCONN_RECEIVE_NO_WAIT

Receive data with no timeout.

Note

Used with lwcell_netconn_set_receive_timeout function

LWCELL_NETCONN_FLAG_FLUSH

Immediate flush after netconn write

Typedefs

typedef struct lwcell_netconn *lwcell_netconn_p

Netconn object structure.

Enums

enum lwcell_netconn_type_t

Netconn connection type.

Values:

enumerator LWCELL_NETCONN_TYPE_TCP = LWCELL_CONN_TYPE_TCP

TCP connection

enumerator LWCELL_NETCONN_TYPE_UDP = LWCELL_CONN_TYPE_UDP

UDP connection

enumerator LWCELL_NETCONN_TYPE_SSL = LWCELL_CONN_TYPE_SSL

TCP connection over SSL

Functions

lwcell_netconn_p lwcell_netconn_new(lwcell_netconn_type_t type)

Create new netconn connection.

Parameters

type[in] Netconn connection type

Returns

New netconn connection on success, NULL otherwise

lwcellr_t lwcell_netconn_delete(lwcell_netconn_p nc)

Delete netconn connection.

Parameters

nc[in] Netconn handle

Returns

lwcellOK on success, member of lwcellr_t enumeration otherwise

lwcellr_t lwcell_netconn_connect(lwcell_netconn_p nc, const char *host, lwcell_port_t port)

Connect to server as client.

Parameters
  • nc[in] Netconn handle

  • host[in] Pointer to host, such as domain name or IP address in string format

  • port[in] Target port to use

Returns

lwcellOK if successfully connected, member of lwcellr_t otherwise

lwcellr_t lwcell_netconn_receive(lwcell_netconn_p nc, lwcell_pbuf_p *pbuf)

Receive data from connection.

Parameters
  • nc[in] Netconn handle used to receive from

  • pbuf[in] Pointer to pointer to save new receive buffer to. When function returns, user must check for valid pbuf value pbuf != NULL

Returns

lwcellOK when new data ready,

Returns

lwcellCLOSED when connection closed by remote side,

Returns

lwcellTIMEOUT when receive timeout occurs

Returns

Any other member of lwcellr_t otherwise

lwcellr_t lwcell_netconn_close(lwcell_netconn_p nc)

Close a netconn connection.

Parameters

nc[in] Netconn handle to close

Returns

lwcellOK on success, member of lwcellr_t enumeration otherwise

int8_t lwcell_netconn_getconnnum(lwcell_netconn_p nc)

Get connection number used for netconn.

Parameters

nc[in] Netconn handle

Returns

-1 on failure, connection number between 0 and LWCELL_CFG_MAX_CONNS otherwise

void lwcell_netconn_set_receive_timeout(lwcell_netconn_p nc, uint32_t timeout)

Set timeout value for receiving data.

When enabled, lwcell_netconn_receive will only block for up to timeout value and will return if no new data within this time

Parameters
  • nc[in] Netconn handle

  • timeout[in] Timeout in units of milliseconds. Set to 0 to disable timeout feature. Function blocks until data receive or connection closed Set to > 0 to set maximum milliseconds to wait before timeout Set to LWCELL_NETCONN_RECEIVE_NO_WAIT to enable non-blocking receive

uint32_t lwcell_netconn_get_receive_timeout(lwcell_netconn_p nc)

Get netconn receive timeout value.

Parameters

nc[in] Netconn handle

Returns

Timeout in units of milliseconds. If value is 0, timeout is disabled (wait forever)

lwcellr_t lwcell_netconn_write(lwcell_netconn_p nc, const void *data, size_t btw)

Write data to connection output buffers.

Note

This function may only be used on TCP or SSL connections

Parameters
  • nc[in] Netconn handle used to write data to

  • data[in] Pointer to data to write

  • btw[in] Number of bytes to write

Returns

lwcellOK on success, member of lwcellr_t enumeration otherwise

lwcellr_t lwcell_netconn_write_ex(lwcell_netconn_p nc, const void *data, size_t btw, uint16_t flags)

Extended version of lwcell_netconn_write with additional option to set custom flags.

Note

It is recommended to use this for full features support

Parameters
  • nc[in] Netconn handle used to write data to

  • data[in] Pointer to data to write

  • btw[in] Number of bytes to write

  • flags – Bitwise-ORed set of flags for netconn. Flags start with LWCELL_NETCONN_FLAG_xxx

Returns

lwcellOK on success, member of lwcellr_t enumeration otherwise

lwcellr_t lwcell_netconn_flush(lwcell_netconn_p nc)

Flush buffered data on netconn TCP/SSL connection.

Note

This function may only be used on TCP/SSL connection

Parameters

nc[in] Netconn handle to flush data

Returns

lwcellOK on success, member of lwcellr_t enumeration otherwise

lwcellr_t lwcell_netconn_send(lwcell_netconn_p nc, const void *data, size_t btw)

Send data on UDP connection to default IP and port.

Parameters
  • nc[in] Netconn handle used to send

  • data[in] Pointer to data to write

  • btw[in] Number of bytes to write

Returns

lwcellOK on success, member of lwcellr_t enumeration otherwise

lwcellr_t lwcell_netconn_sendto(lwcell_netconn_p nc, const lwcell_ip_t *ip, lwcell_port_t port, const void *data, size_t btw)

Send data on UDP connection to specific IP and port.

Note

Use this function in case of UDP type netconn

Parameters
  • nc[in] Netconn handle used to send

  • ip[in] Pointer to IP address

  • port[in] Port number used to send data

  • data[in] Pointer to data to write

  • btw[in] Number of bytes to write

Returns

lwcellOK on success, member of lwcellr_t enumeration otherwise

Examples and demos

Various examples are provided for fast library evaluation on embedded systems. These are prepared and maintained for 2 platforms, but could be easily extended to more platforms:

  • WIN32 examples, prepared as CMake projects, ready for MSYS2 GCC compiler

  • ARM Cortex-M examples for STM32, prepared as STM32CubeIDE GCC projects. These are also supported in Visual Studio Code through CMake and ninja build system. Dedicated tutorial is available to get started in VSCode.

Note

Library is platform agnostic and can be used on many different products

Example architectures

There are many platforms available today on a market, however supporting them all would be tough task for single person. Therefore it has been decided to support (for purpose of examples) 2 platforms only, WIN32 and STM32.

WIN32

Examples for WIN32 are CMake-ready and VSCode-ready. It utilizes CMake-presets feature to let you select the example and compile it directly.

  • Make sure you have installed GCC compiler and is in env path (you can get it through MSYS2 packet manager)

  • Install ninja and cmake and make them available in the path (you can get all through MSYS2 packet manager)

  • Go to examples win32 folder, open vscode there or run cmd: cmake --preset <project name> to configure cmake and later cmake --build --preset <project name> to compile the project

Application opens COM port, set in the low-level driver. External USB to UART converter (FTDI-like device) is necessary in order to connect to GSM device.

Note

GSM device is connected with USB to UART converter only by RX and TX pins.

Device driver is located in /lwcell/src/system/lwcell_ll_win32.c

STM32

Embedded market is supported by many vendors and STMicroelectronics is, with their STM32 series of microcontrollers, one of the most important players. There are numerous amount of examples and topics related to this architecture.

Examples for STM32 are natively supported with STM32CubeIDE, an official development IDE from STMicroelectronics.

You can run examples on one of official development boards, available in repository examples.

Supported development boards

Board name

GSM settings

Debug settings

UART

MTX

MRX

RST

UART

MDTX

MDRX

STM32F429ZI-Nucleo

USART6

PC6

PC7

PC5

USART3

PD8

PD9

Pins to connect with GSM device:

  • MTX: MCU TX pin, connected to GSM RX pin

  • MRX: MCU RX pin, connected to GSM TX pin

  • RST: MCU output pin to control reset state of GSM device

Other pins are for your information and are used for debugging purposes on board.

  • MDTX: MCU Debug TX pin, connected via on-board ST-Link to PC

  • MDRX: MCU Debug RX pin, connected via on-board ST-Link to PC

  • Baudrate is always set to 921600 bauds

Examples list

Here is a list of all examples coming with this library.

Tip

Examples are located in /examples/ folder in downloaded package. Check Download library section to get your package.

Tip

Do not forget to set PIN & PUK codes of your SIM card before running any of examples. Open /snippets/sim_manager.c and update pin_code and puk_code variables.

Device info

Simple example which prints basic device information:

  • Device Manufacturer

  • Device Model

  • Device serial number

  • Device revision number

MQTT Client API

Similar to MQTT Client examples, but it uses separate thread to process events in blocking mode. Application does not use events to process data, rather it uses blocking API to receive packets

Netconn client

Netconn client is based on sequential API. It starts connection to server, sends initial request and then waits to receive data.

Processing is in separate thread and fully sequential, no callbacks or events.

Call

Call example answers received call. If GSM device supports calls and has microphone/speaker connected to module itself, it can simply communicate over voice.

Call & SMS

This example shows how to receive a call and send reply with SMS. When application receives call, it hangs-up immediately and sends back SMS asking caller to send SMS instead.

When application receives SMS, it will send same SMS content back to the sender’s number.

SMS Send receive

It demonstrates sending and receiving SMS either in events or using thread processing.

Changelog

# Changelog

## Develop

- Split CMakeLists.txt files between library and executable
- Change license year to 2022
- Timeout: Module returns ERRMEM if no memory to allocate block
- MQTT: update client to be consistent with client from LwESP library
- Port: Add ThreadX port aligned with LwESP library
- Add optional keep-alive system period event
- Add `LWGSM ` prefix for debug messages
- Update code style with astyle
- Add `.clang-format` draft
- Delete `lwgsm_datetime_t` and use generic `struct tm` instead
- Rename project from `lwgsm` to `lwcell`, indicating cellular

## v0.1.1

- Update CMSIS OS driver to support FreeRTOS aware kernel
- Update to support library.json for Platform.IO

## v0.1.0

- First release
- Support for SIM800/SIM900 for the moment
- Added AT commands as per documentation
- Sequential API for network supported