When building IoT applications for a simple application space, it pays to build on top of already working systems. That’s the case for the hardware we’re showing today, the Aludel Lute Relay.

This design started as an experiment for a new form factor to plug into our Aludel Elixir Board. The unit has 4 relays capable of switching 20 amps AC or DC. This unit could be useful in remote physical access (i.e. locks), lighting control, or the application that inspired this design: smart lockers. Let’s dive into how this is built and how someone could use this latest Golioth Solutions Marketplace design to build a business.

New form factor

The Aludel platform generally assumes that you will have a somewhat tall case that can fit two different Mikroelektronika Click Boards into the case and then cover it with our Open Source board called the Aludel Ostentus. The Aludel Elixir is the main processing board and contains an nRF9160 primary processor and cellular modem and an ESP32-C3 secondary modem for Wi-Fi and Bluetooth.

If I wanted to build custom hardware to work with the Elixir board, I would probably need to make a Click board of my own. But that is somewhat space constrained. Instead, I decided to take the idea of using a PCB as a cover for this form factor (as is the case for the Ostentus) and instead put the customized components on this new ‘front panel’. The result is the Lute form factor, which can be extended beyond the current implementation. Relays are a great proving ground for large industrial-style components that can control large industrial sized machines.

 

Control Power from Anywhere

This hardware is a great fit for applications where you want to remotely control power. The 4 relays on board and the large 5.08 mm pitch terminal blocks means you can safely pass high currents through the board. The traces on the relay are configured for creepage and clearance required for 240V. The relays being used are marked for 120V, but the datasheet says they are capable of 240V.

This application draws on my own experience building a simple smart locker using Golioth. I built it using an nRF9160-based dev board and then point-to-point wired a bunch of off-the-shelf power controls. It worked great, as the control requirements are low: nothing more than a GPIO is required for output. However, from a manufacturing capability and scaling to a larger fleet of devices, the wiring would have been a horrendous experience. The Lute Relay board codifies the ability to switch power and enables it in a much more compact package. In my case, I was passing 12V through the relays to a set of LED lamps inside the locker and to the solenoid controlling the door latch.

The relays truly are the easy part. The important piece in the system starts from the GPIO (controlling the relays) and goes all the way back to the cloud. The firmware is built on top of Golioth’s Reference Design Template. We trigger the various GPIOs within app_settings.c, which is triggered from the Golioth Settings Service. When we change the relay setting on the console, it gets pushed out from the Golioth service down to the device over CoAP, and then the Golioth Firmware SDK translates the packet and triggers the proper GPIO.

Made for extensibility, not durability

The Lute Form factor, like most of the devices in the Solutions Marketplace, is a starting point, not an off-the-shelf product. We intend for you to take these devices to prove an idea and then modify them for the specifics of your business. You can hire Golioth Solutions Services to help you with that, tap one of our wonderful Design Partners, or take on the task yourself.

What about future Lute boards? Well, the testing of this board has created a great starting point for future designs and applications. If you have one in mind, get in touch via our forum and let us know what you’d like to see us build next.

Watch the Livestream playback

We live streamed the entire creation flow of this board. You can watch it below.

As a hardware engineer, Zephyr can be a bit confusing at first. I am used to tweaking registers, not layering file upon file. Today we’ll talk about how those layers and overlays can help you to configure your device differently than the default board startup sequence.

Devicetree (Re)Introduction

There are two pieces to the configuration of any board in Zephyr: KConfig and Devicetree. The former I think of like a control panel, toggling switches on and off to tell the West build system whether or not to include something. I recently did this to enable the i2c shell on a project, which pulled in code that might be unnecessary for production but helped me troubleshoot a problem. The latter (Devicetree) describes the various elements that make up your board, SoC, and architecture. As I said above, these are really layered on top of one another, which is a source of confusion for new Zephyr developers.

The topmost level of the devicetree is the board setting, and this is where I find myself making changes most often. If you include a boards/<boardname>.overlay file in your application, the build system will pull in those changes and give priority to them during the build. We often write about how we use this for swapping out a sensor, say on the i2c bus, and then add commands to control that sensor in the application code, like in main.c or app_sensors.c. Super useful!

Another key piece of knowledge for newcomers is that Zephyr has many development boards and products included in the overall repository. I find this to be critical for quickly trying out a new board and having sensible defaults for the built in Zephyr samples. Without these, I would be required to build a sample up from scratch, and the last thing I want to be doing is configuring clock trees and digging through register sheets just to blink an LED for the first time.

But with the good, comes the bad. The defaults of the board, aren’t always what we want. And in today’s example, those defaults were put there by…us!

Why not just change the board file?

When you’re in control of the entire board definition, this can make sense. But it’s not the default I would go for. Much like we won’t dive down into the SDK and change some fundamental aspect of the RTOS or a plugin, I wouldn’t normally go and change the board file. It changes the behavior on your computer for your future self and also it might not be tracked in your application’s repo, which could cause issues when building on different systems.

An Example in Action

For this example we’re looking at the Aludel Elixir board running the Reference Design Template. For the RD Template we employ a separate golioth zephyr boards repository called out in the west manifest of the Template. What this means in practice is we control an ‘official’ copy of the Zephyr board files for this hardware we make (a useful tip for your organization’s needs). In the aludel_elixir_common.dtsi file you can see that we call out the behavior of the 3v3 and 5v regulators onboard. This was the subject of the post about Enabling Power Regulators at Boot.

Well, guess who changed his mind? (me)

Now that I want to not enable the regulators at boot. Normally, this would be a simple overlay for a characteristic, say the delay times:

&reg_mikrobus_3v3 {
    startup-delay-us = <5000>;    /* 5ms - adjust as needed */
    off-on-delay-us = <5000>;     /* 5ms - adjust as needed */
};

But in the case of regulator-boot-on, it’s simply a property that is included or not, so there’s no way to set it to true or false. Instead, I need to delete it entirely. In my overlay, it would look like this:

&reg_mikrobus_3v3 {
    /delete-property/ regulator-boot-on;
};

Now when I build with west, the board won’t enable that regulator automatically, but I’ll still be able to configure it during runtime. This is exactly what we did using the Golioth Settings service in our recent Joulescope demo, which happened to be the genesis of this article/topic.

Deletion can also happen at the node level. This occurs when you want to change the behavior or the existence of some element of your configuration. For instance, say I wanted to change the button configuration for the mode_button in the Aludel Elixir board file:

/* Replace a button with different GPIO configuration */
/delete-node/ &mode_button;
mode_button: mode-button {
    gpios = <&gpio0 12 (GPIO_PULL_DOWN | GPIO_ACTIVE_HIGH)>;
    /* additional properties */
};

This would allow me to keep the tooling in the rest of my code but potentially target a different behavior of that particular button. Say some irresponsible hardware engineer ripped a trace off the board and the only setup that would work after repairing it requires a new pin configuration; that would never happen with me, though, right?

Learning to climb the (Device)tree

Despite my trepidation about configuring the devicetree for a project, it really enables a lot of configuration tweaks, which suits the needs of hardware engineers everywhere. Learning how to modify your devicetree overlays to enable new capabilities will unlock the ability to prototype new parts at a moment’s notice. When you’re ready to send that data to the cloud, simply pop on the Golioth Firmware SDK and send some data through friendly device-side APIs. If you get stuck, we’re always happy to help over on the Golioth Forum. Happy hacking!

Firmware versioning is a crucial part of ensuring the binary you’re about to load onto your device is the correct arrangement of bits that will control the hardware how you want. It is fundamental to a system like Golioth’s Over-The-Air update service. Today we’re going to look at different ways that you can tell which version you’re running in Zephyr and how your firmware update is correct the first time and every time. We’ll use Golioth Reference Designs as an approximation of what a product design would do for the devices in their fleet.

How we generate firmware versions

Around these parts, we adhere to Semantic Versioning, or SemVer. It’s a useful way to assign incremental changes to a firmware image. It also matches how Zephyr increments versions, and how the Golioth SDK is versioned. So…it’s a good idea to have any binaries we create for our Reference Designs to also have a SemVer attached. As you can see in the video, we also able to detect versions and show interesting elements when the images adhere to SemVer. We expect our users to follow a similar format (especially if using MCUboot), though Golioth is flexible across different version methods.

For Reference Designs, the firmware version is reported through the bootloader; MCUboot in the case of our Zephyr Designs. That means when the image is being loaded by MCUboot, it is reading the version embedded in the image.

Previously, we needed to do this manually. At some point in the generation of a firmware binary, the firmware engineer needs to decide this is the version that will be released as 1.2.3 (or whatever number is next in your internal process). That’s where things can get tricky, because humans are often in the loop. Perhaps we have a commit in Git like 1a2b3c4dand we want to attach the version 1.2.3 to it. Well, we would do that on the command line… a place I notoriously mess things up! See more in the last section (historical footnote) for more info.

We changed how we do things in Reference Designs starting in v2.4.0 of the RD Template. We now use a controlled VERSION file, following the guidance of the Zephyr Application Version Management system. This has been working out great. We store the file in the repository and it gets incremented (and tracked!) like any other file.

Where can I see it?

OK, so we have the version number embedded into the binary, but how do I actually see it? We can do this in a couple different places: the log output, the MCUboot shell, all the Golioth console.

Application load and boot

When built on top of nRF Connect SDK and Zephyr, you can see logging output for the version in our main application code. This application also shows the firmware version of the modem and the attached Ostentus display firmware (when present).

The MCUboot shell

This shell is not enabled by default on the Reference Design Template, so I turned it on by adding CONFIG_MCUBOOT_SHELL=y to prj.conf in my file. This gives me a new shell menu that shows my regions. Super useful when trying to troubleshoot OTA issues.

On Golioth’s Devices -> Firmware Tab

Part of the OTA code in the Golioth SDK is that it reports back the current version and the current state of OTA, so you can see where you are in the midst of an update. This is a great place to see the last reported version. The same information is available on the Summary tab of the same page.

On Golioth’s Firmware Updates -> Cohorts Tab

This shows the history of packages, which will include the one currently running

On Golioth’s Firmware Updates -> Packages Tab

This is where you upload new images to be included in future deployments and where Golioth extracts the MCUboot version and uses it as another check against existing images on the server. We also point out when you’re trying to upload a similar image you have uploaded before.

Keeping your deployments organized

Attaching a SemVer to your binary won’t solve all your problems, but it will help you organize the range of firmware images you’re sending out to your devices. Golioth helps to check your versions on the cloud and seamlessly deliver the package to eligible devices. If you have questions about how to build or deploy your next firmware image, let us know in the forum!

 

Historical Footnote

When we used to have to call out the image version on the command line, it was a mess. I recall a scenario where I made a change to my firmware and re-used the last build command which also happened to have the version number attached. But that’s because we’re not actually using a controlled document to set the version. Sure, I might have a tag in GitHub that says 1.2.3, but there’s no mandate for the firmware to adhere to that so I needed to do that manually before. Note that there is a --which escapes the west build command and then the next DCONFIG command, which actually sent the version to CMake to include in the build.

west build -p -b nrf9160dk_nrf9160_ns samples/dfu -- -DCONFIG_MCUBOOT_IMAGE_VERSION=\"1.2.3\"

