Zephyr: Device Dashboard

Overview

This tutorial demonstrates how Mongoose Library can be used over the Zephyr OS.

device dashboard login

Features of this implementation include:

  • The Web dashboard provides:
    • User Authentication: login protection with multiple permission levels
    • The web UI is optimised for size and for TLS usage
    • Logged users can view/change device settings
    • The web UI is fully embedded into the firmware binary, and does not need a filesystem to serve it, making it resilient
interactive device dashboard

This example is a hardware adaptation of the Device Dashboard that can run on Mac/Linux/Windows. Mongoose Library, being cross-platform, allows to develop and run the same code on different platforms. That means: all functionality related to networking can be developed and debugged on a workstation, and then run as-is on an embedded device - and this example is a demonstration of that.

Project structure

This example uses the Zephyr freestanding application format, the zephyrproject directory will be cloned on a separate directory and the project structure inside the example directory will follow this format:

  • src/ - the directory that includes the application code
    • src/main.c - main application file, contains Mongoose logic and Zephyr network-ready detection code
    • src/mongoose.c, src/mongoose.h - Mongoose Library
  • src/net.c, src/net.h - part of the device dashboard example, contains the Web functionality
  • src/packed_fs.c - part of device dashboard example, embeds the Web UI used by the dashboard
  • CMakeLists.txt - A cmake file that selects the source files and compilation options; a standard Zephyr project component
  • prj.conf - a standard Zephyr project component, contains configuration values
  • Makefile - mainly invokes Docker to call the Zephyr utilities

Build and run

  • It is assumed that you have Docker installed, and your user is able to run it. If in doubt, check it:
    docker ps
    
  • Follow the Build Tools tutorial to setup your development environment.
  • Start a terminal in the project directory; clone the Mongoose Library repo, and run the make zephyr command:
    git clone https://github.com/cesanta/mongoose
    cd mongoose/examples/zephyr/device-dashboard
    make zephyr
    
    • This takes several minutes, but you only need to run make zephyr once. It clones the Zephyr repo and populates the zephyrproject directory, then you can try other Zephyr examples by just running make build. If you run make zephyr again, it will update the repo, so call it from time to time to keep it up to date.
  • We need to specify for which board we are going to build. You can get a list of the supported boards and their names here, this is a list of the ones we tried:
    Board Zephyr name and link to their info
    STM32 Nucleo-F746ZG nucleo_f746zg
    STM32 Nucleo-F429ZI nucleo_f429zi
  • Run make build BUILD_ARGS="-b yourboardname".
    make build BUILD_ARGS="-b nucleo_f746zg"
    
  • Once the build succeeds, run make flash. The Makefile shares the USB bus with the Docker container, this works well with ST-Link and J-Link devices. If this does not work for you, pass the necessary Docker parameters inside DOCKER_ARGS (e.g.: make flash DOCKER_ARGS=something). If you are using the standard debugger for your board, it will just work. If you are using another option like for example reflashing your ST-Link to a J-Link, you might need to add a --runner parameter, read the Zephyr documentation.
  • The generated ELF file is at build/zephyr
  • Now open a second terminal, this will be our serial console where Zephyr and Mongoose will log their activities. Run a serial port software (we use minicom; make sure you configure it at the proper speed, many boards use 115200bps). Check your board/devtool's device name, for ST-Link and J-Link it is usually /dev/ttyACM0; in Linux, for example, run minicom -D /dev/ttyACM0.
  • Reset your board, you should see Zephyr and Mongoose logs:
    [00:00:00.020,000] <inf> net_config: Running dhcpv4 client...
    1f0e   2 main.c:33:main                 Mongoose version : v7.9
    [00:00:07.930,000] <inf> net_dhcpv4: Received: 192.168.69.242
    [00:00:07.940,000] <inf> net_config: IPv4 address: 192.168.69.242
    [00:00:07.940,000] <inf> net_config: Lease time: 21600 seconds
    [00:00:07.940,000] <inf> net_config: Subnet: 255.255.255.0
    [00:00:07.940,000] <inf> net_config: Router: 192.168.69.1
    2828   2 mongoose.c:4060:sntp_cb        2 got time: 1678300553311 ms from epoch
    
  • Start a browser on http://IP_ADDRESS:8000 where IP_ADDRESS is the board's IP address printed on the serial console. You should see a login screen.

From here on, please go to the device dashboard tutorial and repeat some of the steps depicted there

Cleaning up

  • You can run make clean to clean up the build files but keep the configuration files, which speeds the next build
  • If you do major changes (e.g.: compiling for a different board), run make pristine to clean up everything under the build directory.

main.c overview

As the network is initialized by Zephyr (we configured it for that), the main() function has two blocks:

  1. Wait until the netwok is ready (DHCP has assigned us an IP address)
  2. Run Mongoose

Zephyr will start calling our main() function right from the start, so we need to wait until we have a valid address before opening sockets.

We declare a semaphore and write a simple event handler that will be called by the Zephyr network manager. This function will release the semaphore.

On our main() function then we register that callback function and block on the semaphore:

Init and run Mongoose

Next goes Mongoose. As we commented, it should be run after network initialization is complete. The logic is standard; we initialize the event manager, start a listener on port 8000, and fall into an infinite event loop:

In this case, the listener is started by web_init(), the device dashboard initialization function. The URL is configured by the macro HTTP_URL.

For this example we've chosen to embed all the web files in a packed filesystem, more information on the embedded filesystem tutorial

We have covered those aspects that are specific to the Zephyr implementation, for the details on the application and the UI, please see the device dashboard tutorial.

Configuring Zephyr

  • Mongoose needs a large stack, allocates memory to perform its tasks, and interacts with Zephyr through its socket API. The following is a set of configuration options that generally apply to most Mongoose examples:
      CONFIG_NET_SOCKETS=y
      CONFIG_NET_SOCKETS_POLL_MAX=32
      CONFIG_POSIX_MAX_FDS=32
      CONFIG_NET_CONNECTION_MANAGER=y
      CONFIG_ISR_STACK_SIZE=2048
      CONFIG_MAIN_STACK_SIZE=8192
      CONFIG_IDLE_STACK_SIZE=1024	
      CONFIG_MINIMAL_LIBC_RAND=y
      CONFIG_MINIMAL_LIBC_MALLOC_ARENA_SIZE=32768
    
  • Since in this example we'll be using several connections simultaneously, we need to tell Zephyr so. Even though we are using the socket interface, we need to set the configuration options that increase the number of connections (and contexts) available:
      CONFIG_NET_MAX_CONN=10
      CONFIG_NET_MAX_CONTEXTS=10