Every IoT device should operate over an encrypted channel. But how exactly does that security work as your fleet rapidly grows? Our recommendation is to use certificate authentication to deliver strong encryption while solving common fleet management problems. Today we’re showing how to provision and store IoT device certificates.

Certificate Authentication based on ECDSA

Golioth uses ECDSA encryption. You generate a root certificate and private key, use that key to sign all of your device certificates, then upload your public root certificate to Golioth. The last piece of the puzzled is placing a unique device certificate onto each device.

One method of doing so is to store the cert on a filesystem in non-volatile storage (NVS) that is separate from the firmware itself. Golioth has sample code to show you how, and that’s the subject of today’s video.

Root and Device Certificates

Generating the certificates is beyond the scope of this post, but the process is well-documented in the certificate authentication section of the Golioth docs. As a result of this process you will upload the generated public root certificate to Golioth. This lets Golioth securely connect to any device that uses certificates signed by your private root key.

Every device you put into the field needs to have a unique public/private key pair signed by your private root key. That device key and device certificate is what we store on the device file system.

Storing Device Certificates in a File System

Zephyr RTOS includes a filesystem which we have turned on in our certificate-provisioning sample code. Once the firmware is flashed to the device, use the Zephyr shell to create a directory in which the credentials are stored. This directory creation process can be further automated (through an mcumgr command or perhaps with some clever initialization in the firmware) but we chose to leave this as a manual step so that it’s obvious what is happening under the hood.

Credentials are transferred to the device over a USB connection using mcumgr because it facilitates writing to the filesystem over the serial connection. This is a separate process from updating the firmware, which means you can change the firmware without altering the device certificates. Likewise, you can update the certificates without overwriting the firmware.

Golioth Automatically Provisions Your Devices

We often discuss the security benefits of using certificates instead of pre-shared key authentication. But from a fleet-management standpoint there’s another huge benefit to doing so: automatic cloud provisioning for your devices.

Golioth Console showing IoT device summary

This device was automatically added using certificate authentication

In a nutshell, you don’t need to tell Golioth that you’ve created new devices. As long as they have unique certificate credentials that were signed by your root key, Golioth will authenticate your device and automatically add it to your project.

We begin with an empty project in the video, and after adding the certificates to the device it automatically appears in the Golioth device list. The name of the device is specified in the certificate itself. You choose how you want to organize your device names when you create the certificates.

This may not feel profound with a test fleet of just one device. But as you scale, the ability to add hundreds or thousands of devices to your network becomes unmanageable. This is especially true if you are responsible for adding encryption keys and device names manually. Instead, upload your public root certificate to Golioth and we take care of the rest.

Test Out Provisioning and Storing IoT Credentials Now!

Your first 50 devices are free with Golioth’s Dev Tier. Take your test drive today and see how smooth and powerful the provisioning process can be!

Golioth is a platform that helps firmware and hardware engineers integrate useful cloud functions into their products. We make it really easy to get things connected and immediately start peering into the behavior of devices in a useful way.

That said, not everyone wants to write code at any given moment. Zephyr, for all its wonderfulness, also has a pretty steep learning curve. This is why we are focused on hosting training and helping people move up that curve. So if you’re not ready to pull down an example from GitHub and compile, what is a person to do (engineer, or otherwise)?

Try out Golioth with our pre-compiled binary! We are targeting the Nordic Thingy:91, an all-in-one sensor prototyping platform (with battery) built on top of the Nordic Semiconductor nRF9160.

Great idea, but why now?

It’s a bit embarrassing to say that we didn’t think of this sooner. We have lots of platforms and lots of people wanting to try out Golioth…why didn’t we have binaries ready to go?

One reason is we expect that engineers want to build for their own platforms. A key value proposition of Golioth is that we work on custom hardware. Other platforms require that you buy their hardware in order to get access to get connectivity and hooked into their cloud platform. Wouldn’t engineers want to try things out on their own hardware? The answer is, “Yes, but not if it takes too long”. So now we’re also giving the option to try things out on Golioth without needing to set up the programming toolchain.

Getting Started with the Thingy91 Binary

There are a couple of simple steps to get started with the Thingy91 binary and trying out Golioth

  1. Get out your Thingy91 device (buy one here, if you don’t have one) and insert an activated SIM card.
  2. Download the binaries and PDF Instructions from the latest release on GitHub
  3. Follow the PDF instructions for installing tools to program your Thingy91 and get your device onto the Golioth Cloud

It really is that easy. But for a deep dive, read on for info about the Golioth Services you can test drive now that you have a functional IoT device on your workbench.

What’s in the binary?

Now that we have something you can simply program onto a device and enter credentials for, lets look at what you get to try out:

Databases

A key feature of Golioth and something that makes it into nearly every Reference Design we do is capturing time-series sensor data using LightDB Stream. When there is one-way data going from device to cloud, this is a great fit. On a sensor platform like the Thingy91, there are plenty of sensors to capture: two accelerometers, a light sensor, and a weather sensor. With Zephyr, it’s easy to take these readings and then forward them along to Golioth at customizable interval. It’s also possible to manually trigger a reading using the button on the Thingy91 (the button is under the center of the overmolded orange rubber).

We highlight the LightDB State service using 2 counters, one is incrementing, the other is decrementing. These fire on the same interval as above, but the user is able to interact with the counters from the cloud. This two-way communication is more complex, but can also provide an interesting control mechanism from the Cloud. Users can change where the counters are dynamically, by resetting the count to a particular value.

Settings

The settings service focuses on cloud to device communication. We often see deployments that want to push configuration data out to their devices in the field. The settings service allows users to select if a setting is applicable to the entire project, a subset of devices (using Blueprints as the filter), or on an individual device basis. The final option is great if you are troubleshooting a device and want to dynamically change something on the device.

With the Thingy91 binary, you can configure the red, green, and blue LED color intensities (mix and match to make new colors), the fade speed of the LEDs (it pulses on and off during operation), and the overall reporting interval mentioned above in the databases section above. By default we set the reporting interval to 60 seconds, but you might want to have your device reporting every 5 seconds for higher fidelity data on the cloud. This also allows you to scale how much data you are using from your MVNO/MNO/SIM provider.

Remote procedure calls (RPCs)

RPCs are a cloud to device communication, but the device is doing all the hard work. These enable users to trigger a function on their device from afar and the device may optionally send back data as a result. The key point is that the function being called is written on the device-side by the device programmer.

Another example of being able to throttle device data and battery up and down is the set_log_level RPC in the example below. If you call the set_log_level method along with a parameter (in this case 1, 2, 3, or 4), you can scale the verbosity of logging messages being sent back to the cloud. This is super useful for field devices, as it allows you to have a low amount of logging by default (ie: only send errors) and then scale up if there is an error reported.

Notice how the “Recent Calls” section on the Golioth Console tells you whether they have completed successfully and the round-trip time. This is also where any return messages from the RPC will show up (click the three dots to see return values).

Another example (and of course one of my favorites) is that we managed to program the piezoelectric transducer on board to play a range of different songs. Using the play_song RPC, we can trigger sounds like beep, golioth(the startup tune), mario, and funkytown.

The video above is showcasing sounds coming from the Thingy91, so…make sure you have sound turned on if you want the video to make sense.

Logging

The Golioth Logging service is a device to cloud communication service that automatically compresses and transports log messages that are part of RTOSes like Zephyr and FreeRTOS.  For the Thingy91, we enable Golioth logging in the Golioth Zephyr SDK and all of the messages being printed out on the serial terminal are also sent to the cloud.

In the RPC section above, we mentioned it’s possible to throttle log messages up and down. It’s also possible to filter messages coming back from the cloud to pick out important bits of communication coming from a wide range of devices.

Over-The-Air (OTA) updates

Golioth’s OTA service enables users to field-upgrade devices without a programming cable. This is baked into all of our SDKs and is a truly hands-off cloud to device communication. Each device that is eligible to receive a particular firmware update is notified via a listening service on a specific endpoint. When the device is eligible, it can start downloading the blocks of data over the network, validate the image, and then initiate a reset using the bootloader APIs. On the Thingy91, there is no required device interaction, the device starts the download in the background (while still transmitting things like LightDB Stream data) and then reboots when the image has been validated. You can watch all of the log messages as the download happens, if your logging is set up to see all information during the update process.

The GitHub release with the binaries includes a couple of files that makes it possible for people to not only try out the features above, but also to initiate a firmware update.

  • The initial .hex file for initial programming of the Thingy91 using nRF Connect for Desktop tools (v.1.0.0 as of this writing)
  • A .pdf showing how to download and execute the programming.
  • A .bin file that matches the hex file mentioned above for uploading to the Golioth Cloud (v.1.0.0 as of this writing)
  • An incremental .bin file that will act as your firmware upgrade (v.1.0.1 as of this writing). When you enable this release on the Golioth Cloud, you’ll see the block download start on your device.

Interacting with devices over the REST API

The Thingy91 binary is a great way to inject real data onto the Golioth platform so you can try out the Golioth REST API. All of the functions you have read about here are accessible on the REST API, including pulling data that is going from device to cloud and pushing things down from cloud to device. Hopefully having a real device you can control helps you to understand just how powerful a middleware solution like Golioth can be.

While this isn’t part of the Golioth platform, we think it’s important to point out how easy it is to map the data once it’s in Golioth. We set up a Grafana dashboard talking to our REST API endpoint and were able to extract and visualize the data described above. Notice the various readings coming back from LightDB Stream. In the lower right, we are also querying the settings for a particular device so we can view what color the LEDs should be for the device we’re viewing and how often it is sending back data. If you’d like to get access to this dashboard or need help setting up your own, email [email protected] and reference this post.

Try it, you’ll like it!

With the pre-compiled binary you’ll have a bunch of interesting data being sent to Golioth and things you can modify on the device. What’s more, you can take the code in the thingy91_golioth repository and modify it for your own projects. This demo serves as a great framework for building out your next IoT project or product, including on different hardware.

If you need help translating the code for your next device or have trouble with your Thingy91, please join us on the Golioth Forums to ask questions and brainstorm what else you can build!

A few weeks ago we held a live webinar with Infineon about how to collect sensor data using the Golioth ModusToolbox™ SDK and push that data up to the Golioth Cloud. I (Chris) was an audience member for the webinar and thought I would use my experience watching from the sidelines to comment on some things that I noticed during the video. You can watch the webinar in its entirety on YouTube now.