Note: the above code will not work for modern versions of Zephyr

What if you kept the “core” or the “essence” of a board the same throughout your designs and only changed the peripherals? You could build a range of products that had similar processing capabilities, but might be completely different products. By defining an abstraction layer at the edge of the core module you design, it’s possible to achieve efficiencies for your variety of products, including when those products are manufactured over time.

The above video is a talk given at the Zephyr meetup hosted by NXP at Embedded World North America in October of 2024. I focus on modular hardware design for scalability and adaptability. The Drachm (pronounced “dram”) module is something we have been regularly streaming on YouTube. This talk describes some of the motivations, the challenges of unconstrained designs, and how to build modular systems that take advantage of Zephyr RTOS capabilities.

Many SKUs, One Core

The Drachm module was conceived to be an extension of the Aludel Platform. We currently target a range of different verticals and end-applications with Golioth Reference Designs. But these are somewhat bulky: made to accept MikroBus Click boards, the case measures roughly 80 x 80 x 40 mm. What if we want to go smaller, but not re-design the board every time we have a new product idea?

The Drachm module was meant to achieve this on multiple fronts. The castellated edge design would allow us to have a standard offering that covers a range of peripheral we want to hook up to the core module. But the module itself can change as well. The different versions of the Drachm module offer different capabilities of communication, processing power, and peripheral access on the underlying silicon. As in many modular systems, we trade off flexibility for an overall reduced feature set.

The Challenge of Hardware Changes

Modularity at the module connector edge benefits long term manufacturing as well. Building scalable IoT hardware requires constant adaptability. Component shortages, changing device requirements, and new tech trends demand flexible designs. Traditional methods, such as swapping processors or upgrading communications modules, can be tedious and impractical without a modular system.

How it works in Zephyr 

This talk built on top of the article we published about abstract hardware interfaces in Zephyr. The key change is how we think of each component of the design. The module is being mounted to a carrier board, so it seems like a component of the carrier board’s BOM and software system. But in fact, we need to think about the carrier board as a “shield” (in Zephyr/Arduino) parlance, and instead map pins based on how we define the module edge (castellated pins). See the linked article on how to use predefined node labels and GPIO nexus nodes to achieve those ends.

Another key piece is that each module variant would need to maintain its own board files (Devicetree and Kconfig) to match up the pins on the castellated edge connection to the silicon onboard. The “analog input 1” pin on the drachm-1 variant (nRF9151 + ESP32-C3) would have a different mapping than the drachm-2 variant (nRF52480 + BG77), but to the parts on the shield / carrier board, it’d be all the same, since they only interface via the “analog input 1” nomenclature.

How it fits with Golioth

Golioth works great with customized hardware, which is subject to the harsh realities of sourcing. Engineers who need to redesign their hardware to upgrade or even just keep their production line running don’t want to re-do every aspect of their design, including firmware and cloud software. That’s why engineers are increasingly choosing Zephyr to target many hardware SKUs and why they’re choosing Golioth to help create a seamless experience for those variety of targets.

Using Golioth’s features like Blueprints, Tags, and our new OTA Cohorts capability an engineer could deliver different main firmware updates to boards using drachm-1 or drachm-n modules, but still have a consistent set of AI models being delivered to all eligible units. Golioth can also help to target different cohorts in your fleet to ensure the right updates are going to the right devices in the field.

Want to create a more resilient fleet? We can help! Our Solutions Engineering team can help to introduce more modularity and have a robust OTA strategy for your fleet. If you’re interested in doing it yourself but you’d like to talk through the options with Golioth team members, head over to our forum to start a conversation there.

Slides

Golioth-Module-Abstraction-Layers-Zephyr-Meetup-Oct-9th-2024

Today we are highlighting the AL2LOG, a cellular data logger by Golioth Design Partner AL2TECH. This is the latest entry into the Golioth Solutions Marketplace, announced last week. This device is a great way to capture a wide range of input data from an industrial setting, especially when you don’t know every type of measurement you want to take at the start of your project. You can deploy an AL2LOG to your factory floor or rugged environment and start monitoring signals and processing data on the cloud.

The AL2LOG is a general purpose logger

Let’s take a look at some of the specifications and use cases for the AL2LOG.

The case and PCB of the AL2LOG cellular data logger

A range of input options

The name of the game with the AL2LOG is flexiblility. That’s why you can interface it with the following signals from all types of sensors

  • GPIO
    • Digital Input
    • Dry Contact
    • Pulse Counter
  • CAN
  • RS-485
  • 4-20 mA
  • 0-10 V

Industrial users will recognize many of those signal types. Golioth has some Reference Designs that include more bespoke monitors targeted at specific verticals, like the CAN Asset Tracker and the MODBUS (RS-485) Monitor.

When you need to send control signals out into the world, you can do so with a programmable
power output (5 to 12V) for devices downrange of the logger, open drain outputs, as well as a 220VAC smart switch.

The device is powered off of two massive D-cell batteries for a 10 year battery life, but can also be powered from external sources when you want an even longer lifetime (or higher power utilization).

Built for speed, but not high power drain

While this design is based around the nRF9160 LTE-M / GPS / NB-IoT modem, it also relies on a higher speed ADC for advanced data capture. This Ultra Low Power acquisition and logging subsystem utilizes a true 14 bit ADC, and can efficiently capture high resolution data and cache it before sending it back to the Golioth Cloud. Utilizing Golioth’s new Batch Pipeline Destination, it’s super easy to bundle up a slug of readings and send them to the cloud to be unpacked later. Read about how Mike showcased sending data in batches.

A Case Study on AL2TECH’s Client Work

