This tutorial demonstrates how Mongoose Library can be used to implement a web server on ESP32. A full source code for this tutorial is at https://github.com/cesanta/mongoose/tree/master/examples/esp32

Project structure

This example uses ESP-IDF 4.x toolchain, and therefore project structure follows a standard ESP-IDF CMake-based format:

  • main/ - a "main" component directory which include application code
    • main/CMakeLists - a standard ESP-IDF component-level project CMake file
    • main/main.c - main application file, contains Mongoose logic
    • main/wifi.c - contains Wifi setup routine, wifi_init()
    • main/mongoose.c - a symlink to repo's mongose.c
    • main/mongoose.h - a symlink to repo's mongose.h
  • CMakeLists - a standard ESP-IDF top-level project CMake file
  • partitions.csv - defines a partition table for a 4MB board
  • sdkconfig.defaults - default build values
  • Makefile - this Makefile is just a wrapper that invokes IDF

In the production code, mongoose.c and mongoose.h files should be copied to the main/ directory instead of creating symlinks.

Build a project

Before building a project, open main/main.c file and edit WiFi network name and WiFi password settings:

The next step is to build a project. It is assumed you're using Linux or Mac as a workstation, and you have Docker installed.

Start a terminal in the project's directory and run make build command:

$ git clone https://github.com/cesanta/mongoose
$ cd mongoose/examples/esp32
$ make build

In order to flash a built firmware to your ESP32 board, plug it in the USB and execute (change /dev/ttyUSB0 to an actual serial port on your system):

$ cd build
$ esptool.py --chip esp32 -p /dev/ttyUSB0 -b 460800 --before=default_reset --after=hard_reset write_flash --flash_mode dio --flash_freq 40m --flash_size 2MB 0x8000 partition_table/partition-table.bin 0x1000 bootloader/bootloader.bin 0x100000 mongoose-esp32-example.bin
$ cu -l /dev/ttyUSB0 -s 115200

When done, start a browser on http://IP_ADDRESS where IP_ADDRESS is the board's IP address printed on the serial console. You should see a directory listing with hello.txt file.

Main function

The main() function has three blocks:

  1. Mount a filesystem and write a sample file
  2. Initialise WiFi
  3. Run Mongoose

Mount filesystem

A firmware that is built does not have a filesystem image flashed, but it has SPIFFS filesystem support. So the following piece of code initialises a filesystem, and writes a file hello.txt using mg_file_printf() API call. This file will be shown on a browser's directory listing.

Initialise wifi

There we call a wifi_init() function which is defined in main/wifi.c. That is a blocking function, i.e. it does not return until the board gets IP address:

Run Mongoose

Next goes Mongoose. Note that it should be run after network initialisation is complete - which is true in our case. A logic is standard - initialise event manager, start a listener on port 80, and fall into an infinite event loop:

The event handler function handles /api/state RESTful handler which reports a free RAM. Other URIs are treated as static server requests:

There is a caveat there - we redefine FS handler. This is done because SPIFFS is a flat file system without directory support. Therefore we create our own filesystem which is derived from a standard POSIX FS but has a stat() function redefined - it returns a "directory" flag for /: