ESP-AT Lib  Version v0.3
Advanced AT parser for ESP8266 WiFi module
Application note

Clone repository and getting started

Library development is fully hosted on Github and there is no future plans to move to any other platform.

There are 2 repositories

  • ESP_AT_Lib: Source code of library itself.
    • Repository is required when developing final project
  • ESP_AT_Lib_res: Resources, development code, documentation sources, examples, code snippets, etc.
    • This repository uses ESP_AT_Lib repository as submodule
    • Repository is used to evaluate library using prepared examples

Clone resources repository with examples

Easiest way to test the library is to clone resources repository.

  • Download and install git if not already
  • Open console and navigate to path in the system to clone repository to. Use command cd your_path
  • Run git clone https://github.com/MaJerle/ESP_AT_Lib_res command to clone repository
  • Enter into newly cloned folder using cd ESP_AT_Lib_res. Now we are inside working git directory
  • Run command git submodule update --init --recursive to download and update all submodules
  • Navigate to examples directory and run favourite example

Clone library only

If you are already familiar with library and you wish to include it in existing project, easiest way is to clone library repository only.

  • Download and install git if not already
  • Open console and navigate to path in the system to clone repository to. Use command cd your_path
  • Run git clone https://github.com/MaJerle/ESP_AT_Lib command to clone repository

Example projects

Note
Examples are part of ESP_AT_Lib_res repository. Refer to Clone resources repository with examples

Several examples are available to show application use cases. These are split and can be tested on different systems.

WIN32 examples

Library is developed under WIN32 system. That is, all examples are first developed and tested under WIN32, later ported to embedded application. Examples come with Visual Studio project. You may open project and directly run the example from there.

Note
It may happen that Visual Studio sets different configuration on first project load and this may lead to wrong build and possible errors. Active configuration must be Debug and Win32 or x86. Default active build can be set in project settings.
NodeMCU development board

For development purposes, NodeMCU v3 board is used with virtual COM port support to translate USB communication to UART required for ESP8266.

Warning
Some NodeMCU boards have CH340 USB->UART transceiver where I found problems with communication due to data loss between ESP and PC even at 115200 bauds. Try to find NodeMCU with something else than CH340.
System functions for WIN32

Required system functions are based on "windows.h" file, available on windows operating system. Natively, there is support for:

  • Timing functions
  • Semaphores
  • Mutexes
  • Threads

The last part are message queues which are not implemented in Windows OS. Message queues were developed with help of semaphores and dynamic memory allocatations. System port for WIN32 is available in src/system/esp_sys_win32.c file.

Low-level communication between NodeMCU and WIN32

Communication with NodeMCU hardware is using virtual files for COM ports. Implementation of low-level part (together with memory allocation for library) is available in src/system/esp_ll_win32.c file.

Note
In order to start using this port, user must set the appropriate COM port name when opening a virtual file. Please check implementation file for details.

ARM Cortex-M examples

Library is independant from CPU architecture, meaning we can also run it on embedded systems. Different ports for FreeRTOS operating system and STM32 based microcontrollers are available too.

STM32 boards and pinouts for tests
ESP target settings Debug settings
Board name UART MTX MRX RST GPI0GPI2CHPDUART MDTXMDRXDBD Comment
STM32F769I-Discovery UART5 PC12PD2 PJ14- - - USART1 PA9 PA10921600 OBSTL
STM32F723E-Discovery UART5 PC12PD2 PG14- PD6 PD3 USART6 PC6 PC7 921600 OBSTL
STM32L496G-Discovery USART1 PB6 PG10PB2 PH2 PA0 PA4 USART2 PA2 PD6 921600 OBSTL
STM32L432KC-Nucleo USART1 PA9 PA10PA12PA7 PA6 PB0 USART2 PA2 PA3 921600 OBSTL
STM32F429ZI-Nucleo USART2 PD5 PD6 PD1 PD4 PD7 PD3 USART3 PD8 PD9 921600 OBSTL
  • MTX: MCU TX pin, other device RX pin
  • MRX: MCU RX pin, other device TX pin
  • RST: Reset pin from ESP device, connected to MCU
  • GPI0: ESP GPIO0 pin, connected to MCU
  • GPI2: ESP GPIO0 pin, connected to MCU
  • CHPD: ESP CH PD pin, connected to MCU
  • MDTX: MCU Debug TX pin, other device RX pin
  • MDRX: MCU Debug RX pin, other device TX pin
  • DBD: Debug UART baudrate
  • OBSTL: On-Board ST-Link USB virtual COM port