We recently profiled AL2TECH and their work with Hubwater, also seen in the video above. Hubwater is reducing waste and improving water distribution at commercial locations in Italy and beyond.

Read the full case study of AL2TECH and Hubwater

Some key points from that case study include how quickly they were able to get up and running using Golioth, and how they get to continually offer new services to their clients by utilizing Golioth Pipelines and OTA.

More Solutions Coming Soon

This is only the second of many solutions in our Golioth Solutions Marketplace. We are excited to share additional work of our Design Partners and Golioth Reference Designs. If you have a solution you’d like to share with Golioth and the world, please get in touch!

Golioth is all about sharing ideas and knowledge. This now includes our hardware, which today has been released as open source under the CERN OHL-P license. The Aludel Elixir and Ostentus are available on GitHub as KiCad projects and you can use them to start building your own designs.

The Aludel Platform

As an IoT platform that doesn’t build or sell commercial hardware, we still need to find ways to highlight the capabilities our platform offers. This often includes building on top of development boards from our various partners. Engineers are used to starting from well known vendor platforms, like some of our favorites which are in the list of Continuously Verified Boards (CVBs) on the Golioth platform:

When you’re ready to start building devices that look more like real-world products, however, it becomes a bit bulky to use the easy-to-access development platforms. We knew we wanted to capture this “magic in a box”, which was the genesis of the name — an “aludel” was a sublimating pot in alchemy.

We started using alternative boards like the nRF9160 Feather from Circuit Dojo and encapsulating the Feather form factor in a box. The early Aludel boxes were much larger and included headers to plug in different boards. Over time, we knew we wanted to more closely approximate designs that will go out into the field, so we downsized to the current ‘Mini’ version that utilizes the Bud Industries CU-1937-MB Utilibox and then we started modifying it and adding 3D printing elements. That formed the basis of the new Aludel platform, which includes two boards we’re releasing today.

Elixir


The Elixir Hardware Repository is now open source on GitHub

The Elixir board is a natural offshoot of the Aludel Mini form factor, including the new case we adopted. We had first created a board called the aludel-mini which you’ll see mentioned in some of our reference design repositories. This was an interposer board between a soldered down nRF9160 Feather and some mikroBUS Click headers. However, we started to experience some limitations with how the board operated and wanting to add other features. This is when we planned to build the Elixir.

The Elixir has many of the same elements from the OSHW Feather board, and adds on new capabilities that are common to many of our designs:

  • nRF9160 – Cat M1 / NB-IOT / GPS chip with dual core Cortex M33 processing
  • ESP32-C3 – Running ESP-AT firmware, interfaced to the nRF9160 over UART
  • BME280 – Weather sensor from Bosch, common to many Golioth designs
  • Power
    • Low quiescent buck circuit, inherited from Feather Board
    • 5V boost to service the 5V output on the mikroBUS
    • Battery charging
    • Fuel gauge
    • High voltage input (5.5V to 48V) for powering from things like OBD-II and industrial supplies
  • LIS2DH – Accelerometer, inherited from Feather board
  • PWM Buzzer – Allow audible indication similar to the Thingy91
  • Real Time Clock
  • Secure Element (ATECC608B)
  • Power Switching – Power switch of 3V3 for sensor nodes to shut down in low power situations
  • QWIIC / STEMMA Headers – External sensors and peripherals including the Aludel Ostentus
  • mikroBUS headers
  • USB C interface and power
  • External SIM connector
  • Multiple programming interfaces
    • 10 pin SWD (02×05 .127mm pitch), accessible from outside the case
    • 10 pin SWD Tag-Connect (accessible from the top side of the board)
    • USB using the serial bootloader / MCUBoot

Ostentus


The Ostentus Hardware Repository is now open source on GitHub

Early Reference Designs were simply aludel-mini boards in the Utilicase. We utilized vinyl stickers to differentiate between designs, but this didn’t have the impact we were looking for when we went to tradeshows or shared photos on our projects site. What’s more, it didn’t represent a product that might be on-site at an industrial facility (more on the ‘field readiness’ in a bit).

If you walk up to a sensor monitor at a plant, you likely don’t want to pull out a phone and require a network in order to interact with the device in front of you. Having a simple, power efficient, persistent display that is visible in high brightness environments felt really useful. In fact, our early design hypothesis is that you’d still want to have readings on the screen, which is why the Ostentus continues displaying data even when the QWIIC header has been powered down from the main board (in low power modes). That trick is handles using ePaper displays.

Over different iterations we added other capabilities to allow user interactivity. Capacitive touch buttons around the bezel of the screen and an accelerometer for sensing double tap actions through the case deliver user input in the field.

  • Raspberry Pi Pico – Originally chosen during the part shortages of 2022-23, the RP2040 on the Pico provides a low cost solution including a programming interface over the USB port + MicroPython ease-of-use
  • ePaper interface – This design was derived from the Pimoroni Badger 2040 and that had a reference circuit for interfacing to a Good Display 1.54in square ePaper display (not cheap!)
  • Accelerometer (LIS2DH12) — Currently unimplemented but targeting “double tap” behaviors in the future
  • 3 button capacitive touch controller (CAP-1203-1)
  • QWIIC header for incoming power and i2c
  • Downward firing LEDs

Two critical pieces of firmware power this part of the platform. We utilized i2c in order to cut down on the number of I/O required on the main board (in this case the Elixir) and also to abstract it for future boards that might differ from the Elixir:

License and certification

These designs are being released under the CERN Open Hardware License – Permissive, version 2. This is one of the most permissive hardware licenses available. This board has not yet been certified by OSHWA, but we believe the hardware and files comply with the OHSWA definition. No FCC, CE, or cellular carrier certification is guaranteed with these boards and users should expect to take any resulting product through the proper regulatory channels.

Field Readiness

One thing to note is that the Ostentus plus the cut-out version of the Bud Industries case results in an unrealistic deployment option for the platform. I would never feel comfortable sending these boards out into a harsh environment, let alone a slightly damp one, due to the fact that there are gaps in the case and there is no IP rating. However, we didn’t design it for high reliability or even production: the Aludel platform is truly to showcase the widest variety of use cases and maximum flexibility.

This is why you’ll often hear us discussing how this is an “80% done design”. We expect our users to take these designs and shrink them, harden them for environments, and extend them for specific use cases. This could also include doing rounds of Design for Manufacturing (DfM), doing cost optimization, and improving testing for high volume production. Also important are taking the designs through FCC Part 15B certification and any required cellular carrier certification (which depends on which carrier you use and their requirements).

All of those things are left to you, dear reader. We followed the best design practices that we know, though we are always open to learning more and receiving feedback on our forum and issues filed on our GitHub repositories. You can start to evaluate whether this custom hardware is the right path by starting from our range of Follow-Along Hardware and open source firmware on the Golioth Reference Design Project site.

IoT is all about data. How you choose to handle sending that data over the network can have a large impact on your bandwidth and power budgets. Golioth includes the ability to batch upload streaming data, which is great for cached readings that allows your device to stay in low power mode for more of the time. Today I’ll detail how to send IoT data in batches.

What is Batch Data?

Batch data simply means one payload that encompasses multiple sensors readings.

[
    {
        "ts": 1719592181,
        "counter": 330
    },
    {
        "ts": 1719592186,
        "counter": 331
    },
    {
        "ts": 1719592191,
        "counter": 332
    }
]

The example above shows three readings, each passing a counter value the represents a sensor reading, along with a timestamp for when that reading was taken.

Sending Batch Data from an IoT Device

The sample firmware can be found at the end of the post, but generally speaking, the device doesn’t need to do anything different to send batch data. The key is to format the data as a list of readings, whether you’re sending JSON or CBOR.

int err = golioth_stream_set_async(client,
                                   "",
                                   GOLIOTH_CONTENT_TYPE_JSON,
                                   buf,
                                   strlen(buf),
                                   async_push_handler,
                                   NULL);

We call the Stream data API above. The client and data type are passed as the first two arguments, then the buffer holding the data and the buffer length are supplied. The last two parameters are a callback function and an optional user data pointer.

Routing Batch Data using a Golioth Pipeline

Batch data will be automatically sorted out by the Golioth servers based on the pipeline you use.

filter:
  path: "*"
  content_type: application/json
steps:
  - name: step0
    destination:
      type: batch
      version: v1
  - name: step1
    destination:
      type: lightdb-stream
      version: v1

This example pipeline listens for JSON data coming in on any stream path. In step0 it “unpacks” the batch data into individual readings. In step1 the individual readings are routed to Golioth’s LightDB stream. Here’s what that looks like:

Note that all three readings are coming in with the same server-side timestamp. The device timestamp is preserved in the data, but you can also use Pipelines to tell Golioth to use the embedded timestamps.

Batch Data with Timestamp Extract

For this example we’re using a very similar pipeline, with one additional transformer to extract the timestamp from the readings and use it as the LightDB Stream timestamp.

filter:
  path: "*"
  content_type: application/json
steps:
  - name: step0
    destination:
      type: batch
      version: v1
  - name: step1
    transformer:
      type: extract-timestamp
      version: v1
    destination:
      type: lightdb-stream
      version: v1

Note that we didn’t even need an additional step, but simply added the transformer to the step that already set lightdb-stream as the destination.

You can see that the Linux epoch formatted timestamp has been popped out of the data and assigned to the LightDB timestamp. Extracting the timestamp is not unique to Golioth’s LightDB Stream service.

Streaming data may be routed anywhere you want it. For instance, if you wanted to send your data to a webhook, just use the webhook destination. If you included the extract-timestamp transformer, you data will arrive at the webhook with the timestamps from your device as part of the metadata instead of nested in the JSON.object.

Using a Special Path for Batch Data

What happens if your app wants to send other types of streaming data beyond batch data? The batch destination will automatically drop data that isn’t a list of data objects. But you might like to be more explicit about where you send data and for that you can easily create a path to receive batch data.

filter:
  path: "/batch/"
  content_type: application/json
steps:
  - name: step0
    destination:
      type: batch
      version: v1
  - name: step1
    transformer:
      type: extract-timestamp
      version: v1
    destination:
      type: lightdb-stream
      version: v1

This pipeline is nearly the same as before with the only change on line 2 where the * wildcard was removed from path and replaced with "/batch/". Now we can update the API call in the device firmware to target that path:

int err = golioth_stream_set_async(client,
                                   "batch",
                                   GOLIOTH_CONTENT_TYPE_JSON,
                                   buf,
                                   strlen(buf),
                                   async_push_handler,
                                   NULL);

Although the result hasn’t changed, this does make the intent of the firmware more clear, and it differentiates the intent of this pipeline from others.

Sample Firmware

This is a quick sample firmware I made to use while writing this post. It targets the nrf9160dk. One major caveat is that the function that pulls time from the cellular network is quite rudimentary and should be replaced on anything that you plan to use in production.

To try it out, start from the Golioth Hello sample and replace the main.c file. This post was written using v0.14.0 of the Golioth Firmware SDK.

Wrapping Up

Batch data upload is a common request in the IoT realm. Golioth has not only the ability to sort out your batch data uploads, but to route them where you want and even to transform that data as needed. If you want to know more about what Pipelines brings to the party, check out the Pipelines announcement post.

Embedded systems, like any software system, benefits from modularizing software components, especially as they approach production. In this talk at the Embedded Open Source Summit 2024, Golioth Firmware Lead Sam Friedman talks about how to create “microservices” for microcontrollers. He maps a popular web concept onto existing software characteristics in Zephyr and shows how a real-world example can benefit from truly modular software.

Mapping a web concept to the microcontroller realm

As Sam points out early in this talk, it’s not really about microservices, because that’s a web concept. A microservice on the web is a piece of software, normally deployed onto cloud infrastructure, that can stand alone. It has defined inputs and outputs (APIs) and can operate independent of any other microservice. This helps for scalability and testing, but is a general trend in web software and deploying applications.

Microcontrollers are smaller and traditionally operate more like a “monolith” (another web term) because everything is interconnected. But there are concepts like Inter-Process Communication (IPC), which allows constrained devices to have similar ideas. IPC is a computer science idea that helps to optimize communication inside of operating systems. As it so happens, Zephyr is a (real time) operating system. Let’s look at what these are in practice.

How firmware developers can benefit

Sam describes how the concepts of Tasks, IPC, and Event Tasks are defined and might be used. But it is the Zephyr analogs that highlights familiar features, like the relatively new ZBus methodology. If a user adds a listener on the ZBus, they can listen (subscribe) for a particular value (topic) on the bus and take action based off of it. This helps to make the overall system more modular, because the addition or removal of a feature is not deeply integrated between elements of the system. Instead, the new piece of code is reacting to data put on the bus, which reduces interdependency and improves test areas.

Real-World Example

Sam drives home his point by talking about a Golioth Reference Design like the Cold Chain Asset Tracker and how we can add capabilities like an onboard alarm when we hit a temperature threshold. Previously, this would have required refactoring to also send data from the sensor process to a new module that containes the alarm code. But with something like ZBus, the alarm can simply listen for a topic on ZBus and when the temperature module publishes to that topic, all relevant parties are updated.

This works in the opposite direction as well. Code written with this in mind would not break any future builds if a hardware cost-down removed an element like a front panel display. Instead, the user chooses not to build in that portion of the code (memory savings, yay!) and other parts of the code are not negatively impacted.

Bringing together the Cloud and Embedded Developers

Sam’s talk showcases what Golioth does well: match up the capabilities of the Cloud with the capabilities of an embedded system. Often many of the key ideas from computer science are more onerous to implement on a constrained system like a microcontroller, but Zephyr’s growing software toolbox makes it easier than ever to build a modular, testable system. Check out Sam’s talk above and his slides below for more context into how to build such a system.

 

We got an early look at Nordic’s new cellular modem, the nRF9151, and it already works with Golioth!

With any new board, we ask ourselves “can we connect it to Golioth?”. You may remember a similar post when the nRF7002-DK first came out. Of course the answer for these two boards, and pretty much all other network-enabled embedded systems, is: yes, you can use them with Golioth. So today we’ll walk though the experience of connecting the nRF9151 to Golioth for the first time.

What’s new with the nRF9151?

We love the nRF9160 cellular modem and have support for it in all of the Golioth Firmware SDK samples, as well as using it in the Hardware-in-the-Loop (HIL) testing that is connected to our continuous integration infrastructure. So what’s the deal with the new part?

Finger pointing at a small rectangular chip (SOC)Most obviously, it’s really really small. The 9151 is about a 20% size reduction from the 9160 (new dimensions are approximately 11×12 mm). Here you can see it’s smaller than the fingernail on my pointer finger. The smaller sized also delivers lower peak current consumption. As with the recently announced nRF9161, the nRF9151 supports DECT NR+. And Nordic indicates the new design is fully compatible with the existing nRF91 family of chips.

This is also the first Nordic dev board I’ve seen that uses a USB-C connector. While you can’t get your hands on one of these just yet, since Golioth is partners with Nordic they were kind enough to send us one of these nRF9151 Development Kits to take for a test drive.

Building Golioth examples with the nRF9151

This board is not yet available to order, but support has already been added to Zephyr. To get it working with Golioth, we needed a fix that Nordic merged after their v2.6.1 release of the nRF Connect SDK (NCS). So today I’ll be checking out a commit in between releases. When Nordic releases v2.7.0 everything will work without this extra step.

0. Install the Golioth Firmware SDK

You will need an NCS build environment along with the Golioth SDK. You can follow the Golioth Docs to install an NCS workspace, or add Golioth to your existing NCS workspace.

1. Update NCS version (if needed)

If you are using NCS v2.7.0 (not yet released at the time of writing) or newer, you can skip this step. Otherwise, edit your west manifest and update the NCS version. Below is the west-nrf.yml file from the Golioth SDK with the changed line highlighted.

manifest:
  projects:
    - name: nrf
      revision: 85097eb933d93374fe270ce4c004bea10ee80e97
      url: http://github.com/nrfconnect/sdk-nrf
      import: true

  self:
    path: modules/lib/golioth-firmware-sdk

This happened to be the commit at the tip of main when writing this post. We usually recommend against targeting commits in between releases, so consider this experimental.

2. Add a board Kconfig file for the nRF9151

Add the board-specific configuration to the boards’ directory. For today’s post, I’m building the Golioth stream sample so I’ve added this nrf9151dk_nrf9151_ns.conf board file to that sample directory.

# General config
CONFIG_HEAP_MEM_POOL_SIZE=4096
CONFIG_NEWLIB_LIBC=y

# Networking
CONFIG_NET_SOCKETS_OFFLOAD=y
CONFIG_NET_IPV6=y
CONFIG_NET_IPV6_NBR_CACHE=n
CONFIG_NET_IPV6_MLD=n