Starting from a low-cost dev kit

As a hardware engineer, the first thing I notice in any demonstration is the hardware the demo is running on. In this case, Mike was showcasing the SDK running on the PSOC™ 6  CY8CPROTO-062-4343W. This is a fun, low cost board (only $28 at Digikey) with the 4343W Wi-Fi module external to the main processor. It has Infineon’s signature capacitive touch controller capability on it.

The CAPSENSE™ feature is what Mike pulled into the demonstration. He utilized the touch controller to modify the brightness of an LED on a scale of 0-255. There are also cap-touch “push buttons” that can control the on/off state of that same LED. These are common use cases of cap-touch, but Mike was able to take these values and publish them to the cloud using Golioth. From there, it’s possible to view and/or chart the values in a visualization program like Grafana.

Mike demonstrating the capacitive touch controller values going through Golioth to Grafana

It’s not a large mental leap to consider this same function being used for larger LED lighting arrays or industrial control panels. With Golioth’s LightDB State, it’s also possible to modify the device side on/off behavior, but using the Cloud. Within the span of a few minutes, Mike was able to showcase what entire companies build their product functionality around. Golioth gives you these capabilities, just about “out of the box”.

The ModusToolbox™ Ecosystem

Clark from Infineon walked everyone through how ModusToolbox™ pulls together a bunch of tools. This feels like the evolution–and improvement–of the ways that IDEs used to work. Many of the same paradigms of pulling in sample code are there, but the tools are now external to an IDE (hardware and firmware engineers will recall the days of every IDE remaining captive within a customized Eclipse environment).

Now that the tools are external, you have the option to choose a wider variety of coding platforms to work from. This is a welcome change, especially as a VScode convert. Mike showed how it’s possible to boot up a new project using the examples pulled in through ModusToolbox™, while also configuring a VScode workspace for your project.

Under the hood, this is a FreeRTOS implementation. However, I am regularly calling out the difference between the RTOS (in this case, FreeRTOS) and the ecosystem (in this case, ModusToolbox™). The ecosystem is important to engineers, because this is the layer of integration that they really want: piecing together 3rd party plugins and a range of examples to get them started quickly. The engineers at Infineon have spent their time layering drivers on top of the FreeRTOS core and enabling a range of new features specific to their parts.

Since many engineers are starting from the context of a specific chipset, it’s important to understand the difference between RTOS and ecosystem, in my opinion. As a hardware engineer, I want the convenience of trying out a range of examples specific to my hardware, instead of building my own tooling from the ground up, which would be required if I was starting just from the FreeRTOS core. Granted, some firmware and software engineers might prefer a different flow.

Get started with Golioth and Infineon

Golioth takes you even further down the path to a functional demo that you can show to your team when trying out a PSOC™ 6 part in an IoT context to your team. The Golioth Firmware SDK enables you to jumpstart development and immediately open up a range of features on your project, such as:

After you watch the video above, sign up for a free Dev Tier Golioth Account, which gives you access to your first 50 devices on the platform for free. If you have any questions, check out our Forums or jump over to our Discord channel.

This is a guest post from Chris Wilson discussing how the Golioth training inspired him to create a custom Zephyr board definition for the Adafruit MagTag board used in the training.

Back in November of 2022, I ran across a post from Chris Gammell announcing a free developer training that Golioth would be offering the following month. At the time, I had no previous experience working with Zephyr or the Golioth IoT platform, but this seemed like a good introduction to both–so I signed up!

New to Golioth? Sign up for our newsletter to keep learning more about IoT development or create your free Golioth account to start building now.

The training offered by the Golioth team was really approachable, even for people like me without an extensive background in firmware development or real-time operating systems. The training starts with a basic introduction to building firmware in the underlying Zephyr RTOS and progresses through a series of examples that showcase the features of the Golioth SDK.

However, there was one aspect of the training that initially confused me: the training docs instruct you to build firmware for the ESP32-S2-Saola-1 board, but then run that firmware image on the Adafruit MagTag board.

For example, to build the firmware for the Golioth Demo application, the -b esp32s2_saola board argument is passed to the west build command:

west build -b esp32s2_saola app/golioth-demo

Why are we building firmware for a completely different board? 🤔

It turns out this works because:

  1. The ESP32-S2-Saola-1 board uses the exact same ESP32-S2 system-on-chip (SoC) as the Adafruit MagTag board, so firmware compiled for one board can run on the other.
  2. The Golioth training repo includes some additional Zephyr “overlay” files that modify the base board definition for the ESP32-S2-Saola-1 in Zephyr to work with the additional hardware features on the MagTag board.

This highlights one of the strengths of the underlying Zephyr RTOS: the ability to quickly extend or modify existing board definitions through the use of devicetree overlay files. Overlays make it possible to extend or modify an existing board definition to support new hardware variants, without having to go through the major effort of defining and upstreaming a brand new board definition to the Zephyr project.

This is great for getting something running quickly, but since these are totally different boards, I thought it felt a bit awkward (and potentially confusing) to keep using the esp32s2_saola board name in the training demos. I thought:

Wouldn’t it be nice if we could use the adafruit_magtag board name in the Golioth demo apps without having to add it to the upstream Zephyr repo?

Fortunately, Zephyr’s flexibility provides us with an option: we can bundle a custom MagTag board definition alongside the training demo apps, without having to touch the upstream Zephyr repository!

In this article, I’ll walk through step-by-step how I added a new board definition for the Adafruit MagTag board in the Golioth magtag-demo repository. By the end of the article, we’ll be able to pass the adafruit_magtag board argument to west commands like this:

west build -b adafruit_magtag app/golioth-demo

Understanding “Boards” in Zephyr

Since we want to add support for a new physical board, we need to understand what a “Board” is in the Zephyr ecosystem.

Zephyr has a layered architecture that explicitly defines a “Board” entity that is distinct from other layers in the architecture like a “CPU” or a “SoC”.

Configuration Hierarchy image from https://docs.zephyrproject.org/latest/hardware/porting/board_porting.html

The Zephyr glossary defines a board this way:

A target system with a defined set of devices and capabilities, which can load and execute an application image. It may be an actual hardware system or a simulated system running under QEMU. The Zephyr kernel supports a variety of boards.

Zephyr already has support for the Xtensa CPU (zephyr/arch/xtensa/core/) and the ESP32-S2 SoC (zephyr/soc/xtensa/esp32s2/), so we don’t need to add anything new for these layers. The only thing we need to add is a new definition for the MagTag board itself.

Let’s dig into the Zephyr documentation to see how to add support for a new board.

Adding a new Board in Zephyr

Zephyr has extensive documentation on how to add support for new hardware (see Porting). For this article specifically, I referred to the Board Porting Guide that covers how to add support for a new board in Zephyr.

The board porting guide provides a generic overview of the porting process for a fake board named “plank”, while this article tries to “fill in the gaps” for some of the more specific questions I had while working on the definition for the Adafruit MagTag board. I find it’s helpful to walk through the end-to-end process for a real board, but because this article is tailored specifically for the MagTag board, it may not exhaustively cover every possible aspect of porting Zephyr to a new board.

Zephyr is flexible and it supports pulling in board definitions from multiple possible locations. Before we can dive in and start adding a new MagTag board definition, we need to understand where to locate the files so the Zephyr build system can find them. To do that, we need to take a quick step back to understand how west workspaces and manifest repositories work.

Understanding west workspaces and manifest repositories

Building a Zephyr-based firmware image requires pulling in source code for the bootloader, kernel, libraries, and application logic from multiple Git repositories (the Zephyr term for these individual Git repositories is projects). Managing these individual repos manually would be a nightmare! Thankfully, Zephyr provides a command line tool named west that automatically manages these Git repositories for us.

West manages all these dependencies inside a top-level directory called a workspace. Every west workspace contains exactly one manifest repository, which is a Git repository containing a manifest file. The manifest file (named west.yml by default) defines the Git repositories (projects) to be managed by west in the workspace.

West is flexible and supports multiple topologies for application development within a workspace (you can read about all the supported topologies here). The magtag-demo repo is structured as a variation of the T2: Star topology. This means the magtag-demo repo is the manifest repository inside the magtag-demo-workspace west workspace, and the zephyr repository is included as a dependency in the west manifest file (in our example we keep this in deps/zephyr).

The workspace looks something like this (some folders redacted for clarity):

magtag-demo-workspace/                 # west workspace ("topdir")
├── .west/                             # marks the location of the west topdir
│   └── config                         # per-workspace local west configuration file
│
│   # The manifest repository, never modified by west after creation:
├── app/                               # magtag-demo.git repo cloned here as "app" by west
│   ├── golioth-demo/                  # Zephyr app for Golioth demo
│   │   └── boards/
│   │       ├── esp32s2_saola.conf     # app-specific software configuration
│   │       └── esp32s2_saola.overlay  # app-specific hardware configuration
│   └── west.yml                       # west manifest file
│
│   # Directories containing dependencies (git repos) managed by west:
└── deps/
    ├── bootloader/
    ├── modules/
    ├── tools/
    └── zephyr/
        └── boards/
            └── xtensa/
                └── esp32s2_saola/     # board definition for ESP32-S2-Saola-1

When we run the west build -b esp32s2_saola command, the Zephyr build system will look for a board named esp32s2_saola in a subdirectory of the zephyr/boards directory AND in a subdirectory of app/boards (if it exists). As you can see in the hierarchy above, the zephyr repo already includes the board definition for the ESP32-S2-Saola-1 board in the zephyr/boards/xtensa/esp32s2_saola/ directory, so this is the board definition that is pulled in when building the golioth-demo application.

However, if you look in the magtag-demo-workspace/app/golioth-demo/boards/ directory, you’ll notice files like esp32s2_saola.conf and esp32s2_saola.overlay that extend the esp32s2_saola board definition to enable additional software/hardware features on the MagTag board (LEDs, buttons, etc). I’ll cover the details of these files later on in this article, but for now, you just need to know that they allow application-specific modifications to the base esp32s2_saola board definition. The key takeaway here is that your Zephyr application can use and extend any existing board definition from the upstream zephyr repo.

So, to recap, if we want to add a new adafruit_magtag board definition for our app, there are two places where we could add it:

  1. In the upstream zephyr repository as boards/xtensa/adafruit_magtag
  2. In the magtag-demo repository as boards/xtensa/adafruit_magtag