Note
All examples for STM32 come with ST's official free development studio.

Porting guide

System structure

system_structure.svg
System structure organization

We can describe library structure in 4 different layers:

  • User application: User application is highest layer where entire code is implemented by user and where ESP AT library API functions are called from
  • ESP AT middleware: ESP AT middleware layer consists of API functions, thread management functions and all utilities necessary for smooth operation.
  • System functions: Layer where system dependant functions must be implemented, such as current time in milliseconds and all OS dependant functions for:

    • Managing threads
    • Managing semaphores
    • Managing mutexes
    • Managing message queues

    More about this part can be found in System functions section.

  • AT port communication functions or ESP LL: Part of code where user must take care of sending and receiving data from/to ESP AT lib to properly handle communication between host device and ESP device.

    More about this part can be found in Low-level functions section.

    Together with this section, user must implement part to input the received data from AT port.

  • ESP physical device: Actual ESP8266 or ESP32 device

Implementation specific part

Before usage, user must implement all functions in Low-level functions section as well as take care of proper communication with ESP device in Low-level functions section.

Note
For more information about how to port, check sections accordingly

Library configuration

To make library as efficient as possible, different configuration parameters are available to make sure all the requirements are met for different purposes as possible.

A list of all configurations can be found in Configuration section.

Project configuration file

Library comes with 2 configuration files:

When project is started, user has to rename template file to esp_config.h and if required, it should override default settings in this file.

Default template file comes with something like this:

#ifndef ESP_HDR_CONFIG_H
#define ESP_HDR_CONFIG_H
/* Rename this file to "esp_config.h" for your application */
/*
* Open "include/esp/esp_config_default.h" and
* copy & replace settings you want to change here
*/
/* After user configuration, call default config to merge config together */
#include "esp/esp_config_default.h"
#endif /* ESP_HDR_CONFIG_H */

In case user wants to increase default buffer size_t for received data, a file should be modified to something similar like code below:

#ifndef ESP_HDR_CONFIG_H
#define ESP_HDR_CONFIG_H
/* Rename this file to "esp_config.h" for your application */
/* Increase default receive buffer length */
#define ESP_RCV_BUFF_SIZE 0x800
/* After user configuration, call default config to merge config together */
#include "esp/esp_config_default.h"
#endif /* ESP_HDR_CONFIG_H */
Note
Always modify default settings by overriding them in user's custom esp_config.h file which was previously renamed from esp_config_template.h

Inter-thread communication

In order to have very effective library from resources point of view, an inter-thread communication was introduced.

thread_communication.svg
Inter-Thread communication between user and library.

Library consists of 2 threads working in parallel and bunch of different user threads.

User thread(s)

User thread is a place where user communicates with ESP AT library. When a new command wants to be executed to ESP device, user calls appropriate API function which will do following steps:

  • Allocate memory for command message from memory manager
  • Assign command type to message
  • Set other parameters, related or required to command
  • If user wants to wait for response (blocking mode), then create system semaphore sem and lock it immediatelly
  • Send everything to producing message queue which is later read in producing thread
  • If user don't want blocking mode, return from function with status OK otherwise wait for semaphore sem to be released from producing thread
    • When sem semaphore is locked, user thread may sleep and release resources for other threads this time
  • If user selects blocking mode, wait for response, free command memory in memory manager and return command response status

User may use different threads to communicate with ESP AT lib at the same time since memory manager is natively protected by mutex and producing queue is protected from multiple accesses by OS natively.

Producer thread

Producer threads reads message queue with user commands and sends initial AT command to AT port. When there is no commands from user, thread can sleep waiting for new command from user.