# Increase native TLS socket implementation, so that it is chosen instead of
# offloaded nRF91 sockets
CONFIG_NET_SOCKETS_TLS_PRIORITY=35

# Modem library
CONFIG_NRF_MODEM_LIB=y
CONFIG_NRF_MODEM_LIB_ON_FAULT_APPLICATION_SPECIFIC=y

# LTE connectivity with network connection manager
CONFIG_NRF_MODEM_LIB_NET_IF=y
CONFIG_NRF_MODEM_LIB_NET_IF_AUTO_START=y
CONFIG_NRF_MODEM_LIB_NET_IF_AUTO_CONNECT=y
CONFIG_NRF_MODEM_LIB_NET_IF_AUTO_DOWN=y

CONFIG_NET_CONNECTION_MANAGER=y
CONFIG_NET_CONNECTION_MANAGER_MONITOR_STACK_SIZE=1024

# Increased sysworkq size, due to LTE connectivity
CONFIG_SYSTEM_WORKQUEUE_STACK_SIZE=2048

# Disable options y-selected by NCS for no good reason
CONFIG_MBEDTLS_KEY_EXCHANGE_DHE_PSK_ENABLED=n
CONFIG_MBEDTLS_KEY_EXCHANGE_DHE_RSA_ENABLED=n

# Generate MCUboot compatible images
CONFIG_BOOTLOADER_MCUBOOT=y

3. Build the Golioth stream sample

Building and running this sample is now quite simple. I have included the option to use runtime credentials in this build so that we can provision the device from the Zephyr shell.

$ cd examples/zephyr/stream
$ west build -b nrf9151dk/nrf9151/ns -- -DEXTRA_CONF_FILE=../common/runtime_settings.conf
$ west flash

4. Provision and run the sample

Golioth is free for individual use so sign up for an account if you have not already done so. After creating a project and device we can provision the PSK-ID/PSK by opening a serial connection to the device.

uart:~$ settings set golioth/psk-id <your-psk-id>
uart:~$ settings set golioth/psk <your-psk>

Here’s the terminal output during my tests:

*** Booting nRF Connect SDK v2.6.99-85097eb933d9 ***
*** Using Zephyr OS v3.6.99-18285a0ea4b9 ***
[00:00:00.538,452] <inf> fs_nvs: 2 Sectors of 4096 bytes
[00:00:00.538,482] <inf> fs_nvs: alloc wra: 0, fb8
[00:00:00.538,482] <inf> fs_nvs: data wra: 0, 68
[00:00:00.538,879] <dbg> golioth_stream: main: Start Golioth stream sample
[00:00:00.539,001] <inf> golioth_samples: Bringing up network interface
[00:00:00.539,001] <inf> golioth_samples: Waiting to obtain IP address
[00:00:01.691,894] <inf> lte_monitor: Network: Searching
uart:~$ settings set golioth/psk-id 20240603190757-nrf9151dk@nrf9151-demo
Setting golioth/psk-id to 20240603190757-nrf9151dk@nrf9151-demo
Setting golioth/psk-id saved as 20240603190757-nrf9151dk@nrf9151-demo
uart:~$ settings set golioth/psk e487ea809e5fa705c2af4050150f822c
Setting golioth/psk to e487ea809e5fa705c2af4050150f822c
Setting golioth/psk saved as e487ea809e5fa705c2af4050150f822c
[00:01:10.748,168] <inf> lte_monitor: Network: Registered (roaming)
[00:01:10.748,901] <inf> golioth_mbox: Mbox created, bufsize: 1232, num_items: 10, item_size: 112
[00:01:12.994,964] <inf> golioth_coap_client_zephyr: Golioth CoAP client connected
[00:01:12.995,025] <inf> golioth_stream: Sending temperature 20.000000 (sync)
[00:01:12.995,269] <inf> golioth_stream: Golioth client connected
[00:01:12.995,269] <inf> golioth_coap_client_zephyr: Entering CoAP I/O loop
[00:01:13.543,975] <dbg> golioth_stream: temperature_push_cbor: Temperature successfully pushed
[00:01:18.544,067] <inf> golioth_stream: Sending temperature 20.500000 (async)
[00:01:20.953,582] <wrn> golioth_coap_client: Resending request 0x2001e2c0 (reply 0x2001e308) (retries 2)
[00:01:23.544,311] <inf> golioth_stream: Sending temperature 21.000000 (sync)
[00:01:25.544,677] <wrn> golioth_stream: Failed to push temperature: 9
[00:01:25.772,186] <wrn> golioth_coap_client: Resending request 0x2001e2c0 (reply 0x2001e308) (retries 1)
[00:01:25.947,631] <wrn> golioth_coap_client: Resending request 0x2001e440 (reply 0x2001e488) (retries 2)
[00:01:30.544,738] <inf> golioth_stream: Sending temperature 21.500000 (async)
[00:01:30.581,359] <dbg> golioth_stream: temperature_async_push_handler: Temperature successfully pushed
[00:01:30.949,401] <dbg> golioth_stream: temperature_async_push_handler: Temperature successfully pushed
[00:01:35.544,952] <inf> golioth_stream: Sending temperature 22.000000 (sync)
[00:01:36.326,812] <dbg> golioth_stream: temperature_push_cbor: Temperature successfully pushed
[00:01:41.326,873] <inf> golioth_stream: Sending temperature 22.500000 (async)
[00:01:42.582,946] <dbg> golioth_stream: temperature_async_push_handler: Temperature successfully pushed
[00:01:46.327,117] <inf> golioth_stream: Sending temperature 23.000000 (sync)
[00:01:46.947,204] <dbg> golioth_stream: temperature_push_cbor: Temperature successfully pushed
[00:01:51.947,296] <inf> golioth_stream: Sending temperature 23.500000 (async)
[00:01:52.718,261] <dbg> golioth_stream: temperature_async_push_handler: Temperature successfully pushed
[00:01:56.947,540] <inf> golioth_stream: Sending temperature 24.000000 (sync)
[00:01:57.663,665] <dbg> golioth_stream: temperature_push_cbor: Temperature successfully pushed
[00:02:02.663,726] <inf> golioth_stream: Sending temperature 24.500000 (async)
[00:02:03.725,708] <dbg> golioth_stream: temperature_async_push_handler: Temperature successfully pushed
[00:02:07.663,970] <inf> golioth_stream: Sending temperature 25.000000 (sync)
[00:02:08.589,111] <dbg> golioth_stream: temperature_push_cbor: Temperature successfully pushed
[00:02:13.589,172] <inf> golioth_stream: Sending temperature 25.500000 (async)