If we add the board definition into the upstream zephyr repository, it would make the board definition available to anybody who uses Zephyr. That’s great! However, it can take a while for the Zephyr developers to review and approve a PR to add a new board definition. It is also required to add documentation for the board as part of the PR, which adds some additional overhead to the submission process.

In this article, we’re just going to add the custom board definition in the magtag-demo repo (as described here) so that we can bundle it alongside the training apps without waiting for it to go through the upstream submission process.

By the end of this article, we’ll end up creating the following new files:

magtag-demo-workspace/
└── app/
    ├── boards/
    │   └── xtensa/
    │       ├── Kconfig.board
    │       ├── Kconfig.defconfig
    │       ├── adafruit_magtag-pinctrl.dtsi
    │       ├── adafruit_magtag.dts
    │       ├── adafruit_magtag_defconfig
    │       └── board.cmake
    ├── dts/
    │   └── bindings/
    │       └── gpios.yaml
    ├── golioth-demo/
    │   └── boards/
    │       ├── adafruit_magtag.conf
    │       └── adafruit_magtag.overlay
    └── zephyr/
        └── module.yml

Let’s take a look at each of these files in detail.

Create the new board directory

The first step is to create a new directory where we can add the files for the adafruit_magtag board definition:

magtag-demo-workspace/app/boards/xtensa/adafruit-magtag/
This directory doesn’t need to match the board name. However, the board name must be unique. You can run west boards to get a list of the existing Zephyr board names.

Define the board hardware using Devicetree

In order to generate customized firmware for each supported board, Zephyr needs to have an understanding of each board’s hardware configuration. Rather than hard coding all the hardware details of each board into the operating system, Zephyr uses the Devicetree Specification to describe the hardware available on supported boards. Using devicetree, many aspects of the hardware can be described in a data structure that is passed to the operating system at boot time. Using this data structure, the firmware can get information about the underlying hardware through the standard devicetree.h API at runtime.

It’s easy to get overwhelmed when you first start trying to understand devicetree. Hang in there! You’ll soon see that the benefits of devicetree are worth the initial learning curve. If you’ve never worked with devicetree before, I would encourage you to spend some time reading the Introduction to devicetree in the Zephyr docs. If you prefer a video introduction, check out Marti Bolivar’s talk A deep dive into the Zephyr 2.5 device model from the 2021 Zephyr Developer’s Summit.

The devicetree data structure is essentially a hierarchy of nodes and properties. In practice, the hierarchy of nodes reflects the real-world hierarchy of the hardware, and the properties describe or configure the hardware each node represents.

There are four Devicetree files we need to provide as part of the board definition:

magtag-demo-workspace/
└── app/
    ├── boards/
    │   └── xtensa/
    │       ├── adafruit_magtag-pinctrl.dtsi
    │       └── adafruit_magtag.dts
    ├── dts/
    │   └── bindings/
    │       └── gpios.yaml
    └── golioth-demo/
        └── boards/
            └── adafruit_magtag.overlay

adafruit_magtag-pinctrl.dtsi

Zephyr uses a system called Pin Control to map peripheral functions (UART, I2C, etc) to a specific set of pins. It’s common to put these pin definitions in a <board_name>-pinctrl.dtsi file and include that file in the main <board_name>.dts device tree source file for the board.

The Golioth magtag-demo uses UART0 for the serial console, I2C1 for the onboard LIS3DH accelerometer, SPIM2 for the WS2812 “neopixel” LEDs, and LEDC0 as the PWM controller for the red LED.

Here’s the pin mapping for these peripherals on the MagTag board:

UART0:

  • TX: GPIO43
  • RX: GPIO44

I2C1:

  • SDA: GPIO33
  • SCL: GPIO34

SPIM2

  • MOSI: GPIO1
  • MISO: (not used)
  • SCLK: (not used)

To describe the hardware pin mapping, we need to create a devicetree include file:

magtag-demo-workspace/app/boards/xtensa/adafruit-magtag/adafruit_magtag-pinctrl.dtsi

First, we need to include a couple pin control header files for the ESP32-S2. These files contain macros that we’ll use in the pin control definitions:

#include <zephyr/dt-bindings/pinctrl/esp-pinctrl-common.h>
#include <dt-bindings/pinctrl/esp32s2-pinctrl.h>
#include <zephyr/dt-bindings/pinctrl/esp32s2-gpio-sigmap.h>
Although DTS has a /include/ "<filename>" syntax for including other files, the C preprocessor is run on all devicetree files, so includes are generally done with C-style #include <filename> instead.

Espressif also provides an ESP32-S2 devicetree include file (zephyr/dts/xtensa/espressif/esp32s2.dtsi) that contains a devicetree node for the pin controller called pin-controller with a node label named pinctrl:

pinctrl: pin-controller {
    compatible = "espressif,esp32-pinctrl";
    status = "okay";
};

We need to extend this node to include the missing pin configuration for the MagTag board. Zephyr provides a convenient shortcut to refer to existing devicetree nodes via the &node syntax (where node is the node label). In the adafruit_magtag-pinctrl.dtsi file, we’ll refer to this node as &pinctrl and extend it by providing additional properties:

&pinctrl {
    ...
};

Pin control has the concept of states, which can be used to set different pin configurations based on runtime operating conditions. Currently, two standard states are defined in Zephyr: default and sleep. For the Golioth magtag-demo we’re only going to define pin mappings for the default state.

Let’s define the default state mapping for the UART0 pins. We’ll define a node named uart0_default with matching node label uart0_default. Since the RX pin requires an internal pull-up to be enabled on our board, we’ll define two groups: group1 and group2. Groups allow properties to be applied to multiple pins at once, and we’ll use it here to apply the bias-pull-up property to the RX pin. In each group, pins are declared by assigning one of the macro definitions from esp32s2-pinctrl.h to the pinmux property. For example, the UART0_TX_GPIO43 macro assigns GPIO43 to the UART0 peripheral as TX, and UART0_RX_GPIO44 assigns GPIO44 to the UART0 peripheral as RX:

&pinctrl {

    uart0_default: uart0_default {
        group1 {
            pinmux = <UART0_TX_GPIO43>;
        };
        group2 {
            pinmux = <UART0_RX_GPIO44>;
            bias-pull-up;
        };
    };
    
};

We can follow the same procedure to define additional pin mappings for the I2C1, SPIM2, and LEDC0 peripherals (you can see the complete pin control mapping file here).

Now that we’ve got the pin control mappings defined, we can use them in the main adafruit_magtag.dts devicetree source file.

adafruit_magtag.dts

To describe the hardware available on the board, we need to create a devicetree source (DTS) file:

magtag-demo-workspace/app/boards/xtensa/adafruit-magtag/adafruit_magtag.dts

First, we add a line specifying the devicetree syntax version we’re going to be using in the file:

/dts-v1/;

Next, we include the ESP32-S2 SoC devicetree definitions provided by Espressif in zephyr/dts/xtensa/espressif/esp32s2.dtsi:

#include <espressif/esp32s2.dtsi>

This file defines the hardware available on the ESP32-S2 SoC such as the available CPUs, flash memory, WiFi, GPIOs, etc.

Note that many of the peripherals defined in this file are disabled by default (status = "disabled";). We’ll enable all the peripherals used on the MagTag board later.

Since the MagTag board has a PWM-capable LED, we also need to include the PWM device tree bindings header file so that we can use the PWM_HZ(x) macro:

#include <zephyr/dt-bindings/pwm/pwm.h

Finally we include the Pin Control file we created earlier which defines the pin control mappings for the board:

#include "adafruit_magtag-pinctrl.dtsi"

Now we can define the actual device tree data structure for the MagTag board.

/ defines the root node for the board. The model property defines a human readable name for the board, and the compatible property can be used to match this node to a compatible devicetree binding file (you can think of bindings as a sort of schema for the nodes):