Once there is a command read from message queue, these steps are performed:

  • Check if processing function is set and if command is valid
  • Locks sync_sem semaphore for synchronization between processing and producing threads
  • Sends initial AT command to AT port according to command type
  • Waits for sync_sem to be ready again (released in processing thread)
  • If command was blocking, set result and unlock command sem semaphore
  • If command was not blocking, free command memory from memory manager

Process thread

Processing thread reads received data from AT port and processes them.

If command is active and received data belongs to command, they are processed according to command. If received data are not related to command (such as received network data +IPD), they are also processed and callback function is immediatelly called to notify user about received data.

Here is a list of some URC (Unsolicited Result Code) messages:

  • Received network data +IPD
  • Connection just active +LINK_CONN
  • Station disconnected from access point WIFI DISCONNECT
  • Station connected to access point WIFI CONNECTED
  • ...

All these commands must be reported to user. To do this, callback is triggered to notify user.

Events and callback functions

To make library very efficient, events and callback functions are implemented. They are separated in different groups.

Global event function

This is a callback function for all implemented major events, except events related to Connection API. User may implement all cases from esp_evt_type_t enumeration except those which start with ESP_CONN_.

This callback is set on first stack init using esp_init function. If later application needs more event functions to receive all events, user may register/unregister new functions using esp_evt_register and esp_evt_unregister respectively.

Events which can be implemented:

  • WiFi connected/disconnected
  • Device reset detected
  • New station connected to access point
  • ...

Connection event function

To optimize application related to connections and to allow easier implementation of different modules, each connection (started as client or server) has an option to implement custom callback function for connection related events.

User may implement all cases from esp_evt_type_t enumeration which start with ESP_CONN_.

Callback function is set when connection is started as client using esp_conn_start or set on esp_set_server function when enabling server connections

Events which can be implemented:

  • Connection active
  • Connection data received
  • Connection data sent
  • Connection closed
  • ...

Temporary event for API functions

When API function (ex. esp_hostname_set) directly interacts with device using AT commands, user has an option to set callback function and argument when command finishes.

This feature allows application to optimize upper layer implementation when needed or when command is executed as non-blocking API call. Read sect_block_nonblock_commands section for more information

Blocking and non-blocking commands

When API function needs to interact with device directly before valid data on return, user has an option to execute it in blocking or non-blocking mode.

Blocking mode

In blocking mode, function will block thread execution until response is received and ready for further processing. When the function returns, user has known result from ESP device.

  • Linear programming style may be applied when in thread
  • User may need to use multiple threads to execute multiple features in real-time
Example code
char hostname[20];
/* Somewhere in thread function */
/* Get device hostname in blocking mode */
/* Function returns actual result */
if (esp_hostname_get(hostname, sizeof(hostname), NULL, NULL, 1 /* 1 means blocking call */) == espOK) {
/* At this point we have valid result and parameters from API function */
printf("ESP hostname is %s\r\n", hostname);
} else {
printf("Error reading ESP hostname..\r\n");
}
Note
It is not allowed to call API function in blocking mode from other ESP event functions. Any attempt to do so will result in function returning espERRBLOCKING.

Non-blocking mode

In non-blocking mode, command is created, sent to producing message queue and function returns without waiting for response from device. This mode does not allow linear programming style, because after non-blocking command, callback function is called.

Full example for connections API can be found in Connection API section.

Example code
char hostname[20];
/* Hostname event function, called when esp_hostname_get() function finishes */
void
hostname_fn(espr_t res, void* arg) {
/* Check actual result from device */
if (res == espOK) {
printf("ESP hostname is %s\r\n", hostname);
} else {
printf("Error reading ESP hostname...\r\n");
}
}
/* Somewhere in thread and/or other ESP event function */
/* Get device hostname in non-blocking mode */
/* Function now returns if command has been sent to internal message queue */
if (esp_hostname_get(hostname, sizeof(hostname), hostname_fn, NULL, 0 /* 0 means non-blocking call */) == espOK) {
/* At this point we only know that command has been sent to queue */
printf("ESP hostname get command sent to queue.\r\n");
} else {
/* Error writing message to queue */
printf("Cannot send hostname get command to queue. Maybe out of memory? Check result from function\r\n");
}
Note
When calling API functions from any event function, it is not allowed to use blocking mode. Any attempt to do so will result in function returning espERRBLOCKING.