5. View the data sent from the device

In the Golioth web console I can navigate to the LightDB Stream tab for the device and see the data as it arrives on the cloud. Try out Pipelines to transform and send that data to a destination.

A table of temperature data displayed on the Golioth web console

What will you do with the nRF9151?

We see a lot of IoT deployments using the nRF9160 to provide a cellular connection. They’re versatile parts with plenty of peripherals. The new nRF9151 part number is nice for your board footprint, and your power budget. And of course, every fleet needs management and data handling. Golioth already works with this SoC and so many more!

This is a guest post by Sandra Capri, CTO at Ambient Sensors, a Golioth Design Partner who regularly designs and deploys Bluetooth Solutions.

We have been building Bluetooth® Mesh (hereafter referred to as “BT Mesh”) products for a long time. In fact, we helped write some of the BT Mesh models for Nordic, so we are very familiar with it. However, we’ve always been dependent upon applications written for phones to control the BT Mesh, which can be limiting while doing Mesh development or putting together quick prototypes for potential customers. This particular demo allowed us to add network connectivity by building on top of Golioth’s Thingy91 demo, which unlocks a faster design iteration cycle when it comes to BT Mesh. We asked Chris to help record the demo video above.

Controlling from afar

This demo shows that Golioth can control a BT Mesh-based lighting system where each of the individual components (nodes in the mesh) of the lighting system, (e.g., switches, luminaires, sensors), contain a BLE SoC. Normally a user must be within a few tens of meters of a Bluetooth device to interact with it using (for example) a mobile app on a smartphone. But instead, we show Golioth connecting via LTE-M cellular service to a Nordic Semiconductor Thingy91 (combined nRF9160 cellular modem and nRF52840 BLE SoC) which then communicates to the rest of the Bluetooth mesh, thus allowing an authorized user from anywhere on Earth to control the lights (represented in the demo by a series of bbc:microbits). This gives the user control over all aspects of the Mesh servers – sending commands and updating settings without needing to be physically present.

How it works

The button on the Thingy91 is programmed to function as a simple on/off light switch. A much greater set of control functions for the BT Mesh comes from the Golioth web interface (Console). The demo shows a small sample of the kinds of control that can be accomplished using Golioth:

  • Ramping up the LED level on the lights
  • Defining the LED levels
  • Setting the time to keep the LEDs at those levels before ramping down
  • Overriding the LED level – preventing ramping

The Thingy91 contains an nRF9160 LTE-M chip and an nRF52840 BLE chip – this represents the bridge between LTE-M and the BT Mesh. The bbc:microbits contain an nRF52833 BLE chip, and represent the luminaires (Mesh lighting nodes). The Golioth SDK has been integrated into the code for the nRF9160, connecting the Thingy91 to Golioth. When a user executes a Remote Procedure Call (RPC) or sets a device setting, this information is sent via LTE-M to the nRF9160. That chip converts that information into opcode values sending it via UART to the nRF52840 chip. The nRF52840 then builds an appropriate Bluetooth mesh command and sends it to the Mesh network (wirelessly). Each lighting node on the network receives and executes the Bluetooth Mesh command.

In this demo, each lighting node (bbc:microbit) implements the Bluetooth Mesh LC server: an intelligent Lighting Controller. If the LC element on the lighting node receives a Generic On command, it ramps up its LED level to the configured lightness on value and maintains that level for the period of the configured run on time. After that time has expired, it will fade the light level to the configured prolong lightness level and maintain that for the configured prolong time. Then it fades the LEDs to the configured standby lightness level.

Each of these configurable lightness levels and times can be changed in the Device Settings.

Once these are set, an RPC can initiate the LC server control, or it can override the LC server and set an exact light level preventing the LC server from controlling the lights. Additionally, there is an RPC that will re-enable the LC server.

Other applications

This demo gives just a taste of the control that Golioth can provide with the Bluetooth Mesh. In addition to lights and switches, many different classes of devices can be on a Mesh:

  • Burglar and fire alarms
  • Smart locks
  • Thermostats
  • All kinds of sensors (e.g., temperature/humidity, photometry, motion, power, environmental, …)
  • More!

Commands can flow from the Cloud to each device, and status can be returned to the Cloud (e.g., alerts, alarms, etc.) Are the warehouse doors locked and the burglar alarm set? Well let’s check Golioth – oops, still unlocked! Let’s fix that. OK, Golioth just locked the front door, verified the locked status of the other doors, and turned on the burglar alarm. And just for good measure, we checked that the current temperature and humidity are within acceptable parameters. We didn’t even have to leave home to do that.

Now that we have the ability to control all this from the Cloud, we’ve unlocked new designs for our customers, and we’re able to spin up test deployments faster than ever.