/ {
    model = "adafruit_magtag";
    compatible = "adafruit,magtag";
    
    ...

First, we’ll create a node for the GPIO-controlled LEDs on the MagTag board.

The LEDs on the MagTag board are connected to GPIO pins on the ESP32-S2, so we’ll look in the devicetree bindings index to see if there is already a binding that describes this hardware feature. There’s one called gpio-leds and the description says:

This allows you to define a group of LEDs. Each LED in the group is controlled by a GPIO. Each LED is defined in a child node of the gpio-leds node.

Perfect! That sounds exactly like what we want.

We’ll create a leds node for the MagTag based on the example provided in the binding file. The compatible property says that this node is compatible with the gpio-leds binding. Each individual LED is defined as a child node under leds. For example, led_0 is defined as pin 13 on gpio0, and is assigned the node label red_led. The GPIO_ACTIVE_HIGH flag means the LED is on when the pin is high,  and off when the pin is low.

leds {
    compatible = "gpio-leds";
    red_led: led_0 {
        gpios =  <&gpio0 13 GPIO_ACTIVE_HIGH>;
    };
};

Right about now, you might be scratching your head wondering how the heck we knew what to put in the value for the gpios property (i.e. <&gpio0 13 GPIO_ACTIVE_HIGH>;).

Here’s how you figure it out:

The gpio-leds.yaml file defines the gpios property as type: phandle-array, so we know that the value for this property must be of the form <&phandle specifier1 specifier2 etc...>. We also know that the MagTag board has a RED LED connected to pin 13 of the GPIO0 controller, so we need to use the &gpio0 phandle to refer to the controller node. Let’s look up the gpio0 controller in zephyr/dts/xtensa/espressif/esp32s2.dtsi:

gpio0: gpio@3f404000 {
    compatible = "espressif,esp32-gpio";
    gpio-controller;
    #gpio-cells = <2>;
    reg = <0x3f404000 0x800>;
    interrupts = <GPIO_INTR_SOURCE>;
    interrupt-parent = <&intc>;
    ngpios = <32>;   /* 0..31 */
};

The #gpio-cells = <2>; property tells us that there are two specifiers required for the &gpio0 phandle. The compatible = "espressif,esp32-gpio"; property tells us the name of the binding that defines what those specifiers should be. Looking in zephyr/dts/bindings/pwm/espressif,esp32-ledc.yaml, it defines the specifiers required for gpio-cells:

gpio-cells:
  - pin
  - flags

Putting it all together, we can see that the property must be specified like this:

gpios = <&gpioX pin flags>;

which in this specific example is:

gpios = <&gpio0 13 GPIO_ACTIVE_HIGH>

We can follow the same procedure to define additional nodes for the PWM LEDs and the buttons on the MagTag (using the pwm-leds and gpio-keys bindings respectively). You can see these nodes in the complete device tree source file here.

The MagTag board has a couple other GPIOs that are used to gate the neopixel power, control the ePaper display, and drive the speaker. Unfortunately, there aren’t any existing Zephyr bindings we can use to expose this hardware to the custom drivers in the magtag-demo repo, so we’ll create a simple gpios.yaml binding file that allows us to define groups of GPIOs:

magtag-demo-workspace/app/dts/bindings/gpios.yaml

The binding defines a single gpios property (similar to gpio-leds and gpio-keys):

description: |
  This allows you to define a group of GPIOs.
  
  Each GPIO is defined in a child node of the gpios node.

  Here is an example which defines three GPIOs in the node /brd-ctrl:

  / {
      brd-ctrl {
          compatible = "gpios";
          ctrl_0 {
              gpios = <&gpio0 1 GPIO_ACTIVE_LOW>;
          };
          ctrl_1 {
              gpios = <&gpio0 2 GPIO_ACTIVE_HIGH>;
          };
          ctrl_2 {
              gpios = <&gpio1 15 (GPIO_PULL_UP | GPIO_ACTIVE_LOW)>;
          };
      };
  };

compatible: "gpios"

child-binding:
    description: GPIO child node
    properties:
        gpios:
            type: phandle-array
            required: true

Now that we have a generic gpios binding, we can add the missing nodes for the remaining GPIOs.

Let’s create a speaker node that contains the GPIOs needed for the speaker on the MagTag board. In the same way we defined the LEDs above, we define two GPIOs, active to enable the speaker and sound to drive the speaker:

speaker {
    compatible = "gpios";
    active: active_pin {
        gpios = <&gpio0 16 GPIO_ACTIVE_HIGH>;
    };
    sound: sound_pin {
        gpios = <&gpio0 17 GPIO_ACTIVE_HIGH>;
    };
};

We can follow the same procedure to define additional nodes for the neopixel power and the e-paper display GPIOs (you can see these nodes in the complete device tree source file here).

Finally, we’ll create the special /alias and /chosen nodes.

The /chosen node is used to define a set of commonly used Zephyr properties for system-wide settings like the UART device used by console driver, or the default display controller. These properties refer to other nodes using their phandles (&node, where node is the node label):

chosen {
    zephyr,sram = &sram0;
    zephyr,console = &uart0;
    zephyr,shell-uart = &uart0;
    zephyr,flash = &flash0;
};

The /aliases node is used to override generic hardware devices defined by an application. For example, the Blinky sample application requires an alias led0 to be defined. We can build and run the Blinky app on any board that defines this alias, including the MagTag board which defines the alias led0 = &red_led; to map led0 to the red LED:

aliases {
    watchdog0 = &wdt0;
    led0 = &red_led;
    pwm-led0 = &red_pwm_led;
    led-strip = &led_strip;
    sw0 = &button0;
    sw1 = &button1;
    sw2 = &button2;
    sw3 = &button3;
    neopower = &neopower;
    mosi = &mosi;
    sclk = &sclk;
    csel = &csel;
    busy = &busy;
    dc = &dc;
    rst = &rst;
    activate = &active;
    sound = &sound;
};

Now that we’ve finished creating new child nodes under the root node, we can start to customize the existing SoC nodes we included from espressif/esp32s2.dtsi. This is required to provide board-specific customizations, such configuring the pins used for a SPI peripheral or specifying the devices present on an I2C bus. As I mentioned earlier, Zephyr provides a convenient shortcut to refer to existing nodes via the &node syntax (where node is the node label) so we don’t need to write out the full device tree path.

Let’s start by taking a look at the I2C1 controller node that is defined in zephyr/dts/xtensa/espressif/esp32s2.dtsi:

i2c1: i2c@3f427000 {
    compatible = "espressif,esp32-i2c";
    #address-cells = <1>;
    #size-cells = <0>;
    reg = <0x3f427000 0x1000>;
    interrupts = <I2C_EXT1_INTR_SOURCE>;
    interrupt-parent = <&intc>;
    clocks = <&rtc ESP32_I2C1_MODULE>;
    status = "disabled";
};

We can see that the I2C1 controller is disabled by default (status = "disabled";) and it’s missing some of the properties required by the espressif,esp32-i2c binding (for example, the pinctrl properties). In our adafruit_magtag.dts file, we can refer to the &i2c1 node and define the missing required properties:

&i2c1 {
    ...
};

The pinctrl-* properties assign the i2c1_default pin control state to the controller and give it the name "default". To enable the the I2C1 controller, we override the status property by assigning status = "okay";. We also set the I2C clock frequency to I2C_BITRATE_STANDARD (100 Kbit/s).

&i2c1 {
    pinctrl-0 = <&i2c1_default>;
    pinctrl-names = "default";
    status = "okay";
    clock-frequency = <I2C_BITRATE_STANDARD>;
};

The MagTag board has an onboard LIS3DH accelerometer on the I2C1 bus, so we also add a subnode lis3dh@19. In devicetree jargon, the @19 is called the unit address and it defines the “subnode’s address in the address space of its parent node” (which in this case is the accelerometer’s I2C address in the address space of possible I2C addresses). The compatible = "st,lis2dh"; property assigns the correct binding for the accelerometer so that the Zephyr sensor drivers can use it, and the reg = <0x19>; property sets the device’s I2C address on the bus.

&i2c1 {
    pinctrl-0 = <&i2c1_default>;
    pinctrl-names = "default";
    status = "okay";
    clock-frequency = <I2C_BITRATE_STANDARD>;

    lis3dh@19 {
        compatible = "st,lis2dh";
        reg = <0x19>;
    };
};

Some nodes, like &gpio0, don’t require any additional configuration, but are disabled by default. These nodes can be enabled simply by overriding the status property:

&gpio0 {
    status = "okay";
};

We can follow the same procedure to configure the remaining nodes for the ESP32-S2 SoC (you can see these nodes in the complete device tree source file here).

adafruit_magtag.overlay

In some cases, an application may need to extend or modify nodes in the board’s devicetree structure. Zephyr provides this flexibility through the use of a devicetree overlay file. The build system will automatically pick up the overlay file if it’s placed in the <app>/boards/ subdirectory and named <board_name>.overlay.

For example, let’s create an overlay for the golioth-demo app in the magtag-demo repo:

magtag-demo-workspace/app/golioth-demo/boards/adafruit_magtag.overlay

The &wifi node for the ESP32-S2 is disabled by default. The golioth-demo app needs Wi-Fi to be enabled so it can connect to the Golioth cloud, so we’ll enable it in the app overlay:

&wifi {
    status = "okay";
};

You can see the complete overlay file here.

Define the board software features using Kconfig

Before we can compile a firmware image for the board, we need to provide some configuration options that will allow us to control which software features are enabled when building for this board. Similar to the Linux kernel, Zephyr uses the Kconfig language to specify these configuration options.

For more details on how to use Kconfig to configure the Zephyr kernel and subsystems, see Configuration System (Kconfig) in the Zephyr docs.

There are four Kconfig files we need to provide as part of the board definition:

magtag-demo-workspace/
└── app/
    ├── boards/
    │   └── xtensa/
    │       └── adafruit-magtag/
    │           ├── Kconfig.board
    │           ├── Kconfig.defconfig
    │           └── adafruit_magtag_defconfig
    └── golioth-demo/
        └── boards/
            └── adafruit_magtag.conf

Kconfig.board

This file is included by boards/Kconfig to include your board in the list of available boards. We need to add a definition for the top-level BOARD_ADAFRUIT_MAGTAG Kconfig option. Note that this option should depend on the SOC_ESP32S2 Kconfig option which is defined in soc/xtensa/esp32s2/Kconfig.soc:

config BOARD_ADAFRUIT_MAGTAG
    bool "Adafruit MagTag board"
    depends on SOC_ESP32S2

Kconfig.defconfig

This file sets board-specific default values.

# Always set CONFIG_BOARD here. This isn't meant to be customized,
# but is set as a "default" due to Kconfig language restrictions.
config BOARD
    default "adafruit_magtag"
    depends on BOARD_ADAFRUIT_MAGTAG

The ENTROPY_GENERATOR Kconfig option enables the entropy drivers for the networking stack:

config ENTROPY_GENERATOR
    default y

adafruit_magtag_defconfig

This file is a Kconfig fragment that is merged as-is into the final .config in the build directory whenever an application is compiled for this board.

The CONFIG_XTENSA_RESET_VECTOR Kconfig option controls whether the initial reset vector code is built. On the ESP32-S2, the reset vector code is located in the mask ROM of the chip and cannot be modified, so this option is disabled:

CONFIG_XTENSA_RESET_VECTOR=n

Whenever we’re building an application for this board specifically, we want to ensure that the top-level Kconfig options for the SoC and the board itself are enabled:

CONFIG_BOARD_ADAFRUIT_MAGTAG=y
CONFIG_SOC_ESP32S2=y

Change the main stack size for the various system threads to 2048 (the default is 1024):

CONFIG_MAIN_STACK_SIZE=2048

Set the system clock frequency to 240 MHz:

CONFIG_SYS_CLOCK_HW_CYCLES_PER_SEC=240000000

Zephyr is flexible and it supports emitting console messages to a wide variety of console “devices” beyond just a serial port. For example, it is possible to emit console messages to a RAM buffer, the semihosting console, the Segger RTT console, etc. As a result, we need to configure Zephyr to:

  1. Enable the console drivers (CONFIG_CONSOLE)
  2. Enable the serial drivers (CONFIG_SERIAL)
  3. Use UART for console (CONFIG_UART_CONSOLE)
CONFIG_CONSOLE=y
CONFIG_SERIAL=y
CONFIG_UART_CONSOLE=y

The ESP32-S2 defines its own __start so we need to disable CONFIG_XTENSA_USE_CORE_CRT1:

CONFIG_XTENSA_USE_CORE_CRT1=n

Enable the GPIO drivers:

CONFIG_GPIO=y

The ESP32 platform uses the gen_isr_tables script to generate its interrupt service request tables. Reset vector code is located in the mask ROM of the ESP32 chip and cannot be modified, so it does not need an interrupt vector table to be created:

CONFIG_GEN_ISR_TABLES=y
CONFIG_GEN_IRQ_VECTOR_TABLE=n

Enable support for the hardware clock controller driver:

CONFIG_CLOCK_CONTROL=y

Configure the ESP-IDF bootloader to be built and flashed with our Zephyr application:

CONFIG_BOOTLOADER_ESP_IDF=y

Enable the SPI drivers for the WS2812 “neopixel” LEDs:

CONFIG_SPI=y
CONFIG_WS2812_STRIP_SPI=y

adafruit_magtag.conf

This file defines the application-specific configuration options.

For example, magtag-demo-workspace/app/golioth-demo/boards/adafruit_magtag.overlay enables & configures the WiFi networking stack, including the Golioth utilities for easy WiFi setup:

CONFIG_WIFI=y
CONFIG_HEAP_MEM_POOL_SIZE=37760

CONFIG_NET_L2_ETHERNET=y

CONFIG_NET_DHCPV4=y

CONFIG_NET_CONFIG_LOG_LEVEL_DBG=y
CONFIG_NET_CONFIG_NEED_IPV4=y

CONFIG_MBEDTLS_ENTROPY_ENABLED=y
CONFIG_MBEDTLS_KEY_EXCHANGE_ECDHE_ECDSA_ENABLED=y
CONFIG_MBEDTLS_ECP_ALL_ENABLED=y

CONFIG_ESP32_WIFI_STA_AUTO_DHCPV4=y

CONFIG_GOLIOTH_SAMPLE_WIFI=y

# when enabling NET_SHELL, the following
# helps to optimize memory footprint
CONFIG_ESP32_WIFI_STATIC_RX_BUFFER_NUM=8
CONFIG_ESP32_WIFI_DYNAMIC_RX_BUFFER_NUM=8
CONFIG_ESP32_WIFI_DYNAMIC_TX_BUFFER_NUM=8
CONFIG_ESP32_WIFI_IRAM_OPT=n
CONFIG_ESP32_WIFI_RX_IRAM_OPT=n

Configure the build system

Before we can actually build and flash the firmware, we need to add a couple additional files for the Zephyr build system.

zephyr/module.yml

First, we need to create a module description file:

magtag-demo-workspace/app/zephyr/module.yml

This tells the build system where to find the new board and device tree source files we added above:

build:
    settings:
        board_root: .
        dts_root: .

board.cmake

In order to flash the firmware image onto the MagTag board, we need to add CMake board file:

magtag-demo-workspace/app/boards/xtensa/adafruit-magtag/board.cmake

We can just copy the file that Espressif provided for the esp32s2_saola board (since the MagTag uses the same ESP32-S2 module). This file includes the generic CMake files for the ESP32 family and OpenOCD (making sure the correct OpenOCD from the Espressif toolchain is used):

if(NOT "${OPENOCD}" MATCHES "^${ESPRESSIF_TOOLCHAIN_PATH}/.*")
    set(OPENOCD OPENOCD-NOTFOUND)
endif()
find_program(OPENOCD openocd PATHS ${ESPRESSIF_TOOLCHAIN_PATH}/openocd-esp32/bin NO_DEFAULT_PATH)

include(${ZEPHYR_BASE}/boards/common/esp32.board.cmake)
include(${ZEPHYR_BASE}/boards/common/openocd.board.cmake)

Build the firmware

At this point, we should have everything we need for the new MagTag board definition. For example, we should be able to build the firmware for the golioth-demo app using the following command:

west build -b adafruit_magtag app/golioth-demo

Next Steps

Hooray! We’ve successfully added a new board definition! 🎉

If you’d like to try out the Golioth demo apps yourself, you can take the self-paced training online for free at https://training.golioth.io/docs/intro

We can also provide private training for your company or group. Please contact us directly if interested.

Most of the time we’re writing about how to build out the firmware that will go onto your IoT Devices. Our device Software Development Kits (SDKs) are a key offering that Golioth provides to hardware and firmware engineers; we want it to be easier for you to build the code that will go onto the device out in the field. Once the code is ready to go, you need to think about how you are going to enclose the thing you just made, whether it’s a product or a prototype.

We have been building prototypes in the form of Golioth Reference Designs. These are end-to-end demos that include hardware, firmware, software, and visualization, and we’ve been building a bunch of them. That has meant we are developing a “high mix, low volume” set of products (really prototypes) and have needed to figure out how to utilize enclosures for the different things we’re trying to do.

What’s in the box

Most of our current Reference Designs contain a few key things:

  • A development board, such as the Sparkfun Thing Plus nRF9160 (our current most-used board)
  • Sensor boards or interfaces to external items
  • A battery
  • A centralized board that makes it easy to mount each of the above items

Off-the-shelf hardware like this is less a “product” than it is a first round prototype or even Proof-of-Concept. It can be used to test a business idea or to build requirements for a future high volume product.

Requirements influence design

We have tried a few different ways of enclosing our projects so far. As you’ll read below, each time we built something, we took the lessons we learned and modified our next iteration.

Color demos (Aludel)

Our first round of assumptions was that we needed to make everything waterproof and have maximum flexibility. As such, we decided on the Bud model PN-1324-CMB. It’s a normally gray opaque base with a clear polycarbonate lid where we could have a display element on a top PCB. The main function was that we could implement lots of cabling internally but still have access to Click boards (based on the Mikroelectronica MikroBus standard). The cabling would all go through cable glands to keep out moisture. I also liked that the box had flanges so we could screw this unit to a wall or a display stand.

This method worked out OK but had the downside of requiring each sensor to be either outside the box and cabled in or we needed to “break” the waterproof nature to monitor things like air humidity. Most things we were planning to do involved direct monitoring of the environment. This, in combination with the large overall size of the demos, meant that we thought we could improve on future revisions.

You can see these cases in action (including cover plates that hid the base PCB) in the green and blue demos that we took to Embedded World in 2022.

Trashcan Reference Design – First iteration

Since we were monitoring using a time-of-flight sensor on the Trashcan Reference Design, I knew we would need the sensor to be external to the case. Or at least it would need to be pointing outside the envelope of the case, as shown below. I used a Sparkfun breakout board for the VL53L0X sensor from ST Micro and drilled out a hole to allow the signal to point down into a trashcan and reflect off the top of the trash piled up inside that can. Then I hot glued it in place.

Open case

This is unrealistic in a deployment as a trashcan is a harsh environment, especially deployed somewhere like a national park. The moisture and heat inside a trashcan would require the device be more watertight. After building this iteration and realizing we were not designing something deployable, my mindset around how we needed to make our cases began to change. It would be imperative on anyone who utilizes our reference designs to make revisions, optimizing it for manufacture and hardening it for their specific environmental needs. What is the impact if our Reference Designs are no longer planned to be field ready or watertight?

Trashcan Reference Design – Second Iteration

In the second iteration of the Trashcan Reference Design, I started to optimize for some different elements, most noticeably the repeatability of the design. There is nothing special about the PCB shown below, only that it can be manufactured more easily than the hotglue-laden prototype in the first iteration. Again, we see that the design is not water tight. I did focus on maintaining a smaller form factor than the Aludel, with a low cost case.

Side view of case open

The IoT Trashcan Reference Design with the top of the case open

This one is the Bud CU-1937-MB, part of their “Utilibox” line. Not designed to show much of anything, it’s meant to really be screwed to the wall and forgotten (as many IoT things are). I once again chose a design that had flanges as part of the case for easy mounting on a wall; in this case, it’d be mounted on the underside of a trashcan lid.

The difficulty was in a non-standard cutout. Also the fact that I was using a Dremel hand tool instead of something more precise like a milling machine. This goes back to the “high mix” element, it was tough to justify buying a milling machine and then programming it or getting better at manual machining. I took my best shot with a Dremel (spending quite a bit of time in the process) and then 3D printed a thin case over top of the quite ragged holes that I cut.

Side view of case closed

A side view of the IoT Trashcan Reference Design including 3D printed cover

Most people wouldn’t/won’t see this element of the design, but it’s still important not to have holes cut out that are all jagged and rough.

Aludel Mini

In our most current iteration on display at Embedded World coming up in 2 weeks, I decided to take a different tack, based on some brainstorming with my coworker Mike. We had already re-designed the landing board to once again utilize Click boards from Mikroelectronica, and to fit in the same case (CU-1937-MB); this was called the “Aludel mini”, playing off the name of the original Aludel case design.

It was unclear at first if we could fit the size of the headers (in width and height) into that case, but it turns out we can. We really liked that there is a wide variety of sensors available in that form factor and we can also buy things like the Arduino to Click converter boards to make the same sensors available on platforms like the nRF9160-DK.

So now that we know we are using that same case and that we are somewhat OK with a 3D printed element on that case, I decided to take a leap and have Bud modify the boxes to plan for this flexibility. Instead of having them mill out just the button and USB C port cutouts, I decided to have them basically wipe out two of the walls of the case. That might be a bit extreme, but it allows for ultimate flexibility.

This was a custom order (with an upcharge for doing so, understandably), but it was a pretty seamless process to go back and forth with the manufacturer rep (Pinnacle Marketing, based on my location in the US Southeast) and the manufacturer (Bud Industries) and get these made. They did warn that the structural integrity of the case would be impacted, but I moved forward knowing we would be able to reinforce with the inserts we design.

Now the trick was to design something that could fit this shape. They have a high draft angle, ostensibly to make it an easier ejection from injection molding equipment, but that makes for some odd trapezoidal walls. I brought the supplied 3D model into FreeCAD and set to work printing and iterating on the design. Here’s the shape I ended up with:

I utilized the mounting posts for the top case as a reinforcing element. For most of the designs I put this into, I’ll glue the plate in place, but it is also designed to slide in and out for prototypes.

On the other side of the board, we expect there to be much more variation. Often this is where wires or extension cables are interfacing with the case. I took the base plate and was able to draw simple shapes onto the face plate to create a variation for each reference design that we make. This will depend on the type of Click board we have plugged into the Aludel Mini. For the Soil Moisture Reference Design, you can see that I made a cutout that allows the Click Shuttle Expander to go out to individual boards:

Soil Moisture Monitor closed with sensors closed

The obvious question

With all of these modifications, the obvious question is: Why not 3D print the entire thing?

A fair question, and definitely something that I might consider in a future iteration. I would still want to maintain many of the same features that we have put into the existing designs (relatively small envelope, mounting flanges, plain exterior). The cost would be roughly the same if sent to China for 3D printing (depending on material used), when compared to a low volume run of modified cases + printed inserts. I also like the implied constraints of choosing an enclosure and then needing to work around those constraints, although the exercise changes when designing a product. It prevents adding “just one more thing”, when in our case we’re primarily trying to showcase the platform and not the hardware itself.

High mix is not the norm

Most product companies work differently. If you are spending enough money to hire engineers to design a product, you are likely making a higher volume of devices to support the salaries of engineers. In that case, your constraints change quite a bit. When moving to a higher volume manufacturing run, it makes sense to take everything you learn from a Golioth Reference Design and spin it into a higher volume product (custom PCBs, integrated sensors, more complex battery management, bespoke watertight enclosures, etc). If you want to take Reference Designs for a spin and use them as the basis of your next product, we’d love to talk. If you’re more interested in chatting about how to optimize cases for a variety of different designs, post about it on our forum!

Eli Hughes is the principal of Wavenumber LLC which delivers positive outcomes in the areas of embedded systems, software, IOT, audio, acoustics, industrial design, and content creation.

My experience with Nordic microcontrollers and modern cloud connectivity began in December of 2017. However, it is only this month that Nordic Semiconductor finally released a Wi-Fi capable component. The nRF7002 is a low power Wi-Fi 6 helper chip (no microcontroller internally) that is deeply integrated with the Nordic Connect SDK (NCS). This article details how I got it booted and why I decided to make my first foray into using it with the Golioth Cloud.

Using Golioth for my 1st nRF7002 project

The nRF7002-DK arrives on my bench

Most of my experiences straddle the worlds of hardware and software. Network connectivity, security, and cloud “backend” can get complex quickly. For hardware-oriented minds, navigating the complexities of modern data transport are daunting. Gone are the days when adding embedded “internet connectivity” meant all one had to do was send a raw UDP packet!

Likewise, folks experienced in modern web backend technologies infrequently approach embedded development. Golioth appeared to be a bridge between these two worlds. Providing engineers on the embedded side tools for easily and securely connecting to the cloud is a big deal. Likewise, an experienced backend engineer will love hearing about access via REST APIs.
The arrival of the nRF7002-DK was a great opportunity for me to “kick the tires”. I also had personal interest in the secure approach to CoAP with DTLS.

Setup to “Live on the Edge” with nRF7002-DK

At the time of this writing, Zephyr support for the nRF7002 is still in active development. One of my soapbox pitches for Zephyr is that it is much more than an RTOS. It comes with a testing framework, open community development model, and packaging system. I could see the current nRF7002 activity on the main branch of the sdk-nrf repository. The last official release was v2.2. To experiment with the nRF7002 I needed to work from main or with one of the v2.2.99 development tags.

Observing nRF7002 development activity

Zephyr’s west tool is used to manage packages and dependencies through a west manifest. Golioth takes full advantage of this system by building their Golioth Zephyr SDK with the NCS in mind. I knew in advance that I would be hacking on samples in the Golioth SDK tree and that I would also need to use a bleeding edge version of the nRF SDK. In this case, I chose to use the fork approach for development. Namely, I forked the Golioth SDK to give myself a personal sandbox.

Forking the Golioth Zephyr SDK

This approach has the advantage that I can experiment with the Golioth samples and submit a pull request to the Golioth team in case I come up with something useful. With my fork in place, the west manifest can be modified so I can be working with the latest nRF7002 examples.
Inside the Golioth SDK is west-ncs.yml, the manifest file for Nordic specific development.

A “one liner” to be on the bleeding edge

It literally is a “one-liner” to be on the bleeding edge, From here, I pull everything down to my local machine to get started.

Note: The assumption here is that your machine has already been setup using the Zephyr getting started guide

Initializing my “sandbox” fork of the Golioth SDK

The west update operation can take a while the first time. It is pulling down all the repositories specified in the manifest.

Basic Verification of the nRF7002

Before I dove into the Golioth samples, I loaded a nRF7002 sample to verify Wi-Fi connectivity. Inside of nrf/samples/wifi/shell is a shell sample which exposes Wi-Fi and network behaviors.

Kicking off a “pristine” build using the nRF7002dk_nrf5340_cpuapp board

The nRF7002-DK has a J-Link debugger onboard and I prefer the Ozone programming/debugging experience. Note that the nRF7002 is a Wi-Fi companion the nRF7002-DK uses a nRF5340 as the host processor. The Zephyr Wi-Fi shell is a helpful tool. I could quickly connect to my guest network and perform network operations such as a ping and DNS query.

Using the Zephyr Wi-Fi Shell for connectivity verification

Getting to Golioth

I choose to start with the Golioth logging sample located in modules/lib/golioth/samples/logging inside the forked repository. Logging is a fundamental Zephyr feature and Golioth implements a logging backend to get data up to the cloud with minimal hassle. Since the nRF7002 board is on the bleeding edge, an nRF7002dk_nrf5340_cpuapp.conf board configuration had to be added to the logging sample. The Golioth examples have board Kconfig overlays located in boards directory inside of the example application folder. These overlays are used to select settings tailored for specific board or hardware platforms.

Adding a nRF7002dk_nrf5340_cpuapp board Kconfig overlay for the Golioth Logging Sample

I started by using the esp32.conf as a template, renamed it to nRF7002dk_nrf5340_cpuapp.conf and then started hacking. Since I had just run the Wi-Fi shell example, I copied all the settings in the prj.conf from the shell application example. This approach is a bit overkill, but it is easy to remove unneeded settings once everything is functional.

I also had a hunch that TLS configuration might be an issue. Nordic has their own fork of mbedTLS, so I copied in the TLS settings from nrf9160dk_nrf9160ns.conf in the logging/boards folder

Working in a Nordic Specific TLS Configuration

Next I configured the application prj.conf with a device PSK_ID and PSK that I created via the Golioth control panel. FluffyBunny1 is now provisioned!

Adding in a Test PSK and ID

To get the Golioth Wi-Fi sample to auto-magically connect to my network at boot, the Wi-Fi SSID and password were added to the prj.conf as well. This is required because we haven’t pulled in the settings subsystem, which would allow me to configure things over UART.

Configuring the sample to auto connect to my Wi-Fi Network

One of the most frustrating “living on the edge” was trying to remember the password for my Wi-Fi network. Is it possible this task consumed the majority of my time on getting things booted? My frantic, scattered brain almost resorted to the “emergency” button on the route. Lucky for me, my spouse came to the rescue. Starting with the stock logging example, I personalized the main loop to be FluffyBunny1 friendly.

The FluffyBunny1 Logging Experiment

FluffyBunny1 is reporting!

Great success! Secure communication to the cloud in minutes. Mind you, I had to do some hacking to get support for bleeding edge hardware, but getting this result was impressive.
Me being me, I couldn’t stop after seeing log messages flow to the Golioth backend.

Enabling Golioth RPC functionality

I had read about the ability to trigger a function on the device by using Remote Procedure Calls (RPCs) on Golioth and wanted to try extending this demo. I sprinkled in a bit of code to define an RPC callback and register it. It was surprisingly simple.

Code modifications to add a bit of RPC functionality

Adding in this function allowed us to “pet the fluffy bunny” directly from the control panel. Let’s look at how it happens on the console + the uart output.

Fluffy Bunny demo

Wrapping Up

My experience with Golioth and the nRF7002 was very smooth! I was quite impressed how quickly I achieve communication with the cloud backend. Even with the “bleeding edge” nature of the nRF7002, the Zephyr ecosystem simplified bring-up. Having Golioth essentially work “out of the box” on new hardware is a big plus.

I already have an idea for a new project using Golioth with custom sensors and the nRF7002. In a previous life I was involved with “raw” postgres and TimeScaleDB. After seeing Golioth LightDB Streams, I believe I have an approach to my newfound hobby: Bonsai monitoring.

I hope you can check out Golioth for your next connected project. The overall experience was Zen-like.

Visual Studio Code, colloquially known as VScode, is among the most popular integrated development environments (IDEs). Today we’re going to walk through the process of setting up ESP-IDF in VScode and using it to run Golioth device management example code on an ESP32.

Not everyone likes to live their lives hammering away at a command prompt. What we’ll cover today is another option which uses Espressif’s VScode extension (plugin) to largely automate how you work with the Espressif IoT Development Framework (ESP-IDF). That means nice buttons and interfaces to build, flash, and monitor applications for the ESP32 family of chips.

Installing VScode and the ESP-IDF extension

As a prerequisite you will need to have VScode installed. If you don’t, head over to the download page and do so now.

ESP-IDF VScode extension

Open VScode and click on the extensions icon (looks like four boxes) on the left sidebar. Type esp-idf into the search bar that appears and the top result will be “Espressif IDF”. Click the install button and you’re off to the races.

Configure ESP-IDF VScode Extension

You will be greeted with options for installing the various ESP-IDF tools. If you don’t have an opinion on how things are installed you can choose the automatic route. I wanted to specify what directories were used during the install so I chose the manual route and used the settings above.

It will take a few minutes for everything to download. You will want to click on the “Download ESP-IDF Tools” to ensure that the compilers and other tools are downloaded (in addition to the Espressif SDK). If you need more help, check out Espressif’s installation guide.

Installing the Golioth Firmware SDK

Now that VScode and the ESP-IDF are installed, let’s take a moment to install the Golioth Firmware SDK. This provides the tools and sample code for connecting your ESP32 to Golioth.

Cloning the Golioth Firmware SDK

VScode has a handy tool for cloning git repositories. Bring up the command palette (ctrl-shift-p), type in gitcl, and press enter. A prompt will open in the same window for you to enter the following URL:

https://github.com/golioth/golioth-firmware-sdk.git

After pressing enter, a window will open where you can select a folder to store the repository. A folder called golioth-firmware-sdk will be placed in that location.

VScode will ask if you want to open the cloned repository. Please click Cancel on this window. The Golioth Firmware SDK supports multiple platforms and we will open the ESP-IDF specific directory in the next step.

Open the project and update the git submodules

Now that the repository has been cloned, let’s open the sample code in VScode. Click File→Open Folder and navigate to the golioth-firmware-sdk/examples/esp_idf/golioth_basicsfolder, then click Open.

The Golioth SDK includes a few packages as submodules and these must be updated before continuing. We’re going to use the terminal for this step. In VScode click Terminal→New Terminal. A terminal window will open in the golioth-basics folder. Type this command to update the submodules:

git submodule update --init --recursive

Type exit to close the terminal window.

Build, flash, and monitor the golioth-basics application

Now that everything is installed we get to see the ease of using an IDE.

ESP-IDF Build, Flash and Monitor

The bar along the bottom of the VScode window includes icons for working with the ESP-IDF tools. Make sure your ESP32 is plugged into USB. Click the flame-shaped icon which will build the project, flash it to the ESP32, and open a serial connection to the chip.

The build will take place and then VScode will open a window in the top center prompting you to select JTAG/UART/DFU. We will be using UART. Also note that there is a selection in the bottom menu bar where you can set the port that will be used when flashing (the image above shows /dev/ttyUSB1 in my case).

Assign device credentials in the monitor window

ESP32 running the golioth-basics app

The golioth-basics app will begin running immediately and you should see an output from the chip in a window inside VScode. We need to give the chip WiFi and Golioth credentials so that it can connect to the cloud.

If you have not yet signed into Golioth, our Dev Tier is free for your first 50 devices. (Tip: there is a Console Overview on our docs that will walk you through creating a set of device credentials.) Get your Golioth credentials, and the login info for your WiFi access point, and pass them to the chip using this command format:

settings set wifi/ssid YourWiFiAccessPointName
settings set wifi/psk YourWiFiPassword
settings set golioth/psk-id YourGoliothDevicePSK-ID
settings set golioth/psk YourGoliothDevicePSK

assign credentials to the device

Once you’ve set the credentials, type reset and the ESP32 will reboot, connect to WiFi and then to Golioth.

Successful connection to Golioth

Wrapping up

You’ve successfully compiled, flashed, and run a demo Golioth application for ESP-IDF using VScode. The same principles can be applied to your own projects.

If you’d like to dig deeper into how the golioth-basics code works, I encourage you to study the golioth_basics.c file in the golioth-firmware-sdk/examples/common folder. It demonstrates all of the Golioth device management features like OTA firmware updates, remote procedure calls (RPC), IoT fleet settings service, LightDB State and LightDB Stream data services, and remote logging.

We’d love to hear about the projects your working on. Share your successes and post your question on the Golioth Forums. If you’re interested in learning how to add Golioth to your IoT fleet, get in touch with our Developer Relations crew.

References:

 

Over the holidays I tried out a fun experiment that combines Arduino and PlatformIO with Golioth. The work was prompted by a customer with an existing Arduino code base. Their long-term plan is to port the code over to ESP-IDF, but this serves as an intermediary step while they work out their own library transition plans. It works for them and today I’ll cover how you can give it a try for yourself. But first, some disclaimers.

Golioth Labs means Experimental

We use the Golioth Labs organization on GitHub for our experimental projects. That means you should not build for production devices using any of those repositories. This experiment targets the ESP32 family of chips. We have full support for ESP-IDF in the Golioth Firmware SDK, which is what we recommend using.

That being said, what if you have an existing Arduino project and want to test out some Golioth features? This is a “what am I getting myself into” step, and depends on your code being able to run on an ESP32 since the experiment builds Arduino as a component of ESP-IDF. The “cherry on top” wraps the project up into PlatformIO, an IDE built around VScode that has become a popular alternative to the native Arduino IDE.

Test Driving Golioth + PlatformIO + Arduino

As a prerequisite you need to install VScode and the PlatformIO extension for it.

With those tools in place, let’s walk through how to clone the Golioth repository and set up submodules, then finish up by building and running the demo program.

Clone the Repository and Submodules

By default, PlatformIO stores new projects in the ~/Documents/PlatformIO/Projects folder. You may clone this project anywhere you like, but that’s as good of a place as any.

Navigate to your preferred folder and clone the GoliothLabs repo and submodules:

# Clone the repository
git clone https://github.com/goliothlabs/golioth_platformio_arduino.git
# Enter the newly created folder
cd golioth_platformio_arduino
# Initialize the submodules (this will take more than 10 minutes)
git submodule update --init --recursive
# Clone a nested repository (yes, this is odd)
git clone https://github.com/hathach/tinyusb.git third_party/esp32-arduino-lib-builder/components/arduino_tinyusb/tinyusb

That’s it for setup. The last step in the commands listed above is an odd one. According to Espressif’s Arduino as an ESP-IDF component documentation, the tinyusb library needs to be nested inside of the esp32-arduino-lib-builder library’s components folder.

Build and Run the Golioth Sample

  1. Start by importing the project into PlatformIO.In VScode, click the alien icon on the left sidebar, choose PIO Home→Open from the sidebar that appears, and select Open Project from the PlatformIO home screen.PlatformIO open project
  2. Next, set up your credentials.Make a copy of the credentials.h_example file and rename it credentials.h. In this file, place the credentials for your WiFi access point, and the PSK-ID/PSK for the device you created using the Golioth Console.Note: With our Dev tier, your first 50 devices are free. Use the Credentials tab when viewing a device to access the PSK-ID/PSK.credentials.h file
  3. Now build the project.Click on the alien icon on the left sidebar and choose Build from the Project Tasks menu. A terminal window will open and the build process will begin.PlatformIO build process
  4. Finally, flash the device and observe the output.From the Project Tasks menu, click on Upload and Monitor. The project will build, upload to the ESP32, and then open a terminal window to show the serial output from the ESP32.

You will see an output similar to the following:

I (942) wifi:new:<6,0>, old:<1,0>, ap:<255,255>, sta:<6,0>, prof:1
I (944) wifi:state: init -> auth (b0)
I (952) wifi:state: auth -> assoc (0)
I (957) wifi:state: assoc -> run (10)
I (970) wifi:connected with golioth-staff, aid = 1, channel 6, BW20, bssid = c2:ff:d4:a8:fa:10
I (970) wifi:security: WPA2-PSK, phy: bgn, rssi: -39
I (977) wifi:pm start, type: 1

I (1041) wifi:AP's beacon interval = 102400 us, DTIM period = 2
.W (1556) wifi:<ba-add>idx:0 (ifx:0, c2:ff:d4:a8:fa:10), tid:0, ssn:1, winSize:64
␛[0;32mI (1727) esp_netif_handlers: sta ip: 192.168.1.157, mask: 255.255.255.0, gw: 192.168.1.1␛[0m
.IP address: 
192.168.1.157
␛[0;32mI (1936) golioth_mbox: Mbox created, bufsize: 2184, num_items: 20, item_size: 104␛[0m
␛[0;32mI (1937) golioth_fw_update: Current firmware version: 1.0.0␛[0m
Hello, Golioth! #0
␛[0;32mI (1953) golioth_example: Updating counter to: 0␛[0m
␛[0;32mI (1987) golioth_coap_client: Start CoAP session with host: coaps://coap.golioth.io␛[0m
␛[0;32mI (1988) golioth_coap_client: Session PSK-ID: platformio-test@golioth-arduino-demo␛[0m
␛[0;32mI (1998) libcoap: Setting PSK key
␛[0m
␛[0;32mI (2005) golioth_coap_client: Entering CoAP I/O loop␛[0m
Hello, Golioth! #1
␛[0;32mI (6955) golioth_example: Updating counter to: 1␛[0m
␛[0;33mW (9968) golioth_coap_client: CoAP message retransmitted␛[0m
␛[0;32mI (11492) golioth_coap_client: Golioth CoAP client connected␛[0m
Hello, Golioth! #2
␛[0;32mI (11957) golioth_example: Updating counter to: 2␛[0m
␛[0;32mI (13544) golioth_fw_update: Waiting to receive OTA manifest␛[0m
Hello, Golioth! #3
␛[0;32mI (16959) golioth_example: Updating counter to: 3␛[0m
Hello, Golioth! #4
␛[0;32mI (21961) golioth_example: Updating counter to: 4␛[0m
Hello, Golioth! #5
␛[0;32mI (26963) golioth_example: Updating counter to: 5␛[0m
^CHello, Golioth! #6
␛[0;32mI (31965) golioth_example: Updating counter to: 6␛[0m
Hello, Golioth! #7
␛[0;32mI (36967) golioth_example: Updating counter to: 7␛[0m

In this example, we are sending Arduino Serial.print() commands and Golioth logs that are displayed on the terminal and sent to the server. They show an upcounting timer that is stored on the Golioth LightDB state service. Over-the-Air (OTA) firmware update is also available.

While this shows a very small subset of Golioth features, everything shown in our golioth_basics example code can be used with this project.

What to do Next

As I mentioned earlier, you should not build production hardware around this repo. This is merely a way to test drive Golioth with your existing Arduino code base. The longer term goal should be to transition your ESP32-based hardware over to the ESP-IDF flavor of the Golioth Firmware SDK. If you’re using non-ESP32 hardware, you should target the Golioth Zephyr SDK.

While Arduino and PlatformIO are great platforms, directly targeting the ESP-IDF framework results in a more stable and predictable build. The Golioth SDK is built as a component of ESP-IDF for rock-solid performance. Building for ESP-IDF is definitely the end goal if you are migrating an existing fleet, and the best place to start if you are bootstrapping new designs.

Do you have questions about how to best integrate Golioth for managing your IoT devices? We’d love to hear from you! Post a message on the Golioth Forum, or reach out to our Developer Relations folks to set up a meeting.

The human body is surprisingly adept at sensing temperature. And so when I sat up in bed my body immediately informed me something was not right with my furnace. Yes, it’s winter in Wisconsin, but it shouldn’t feel that cold inside the house. The furnace was fine, but the thermostat was not, and so begins the story of how Golioth ran my furnace over Christmas.

Twas the Week Before Christmas and Cold in the House

HVAC control panel

EIM that connects to the furnace/AC/HRV. The red/green wires exiting the bottom of the photo go to the Golioth Greenhouse Controller

I arose from my slumber and went to check the thermostat to find it displaying a message that it could not connect to the Equipment Interface Module (EIM). We have a furnace, air conditioner, and a heat recovery ventilator (HRV is a fancy name for a pair of fans that exchange stale inside air with fresh outside air). Being a geek, I wanted one way to control them all. That meant installing the Honeywell EIM to switch those subsystems. The thermostat then connects wirelessly to the EIM to provide automated control. But the chilly morning we’re discussing here, the thermostat wasn’t connecting at all!

After basic troubleshooting I used a jumper wire to short the white “run the furnace now” wire to the 24-volt red wire. The furnace kicked on and started warming up the house. There was nothing wrong with the furnace, but I no longer had an automatic way to control it.

But wait…industrial control is one place where Golioth shines! Could I use Golioth as my furnace controller until the built-in system was repaired?

Connecting the Golioth Greenhouse Controller to My House

Golioth Greenhouse Controller connected to the EIM

The Golioth Greenhouse Controller was placed outside of the utility closet to sense temperature readings from the room.

Just a week before this happened I wrote a blog post about the Golioth IoT Greenhouse Controller. It includes relays that can be switched automatically based on a temperature sensor. This is the definition of a thermostat. And these Golioth reference designs are built to be easily adaptable to similar applications. The hardware connection was dead simple: just run the red and white wires to the temperature-controlled relay on the greenhouse controller.

The logic in the firmware needed a quick tweak as greenhouses regulate temperature by taking action when it gets too hot (the opposite of heating a house). This required the “expert” firmware engineering step of changing a greater-than sign to a less-than sign. Luckily all of our reference designs have Over-the-Air (OTA) firmware update built-in so I was able to make this change while the device was in my basement, still connected to the furnace.

The temperature threshold is set via Golioth’s web console, so at that point I had actually upgraded my home system to include control from anywhere in the world! I also got a dashboard to keep my eye on temperature, pressure, and humidity.

Eating Your Own Dog Food (and Enjoying the Flavor)

So, why didn’t I just go out and buy another thermostat? The first reason is the ongoing chip shortage (now into it’s third year). The EIM part that was broken is about $80 normally, but there’s a 31 week lead time on it right now. The second reason is that the only wires running from the furnace to the thermostat are for supplying power (+24 V and Ground). Not only would a replacement thermostat be temporary, I would need to run new wires to give it furnace control or leave it in the basement.

For me this was a great philosophy experiment. We spend a lot of time planning and developing reference designs. Now I know a bit more about the user experience, and I like it! The hardware form-factor was easy to work with, and the approaches we take to control and feedback worked well for my application.

Temperature graph with many on/off cycles

Temperature control without hysteresis

That doesn’t mean it was a perfect fit. Our basic design doesn’t have hysteresis built in, which means that the controller was trying to cycle more often than normal as there was no deadband to allow the house to warm (or cool) a bit past the target temperature. I implemented a simple cycle time threshold that prevented switching between on and off more than once in a 15 minutes period. It’s an application-specific topic that could be added to an implementation guide for this reference design.

Temperature and humidity graphs with smooth rise and smooth response due to cycle-time control.

Temperature response over the same time period was much smoother with simple cycle-time control

I called several HVAC shops in town and the earliest estimates for a new part was the end of March. Online I was able to find a Honeywell “starter” kit included the replacement EIM with just a week before delivery. During this time the Golioth-controlled furnace kept the house warm, but also getting very very humid without the HRV to help regulate. Good news everyone, the Greenhouse demo has a humidity sensor and a second relay. You guessed, it. I added control for the ventilation as well!

I Built An IoT Thermostat in 20 Minutes

Now, I was already familiar with this demo unit, so take this with a grain of salt. But the purpose of the Golioth reference designs is to give businesses a running start when developing a proof of concept. I was able to substitute this one in place of my thermostat with just about 20 minutes of effort. It kept my house comfortable in the harshest conditions of December’s arctic blast, and it let me check temperature and adjust settings while I was working outside of the home during that time. This is the Internet of Things, and it’s never been easier.

Adding Golioth’s device management features to an existing ESP-IDF project is a snap. The Golioth Firmware SDK has excellent support for Espressif parts. Simply add Golioth as a component in your build, enable it in the CMake and Kconfig files, and start using the API functions.

Today I’m going to walk through how to do this using an ESP32 development board, however, any of the ESP32 family of chips (ESP32s2, ESP32c3, etc.) will work. Advanced users will also want to review the Golioth Firmware SDK Integration Guide which is the bases for this post.

Walkthrough

Since this article is about adding Golioth to an existing ESP-IDF application, I assume you already have the ESP-IDF installed. If you do not, you may consider following our ESP-IDF quickstart guide before continuing.

For illustration purposes, I’ve copied Espressif’s /esp-idf/examples/wifi/getting_started/station example code to a folder called esp_hello_golioth and will add the Golioth device management features to the program.

cp -r ~/esp-idf/examples/wifi/getting_started/station esp_hello_golioth

If you are following along, you will want to set your WiFi credentials either by using menuconfig or by changing the default values in main/Kconfig.projbuild.

1. Install Golioth as a component

Add the Golioth Firmware SDK as a submodule of your project. (Note that this means you need to have your project under version control. Hint: git init)

cd ~/esp_hello_golioth
git submodule add https://github.com/golioth/golioth-firmware-sdk.git third_party/golioth-firmware-sdk
git submodule update --init --recursive

2. Add the Golioth SDK to your CMake configuration

We need to tell CMake to include the Golioth SDK in the build. This is done by editing the project’s top-level CMakeLists.txt file and adding the following line before the project() directive in that file:

list(APPEND EXTRA_COMPONENT_DIRS third_party/golioth-firmware-sdk/port/esp_idf/components)

We also need to add the Golioth SDK as a dependency. This is done by editing the deps list in the CMake file in the main directory of the project. For this ESP-IDF example code, there are no existing deps so I added the following to the top of main/CMakeLists.txt:

set (deps
     "golioth_sdk"
)

3. Add Kconfig settings

Golioth needs MbedTLS so it must be enabled via the Kconfig system. I created an sdkconfig.defaults file in the top-level folder and added the following:

CONFIG_MBEDTLS_SSL_PROTO_DTLS=y
CONFIG_MBEDTLS_PSK_MODES=y
CONFIG_MBEDTLS_KEY_EXCHANGE_PSK=y
CONFIG_GOLIOTH_AUTO_LOG_TO_CLOUD=1
CONFIG_GOLIOTH_COAP_REQUEST_QUEUE_MAX_ITEMS=20

The final two symbols were added because I plan to send logging messages to the Golioth servers.

4. Use Golioth API calls in your C code

Now that we’ve unlocked Golioth we can start making API calls. Gain access to the functions by including the Golioth header file:

#include "golioth.h"

For this demo I created a counter to keep track of a forever loop. After a five-second pause, the counter value is sent to Golioth as a Log message, then sent as a LightDB State value. The highlighted code beginning at line 14 is what I added.

void app_main(void)
{
    //Initialize NVS
    esp_err_t ret = nvs_flash_init();
    if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND) {
      ESP_ERROR_CHECK(nvs_flash_erase());
      ret = nvs_flash_init();
    }
    ESP_ERROR_CHECK(ret);

    ESP_LOGI(TAG, "ESP_WIFI_MODE_STA");
    wifi_init_sta();

    //Everything above this line is unchanged from the ESP-IDF example code

    const char* psk_id = "your-golioth@psk-id";
    const char* psk = "your-golioth-psk";

    golioth_client_config_t config = {
            .credentials = {
                    .auth_type = GOLIOTH_TLS_AUTH_TYPE_PSK,
                    .psk = {
                            .psk_id = psk_id,
                            .psk_id_len = strlen(psk_id),
                            .psk = psk,
                            .psk_len = strlen(psk),
                    }}};
    golioth_client_t client = golioth_client_create(&config);

    uint16_t counter = 0;
    while(1) {
        GLTH_LOGI(TAG, "Hello, Golioth! #%d", counter);
        golioth_lightdb_set_int_async(client, "counter", counter, NULL, NULL);
        ++counter;
        vTaskDelay(5000 / portTICK_PERIOD_MS);
    }
}

For simplicity I hardcoded the Golioth credentials (PSK-ID/PSK) which is not a best practice as hardcoded credentials will not survive a firmware upgrade. Both shell and Bluetooth provisioning are demonstrated in the golioth_basics example code which stores these credentials in non-volatile flash.

See the results on the Golioth Console

The serial output of this app shows the Golioth client connecting and Log messages being sent.

I (937) wifi:AP's beacon interval = 102400 us, DTIM period = 2
I (1647) esp_netif_handlers: sta ip: 192.168.1.159, mask: 255.255.255.0, gw: 192.168.1.1
I (1647) wifi station: got ip:192.168.1.159
I (1647) wifi station: connected to ap SSID:TheNewPeachRepublic password:123456789101112internetplease
I (1657) golioth_mbox: Mbox created, bufsize: 2184, num_items: 20, item_size: 104
I (1667) wifi station: Hello, Golioth! #0
W (1677) wifi:<ba-add>idx:0 (ifx:0, c6:ff:d4:a8:fa:10), tid:0, ssn:1, winSize:64
I (1677) golioth_coap_client: Start CoAP session with host: coaps://coap.golioth.io
I (1697) libcoap: Setting PSK key

I (1697) golioth_coap_client: Entering CoAP I/O loop
I (1917) golioth_coap_client: Golioth CoAP client connected
I (6707) wifi station: Hello, Golioth! #1
I (11707) wifi station: Hello, Golioth! #2
I (16707) wifi station: Hello, Golioth! #3
I (21707) wifi station: Hello, Golioth! #4
I (26707) wifi station: Hello, Golioth! #5
I (31707) wifi station: Hello, Golioth! #6
I (36707) wifi station: Hello, Golioth! #7
I (41707) wifi station: Hello, Golioth! #8
I (46707) wifi station: Hello, Golioth! #9

Both Log messages and State data can be accessed from the tabs along the top of the device view in the Golioth Console. Navigate there by selecting Devices from the left sidebar menu and then choosing your device from the resulting list.

The log view shows each message as it comes in, and from the timestamps we can see that the five-second timer is working. For long-running operations, there are time-window selection tools as well as filters for log levels, devices, and modules.

The LightDB State view shows persistent data so you will always see the most recently reported value at the “counter” endpoint. Make sure the refresh dialog in the upper right is set to Auto-Refresh: Real-time to ensue you see the updates as they arrive from the device.

Give Golioth a try!

We built Golioth to make IoT design easier for firmware developers. We’d love it if you took our platform for a test-drive! With our Dev Tier, your first 50 devices are always free. Grab an ESP32 and you’ll have data management, command and control, and OTA firmware update in your toolbelt in no time.