<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>The Golioth Developer Blog</title><link>https://blog.golioth.io/</link><description>Golioth, Zephyr, and IoT Development — News and How Tos</description><generator>Hugo</generator><language>en-us</language><lastBuildDate>Mon, 02 Feb 2026 18:48:59 +0000</lastBuildDate><atom:link href="https://blog.golioth.io/feed/" rel="self" type="application/rss+xml"/><item><title>Why build hardware at a software company?</title><link>https://blog.golioth.io/why-build-hardware-at-a-software-company/</link><guid>https://blog.golioth.io/why-build-hardware-at-a-software-company/</guid><pubDate>Mon, 02 Feb 2026 18:48:59 +0000</pubDate><dc:creator>Chris Gammell</dc:creator><category>Hardware</category><description>Golioth is a Cloud company that operates a Software-as-a-Service (SaaS) model for its customers and does not sell any hardware…so why do we build hardware? Well, let’s get the obvious one out of the way: As a hardware engineer, it is just something I do. Rightly or wrongly for any particular situation, I just love building hardware. It is the hammer that I have applied to many, many technical nails that I have encountered over the years. As it so happens, this also is a benefit to a software company that serves the IoT market. Putting together solutions that mirrors our customers’ challenges reveals gaps in explanations or services that we offer. It also allows us to empathize with the difficulties of bringing a connected hardware product to market in large volumes. Let’s look at how our internal hardware has evolved and the reasons why we build actual hardware.</description><content:encoded><![CDATA[<p>Golioth is a Cloud company that operates a Software-as-a-Service (SaaS) model for its customers and does not sell any hardware…so why do we build hardware? Well, let’s get the obvious one out of the way: As a hardware engineer, it is just something I <em>do.</em> Rightly or wrongly for any particular situation, I just love building hardware. It is the hammer that I have applied to many, many technical nails that I have encountered over the years. As it so happens, this also is a benefit to a software company that serves the IoT market. Putting together solutions that mirrors our customers’ challenges reveals gaps in explanations or services that we offer. It also allows us to empathize with the difficulties of bringing a connected hardware product to market in large volumes. Let’s look at how our internal hardware has evolved and the reasons why we build actual hardware.</p>
<h2 id="it-starts-with-a-development-board">It starts with a development board</h2>
<p>All of our <a href="https://docs.golioth.io/getting-started">getting started guides</a> are built on top of development kits / boards from our hardware partners. <figure id="attachment_10511" aria-describedby="caption-attachment-10511" style="width: 696px" class="wp-caption aligncenter"><img src="https://blog.golioth.io/wp-content/uploads/2026/01/frdm-rw612-1030x772.jpg" alt=""><figcaption id="caption-attachment-10511" class="wp-caption-text">The FRDM-RW612 from NXP</figcaption></figure> This is for good reason: development boards are broadly available hardware that is reliably manufactured, available for sale at our partner distributors, and have upstream Zephyr support for those boards. It is the best option for getting a user started with a known setup. Even then, things can be difficult! Troubleshooting firmware is easier when the Golioth team has the same board as the one that is sitting on a customer’s bench. All Golioth platform capabilities are showcased in <a href="https://github.com/golioth/golioth-firmware-sdk/tree/main/examples">the “examples” section of our SDK</a>. This serves two purposes:</p>
<ol>
<li>Having a standard set of tests that the Golioth firmware team regularly tests (see <a href="https://blog.golioth.io/?s=hil">our posts on hardware-in-the-loop</a> for more details)</li>
<li>Providing a standalone example of that feature working without any distraction For these purposes, development boards work great. Users have a proven, tested set of samples that can run on supported hardware like our</li>
</ol>
<p><a href="https://docs.golioth.io/reference/hardware/#continuously-verified-boards">Continuously-Verified Boards (CVB)</a>.</p>
<h2 id="doing-more-to-show-more">Doing more to show more</h2>
<p>As we enter another trade show season, capturing engineers’ attention is on my mind. Nestled in a booth among other compelling demos, it can be hard to stand out. There are many things to see at a trade show, and IoT demos don’t benefit from things like cameras, large screens, or the impressive scale of something like an industrial robot. And as tough as standing out at a trade show can be, it might be even more difficult on the internet. How do we showcase complex and abstract topics to a crowd that will click away after a split second? If you want to build a compelling demo (as we often want to do), you need to add other elements to development boards. If I am trying to show the layers and layers of cloud, firmware, and hardware all to showcase how I blink an LED from the internet…that demo falls flat. Don’t get me wrong, I have done this and sometimes all we have is an LED. If you do happen to snag the attention of an engineer who has struggled with the stacks of software required to build an IoT system, the LED will suffice. But they are the exception not the rule. Users eventually want to see <em>all</em> capabilities of Golioth in a coherent demo. This is the origin of our <a href="https://github.com/golioth/reference-design-template">Reference Design Template</a> and more generally <a href="https://projects.golioth.io">our Reference Designs</a>. In order to showcase each of those capabilities in a compelling way, we also wanted to increase the variety of inputs and outputs. <a href="https://blog.golioth.io/instantly-recreate-sophisticated-iot-designs-with-follow-along-hardware/">The “Follow Along Hardware” version</a> of the Reference Designs took an abstracted interface like the Arduino (Uno) Shield interface on something like the nRF9160-DK and added a breakout to accept MikroBus Click Headers and extend the capabilities of the development board. The flexible nature of Zephyr allowed us to <a href="https://blog.golioth.io/abstract-hardware-interfaces-in-zephyr/">create overlay files</a> and interact with a variety of sensors (inputs) and output devices.<img src="https://blog.golioth.io/wp-content/uploads/2026/01/fah-can-asset-tracker-1030x772.jpg" alt=""> But what about when we want to do even more than that? How do we better emulate the setup of our customers?</p>
<h2 id="approximating-production-grade-hardware">Approximating production grade hardware</h2>
<p>In 2022, we started building some of these designs into full featured Reference Designs that look like customer use cases. Application-specific demos are also concrete; recording a video or talking to a potential customer at a trade shows is easier when a use case is front and center. For example, “Would you like to see <a href="https://projects.golioth.io/reference-designs/air-quality-monitor/">this Air Quality Monitor sending back complex data over cellular to Golioth</a> and the wider internet?” Around this same time, we started designing and building pluggable hardware of our own. We wanted the ability to swap out sensors, but in a more realistic form factor. There are still some tradeoffs in order to make it easy to swap out sensors, namely that we built the form factor to include modular headers like the MikroBus Click header and the Sparkfun QWIIC / Adafruit Stemma 4 pin i2c interface. <figure id="attachment_10508" aria-describedby="caption-attachment-10508" style="width: 696px" class="wp-caption aligncenter"><img src="https://blog.golioth.io/wp-content/uploads/2026/01/Aludel-1030x630.jpg" alt=""><figcaption id="caption-attachment-10508" class="wp-caption-text">The Aludel was a large waterproof case built to hold a variety of off-the-shelf sensor boards using MikroBus</figcaption></figure><img src="https://blog.golioth.io/wp-content/uploads/2026/01/Aludel-Green-1030x772.jpg" alt=""> This allowed us to use the same sensors that are on the Follow-Along Hardware examples, but in an enclosed form factor. This still isn’t how our customers operate though. They make truly custom hardware, often only borrowing from the schematic of a reference board they used in earlier stages. <a href="https://github.com/golioth/elixir-hw">The Aludel Elixir</a> migrated us away from soldering external dev boards (like the nRF9160 feather, which we love) to an integrated version where the chips are soldered directly to our custom PCB. <figure id="attachment_10510" aria-describedby="caption-attachment-10510" style="width: 696px" class="wp-caption aligncenter"><img src="https://blog.golioth.io/wp-content/uploads/2026/01/Elixir-1030x605.jpg" alt=""><figcaption id="caption-attachment-10510" class="wp-caption-text">The Aludel Elixir is a flexible form factor that still allows pluggable hardware for maximum flexibility while building reference designs</figcaption></figure> This gave us access to more pins and space, allowed the addition of multiple communication mechanisms onboard (cellular, Wi-Fi, Bluetooth), and a variety of power input capabilities. We also created <a href="https://blog.golioth.io/all-golioth-hardware-is-now-open-source/#ostentus">a PCB front panel called the Ostentus</a>, which took commands over i2c (using the QWIIC / Stemma connectors) and displayed information directly on the device. There are also capactive touch buttons around the screen for user input. This is useful both at trade shows and on the factory floor to give a quick summary of sensor readouts, with more in-depth data available on the Cloud. <figure id="attachment_7553" aria-describedby="caption-attachment-7553" style="width: 650px" class="wp-caption aligncenter"><img src="https://blog.golioth.io/wp-content/uploads/2024/07/Ostentus_Front_Lit_RevC-650x489.jpg" alt=""><figcaption id="caption-attachment-7553" class="wp-caption-text">The Ostentus is an ePaper display with touch inputs that communicate back to the Elixir over i2c</figcaption></figure> The manufacture of those boards was the same process we expect our customers to do. The provisioning process includes the same difficulties as devices rolling off the line. In a best case scenario, they implement <a href="https://docs.golioth.io/reference/device-api/device-auth/">zero-touch provisioning using certificates</a> to seamlessly join a Golioth project and start sending data.</p>
<h2 id="additional-challenges-arise-for-custom-hardware">Additional challenges arise for custom hardware</h2>
<p>Customers have specific needs around form factor, power envelope, and supporting components. The Aludel Elixir was the same. The next challenges are in the firmware space, bending firmware ecosystems like Zephyr to work with custom hardware and implement new features. A hardware device maker needs pull in the Golioth Firmware SDK, but also tap into the various subsystems of Zephyr and write custom elements like device drivers that conform to Zephyr standards. We did the same on the Elixir, and have continuously published about what we have learned in Zephyr as a result. When we emulate the production process, we also run into hardware and firmware challenges that many of our customers run into. Things like <a href="https://blog.golioth.io/manufacturing-a-mini-fleet-provisioning-and-updating-10-thingy91s/">provisioning devices off the “manufacturing line”</a> (simulated in our case, since we either manufacture ourselves or do very small runs of units) or <a href="https://blog.golioth.io/measuring-power-usage-with-golioth-and-joulescope/">measuring current consumption of different aspects of the a device</a> that regularly transmits data or optimizing firmware versioning to work with Golioth Over-The-Air firmware updates. Each time we build our own custom hardware, we step into the shoes of our customers building custom hardware. Each time we learn something new about the process of building efficient IoT devices on top of our platform. <span style="color: #111111; font-family: Roboto, sans-serif; font-size: 27px;">Smaller and smaller</span> We introduced <a href="https://blog.golioth.io/bluetooth-open-beta/">Golioth Connectivity and Bluetooth support back in August of last year.</a> That also surfaced new challenges around the physical constraints of Bluetooth connected devices. While there is no requirement that a Bluetooth device be small, the new capabilities of <a href="https://github.com/golioth/pouch/tree/main">Pouch</a> and the <a href="https://github.com/golioth/pouch-gateway">Golioth Bluetooth Gateway reference implementation</a> means we can build devices that are smaller than others we have created before. <figure id="attachment_10513" aria-describedby="caption-attachment-10513" style="width: 696px" class="wp-caption aligncenter"><img src="https://blog.golioth.io/wp-content/uploads/2026/01/tikk-1030x775.jpg" alt=""><figcaption id="caption-attachment-10513" class="wp-caption-text">The Tikk board stack is a custom board on top of an nRF52840 board with a ProMicro form factor</figcaption></figure> There is also the challenge of creating a diminutive device that still can be eye-catching. That was the thought behind a 104 LED matrix array and two motor drivers attached to a Bluetooth module (<a href="https://blog.golioth.io/open-sourcing-the-tikk-demo-board/">the Tikk board</a>, above). We use it to <a href="https://www.youtube.com/watch?v=o9Whb78XaIo">scroll information during updates</a> and have used it as <a href="https://www.youtube.com/watch?v=Pj1QK_BSSF0">a digital signage application level design</a>. Once again, we are designing and building custom hardware to emulate the challenges of our users building Bluetooth devices. Whereas before we could create larger, modular designs, we have now reached the point when there are longer any modular interfaces on these boards. In the case of the Tikk, this took the form of a small form factor dev board (ProMicro) and permanently attached a very customized sensor breakout. Basically, this is the limit of our modularity, and it’s not very modular at that. As our customers are optimizing for the lowest power devices that can fit in the smallest spaces, this needs to get even more precise. <figure id="attachment_10514" aria-describedby="caption-attachment-10514" style="width: 696px" class="wp-caption aligncenter"><img src="https://blog.golioth.io/wp-content/uploads/2026/01/orleon-a-e1769737719439-1030x497.png" alt=""><figcaption id="caption-attachment-10514" class="wp-caption-text">The Orleon is a Bluetooth board designed to work with a CR2032 battery</figcaption></figure> Once we have a truly integrated solution (like <a href="https://www.youtube.com/shorts/p86tQCDtCVM">the Orleon</a>, above), we once again need to focus on other similar challenges that our customers face. Even lower power operation, the ability to load certificates on in production, the ability to lock down interfaces after production ends, and the ability to still troubleshoot devices that have issues in the field. These may feel like universal needs for electronics devices, but they are most prevalent for devices that race towards high volume production.</p>
<h2 id="dogfooding-at-each-stage-of-the-design">Dogfooding at each stage of the design</h2>
<p>Last month we shared one of our “hack day” projects extending the capabilities of</p>
<p><a href="https://blog.golioth.io/a-physical-meme-terminal-for-distributed-teams/">an ePaper display as a meme delivery device</a>…but really this was an exercise in “dogfooding” our own IoT platform. Each time we port an off the shelf piece of hardware like we used for the MePaper we feel the pain points that IoT developers face from the firmware on up. Building custom hardware takes on even more of the process and is an exercise in empathy for our customers and stress testing our systems. We will continue trying out different designs and look forward to <a href="https://blog.golioth.io/all-golioth-hardware-is-now-open-source">building open source solutions</a> that could be the starting point for new products in the IoT market.</p>
]]></content:encoded></item><item><title>Handling Button Press, Longpress, and Double-Tap with the Zephyr Input Subsystem</title><link>https://blog.golioth.io/handling-button-press-longpress-and-double-tap-with-the-zephyr-input-subsystem/</link><guid>https://blog.golioth.io/handling-button-press-longpress-and-double-tap-with-the-zephyr-input-subsystem/</guid><pubDate>Tue, 27 Jan 2026 15:01:48 +0000</pubDate><dc:creator>Mike Szczys</dc:creator><category>How To</category><description>Embedded engineers know the joys and pains with button inputs: debouncing them, triggering actions, and more advanced input patterns like long holding the button down or quickly clicking more than once. If your project is based on Zephyr, you should take a look at the input subsystem to see if it can do some of the hard work for you. Today we’ll look at using input events to handle button presses, double-taps, and longpresses.</description><content:encoded><![CDATA[<p>Embedded engineers know the joys and pains with button inputs: debouncing them, triggering actions, and more advanced input patterns like long holding the button down or quickly clicking more than once. If your project is based on Zephyr, you should take a look at <a href="https://docs.zephyrproject.org/latest/services/input/index.html">the input subsystem</a> to see if it can do some of the hard work for you. Today we’ll look at using input events to handle button presses, double-taps, and longpresses.</p>
<h2 id="input-events-in-zephyr">Input Events in Zephyr</h2>
<p>The input event system has been around for the last few version of Zephyr, but starting with the next release it will be the default system for the</p>
<p><a href="https://docs.zephyrproject.org/latest/samples/basic/button/README.html">samples/basic/button</a> application, replacing the more esoteric system of setting up callbacks. For me this is a welcome change as it makes a lot more sense:</p>
<pre class="EnlighterJSRAW" data-enlighter-language="c">#include <zephyr/input/input.h>

static void button_input_cb(struct input_event *evt, void *user_data)
{
    if (evt->sync == 0) {
        return;
    }

    printk("Button %d %s at %" PRIu32 "\n",
           evt->code,
           evt->value ? "pressed" : "released",
           k_cycle_get_32());
}

INPUT_CALLBACK_DEFINE(NULL, button_input_cb, NULL);</pre> The code above is all that it takes to register a callback that runs on every input event. The event includes 
<p><a href="https://docs.zephyrproject.org/latest/doxygen/html/group__input__events.html#INPUT_KEY_CODES">a key code</a> (<code>evt-&gt;code</code>) to test which keypress triggered the callback. The <code>evt-&gt;value</code> will be <code>1</code> for “pressed” and `` for “released”. <a href="https://github.com/zephyrproject-rtos/zephyr/blob/2de323cd3eb7490ee1d571b0747cb59a62519f79/boards/nordic/nrf52840dk/nrf52840dk_nrf52840.dts#L63-L67">In your devicetree</a>, ensure that each button has a input code associated with it:</p>
<pre class="EnlighterJSRAW" data-enlighter-language="generic">#include <zephyr/dt-bindings/input/input-event-codes.h>

/ {
	buttons {
		compatible = "gpio-keys";

		button0: button_0 {
			gpios = <&gpio0 11 (GPIO_PULL_UP | GPIO_ACTIVE_LOW)>;
			label = "Push button switch 0";
			zephyr,code = <INPUT_KEY_0>;
		};
	};
};</pre> This is already a fantastic shortcut for controlling your application based on button presses, but there are two additional bindings that you may find useful: 
<p><code>input-longpress</code> and <code>input-double-tap</code>.</p>
<h2 id="detect-long-and-short-button-presses">Detect Long and Short Button Presses</h2>
<p>Long and short button presses are the most fully-featured pseudo input device implementation that I’ve found in Zephyr. This feature will be automatically selected in Kconfig by adding a node to your devicetree:</p>
<pre class="EnlighterJSRAW" data-enlighter-language="generic">/{
    longpress {
        compatible = "zephyr,input-longpress";
        input-codes = <INPUT_KEY_0>, <INPUT_KEY_1>;
        short-codes = <INPUT_KEY_A>, <INPUT_KEY_B>;
        long-codes = <INPUT_KEY_X>, <INPUT_KEY_Y>;
        long-delay-ms = <1000>;
    };
};</pre> Let’s walk through what this node accomplishes: 
<ul>
<li>compatible: enables the <em>input-longpress</em> binding.</li>
<li>input-codes: list of input codes (the keys you plan to press) to listen for.</li>
<li>short-codes: the input event code to send when the button is pressed, but not held.</li>
<li>long-codes: the input event code to send when the button is held.</li>
<li>long-delay-ms: how long a button must be held before it is considered a longpress. In your C code, we need to get the pseudo device from the devicetree, which we use as a filter when registering the callback.</li>
</ul>
<pre class="EnlighterJSRAW" data-enlighter-language="c">#include <zephyr/input/input.h>

static const struct device *const longpress_dev = DEVICE_DT_GET(DT_PATH(longpress));

static void longpress_cb(struct input_event *evt, void *user_data)
{
    if (evt->sync == 0) {
        return;
    }

    int button_number = -1;

    if (0 != evt->value) {
        switch (evt->code) {
            case INPUT_KEY_A:
            case INPUT_KEY_X:
                button_number = 1;
                break;
            case INPUT_KEY_B:
            case INPUT_KEY_Y:
                button_number = 2;
                break;
            default:
                return;
        }

        switch (evt->code) {
            case INPUT_KEY_A:
            case INPUT_KEY_B:
                printk("Short press: button %i\n", button_number);
                break;
            case INPUT_KEY_X:
            case INPUT_KEY_Y:
                printk("Long press:  button %i\n", button_number);
                break;
            default:
                return;
        }
    }
}

INPUT_CALLBACK_DEFINE(longpress_dev, longpress_cb, NULL);</pre> At the top of this code example we use the 
<p><code>longpress</code> path to get the instance from the devicetree. At the bottom of the example we register the callback, passing <code>longpress_dev</code> as the first argument so that only input events from this device will cause the <code>longpress_cb</code> callback function to run. The callback function itself really just filters for the desired button actions. First, it checks to see if the sync flag is set; some input events use this sync flag to indicate a stable state. From there, we use a conditional on <code>evt-&gt;value</code> to filter out the button release events. There are two nested switch statement, the first translates the  event to a button number (this nRF52840dk board labels the buttons starting with 1 for key0 and 2 for key1) and the second prints a message for short or long presses. The <code>input-longpress</code> binding uses the <code>short-press</code> parameter (which is optional) to remap the button presses. This way you will either get the new short keycode or the long keycode. The actual button code event (<code>INPUT_KEY_0</code> or <code>INPUT_KEY_1</code> in our example) will still occur but we have filtered them out from this callback.</p>
<h2 id="detect-double-tap-button-presses">Detect Double Tap Button Presses</h2>
<p>The
<code>input-double-tap</code> binding is a newer addition and not quite as useful as the longpress binding but still worth mentioning here. It is also a pseudo-device that you enable by adding a node in devicetree.</p>
<pre class="EnlighterJSRAW" data-enlighter-language="generic">/{
    double_tap: double_tap {
        compatible = "zephyr,input-double-tap";
        input-codes = <INPUT_KEY_2>, <INPUT_KEY_3>;
        double-tap-codes = <INPUT_KEY_C>, <INPUT_KEY_D>;
        double-tap-delay-ms = <300>;
    };
};
</pre> This functions in a similar way to the previous example: 
<ul>
<li>compatible: enable the <code>input-double-tap</code> binding</li>
<li>input-codes: list of input codes (the keys you plan to press) to listen for</li>
<li>double-tap-codes: the input event code to send when the button is quickly pressed twice</li>
<li>double-tap-delay-ms: maximum amount of time between presses to be considered a double-tap This issue with this binding is that it lacks the</li>
</ul>
<p><code>short-press</code> member that was present in the longpress binding. Because of this, there is no way to separate the double-tap event from two key input events. Nonetheless, let’s implement callbacks for this binding:</p>
<pre class="EnlighterJSRAW" data-enlighter-language="c">#include <zephyr/input/input.h>

static const struct device *const double_tap_dev = DEVICE_DT_GET(DT_NODELABEL(double_tap));

static void double_tap_cb(struct input_event *evt, void *user_data)
{
    if (evt->sync == 0) {
        return;
    }

    int button_number = -1;

    if (evt->value) {
        switch (evt->code) {
            case INPUT_KEY_C:
                button_number = 3;
                break;
            case INPUT_KEY_D:
                button_number = 4;
                break;
            default:
                return;
        }

        printk("Double tap: button %i\n", button_number);
    }

}

INPUT_CALLBACK_DEFINE(double_tap_dev, double_tap_cb, NULL);
</pre> The 
<p><code>double_tap_cb</code> callback is registered using the <code>double_tap_dev</code> pseudo-device as a filter. This makes it quite easy to detect two quick button presses. However, there is no detection for single button presses on those key. We could use the general input event callback show at the top of this post, but the downside is that we will receive two key events each time a double-tap event happens. Implementing single-tap support on this binding is a great place to contribute some code improvements to the Zephyr project. We’ll see if I can find some time in the near future to work on this.</p>
<h2 id="start-using-the-zephyr-input-subsystem">Start Using the Zephyr Input Subsystem</h2>
<p>he input subsystem is a great way to offload some of the input monitoring for your projects. Here I’ve specifically detailed its use for button inputs, but</p>
<p><a href="https://docs.zephyrproject.org/latest/services/input/index.html">the input subsystem</a> is a great place to start for any of your GPIO input handling needs. Expect to see these implemented in reference designs and examples coming from Golioth soon.</p>
]]></content:encoded></item><item><title>WiFi HaLoW with Morse Micro, Zephyr, and Golioth</title><link>https://blog.golioth.io/wifi-halow-with-morse-micro-zephyr-and-golioth/</link><guid>https://blog.golioth.io/wifi-halow-with-morse-micro-zephyr-and-golioth/</guid><pubDate>Thu, 22 Jan 2026 15:45:26 +0000</pubDate><dc:creator>Arien Judge</dc:creator><category>Featured</category><description> Editor’s note: we are happy to welcome Arien from Morse Micro to write about Wi-Fi HaLow and his experience getting it working with Zephyr and Golioth. Sub-Gig networking is a perfect pairing for Golioth right out of the box, and it is one of the first “directly connected” solutions in the ISM band. What is Wi-Fi HaLow? Before we talk about how to get your first Wi-Fi HaLow project off the ground, we thought we should define some terms and talk about the technology a bit.</description><content:encoded><![CDATA[<div class="golioth-callout-banner">
  Editor’s note: we are happy to welcome Arien from <a href="https://www.morsemicro.com/">Morse Micro</a> to write about Wi-Fi HaLow and his experience getting it working with Zephyr and Golioth. Sub-Gig networking is a perfect pairing for Golioth right out of the box, and it is one of the first “directly connected” solutions in the ISM band.
</div>
<h2 id="what-is-wi-fi-halow">What is Wi-Fi HaLow?</h2>
<p>Before we talk about how to get your first Wi-Fi HaLow project off the ground, we thought we should define some terms and talk about the technology a bit.</p>
<p><a href="https://www.wi-fi.org/wi-fi-macphy">Wi-Fi HaLow</a> is the first Wi-Fi standard purpose-built for the Internet of Things (IoT) and is a foundational technology for IoT 2.0. Operating in the sub-GHz spectrum (850–950 MHz), Wi-Fi HaLow delivers what traditional Wi-Fi cannot: long-range, meaningful throughput, low-power, and scalable connectivity, without sacrificing the openness, security, or familiarity of Wi-Fi. Wi-Fi HaLow offers a unique combination of capabilities and advantages that other wireless technologies can’t match. This includes</p>
<li aria-level="1">
  <strong>Long-range connectivity</strong> – Wi-Fi range extending up to 1km in urban environments, with superior penetration through walls, floors, and industrial infrastructure.
</li>
<li aria-level="1">
  <strong>Direct savings</strong> – Ultra-low power consumption enabling IoT devices to run on batteries for years
</li>
<li aria-level="1">
  <strong>Ease of use</strong> – No proprietary hubs or gateways. No lock-in. Just standards-based Wi-Fi that integrates seamlessly with existing IP networks.
</li>
<li aria-level="1">
  <strong>Enterprise-grade security</strong> – Full support of state-of-the-art security protocols including WPA3 Like all new technologies however, the understanding, implementation and governance of Wi-Fi HaLow varies across the globe.
</li>
<p><a href="https://www.morsemicro.com/">Morse Micro</a> is leading the global adoption of Wi-Fi HaLow with purpose-built silicon and reference solutions that redefine what Wi-Fi can do in large, complex environments. With the mass production of the MM8108 back in September 2025, Morse Micro is unlocking a new generation of long-range, low-power IoT devices.</p>
<h2 id="introducing-the-mm8108-ekh05">Introducing the MM8108-EKH05</h2>
<p><a href="https://www.youtube.com/watch?v=aOAnuoo03Jw">The MM8108-EKH05</a> is a fully integrated Wi-Fi HaLow development platform designed for a wide range of IoT applications. It features the Morse Micro MM8108-MF15457 module, alongside an STM32U585 low-power microcontroller, a BlueNRG-M2 Bluetooth® SoC, a rich set of on-board sensor options, and expansion headers to enable users to add their own circuits quickly. The MM8108-EKH05 is the ideal hardware platform to get started building Wi-Fi HaLow-enabled IoT devices or gateways. Morse Micro provides <a href="https://github.com/MorseMicro/mm-iot-zephyr">a Zephyr West manifest repository</a>, enabling rapid bring-up of Wi-Fi HaLow on Zephyr compatible targets. This includes the devicetree configuration for the MM8108-EKH05 board, but also definitions for Morse Micro shields, the Morse Micro Wi-Fi HaLow drivers for Zephyr, and a small number of sample applications to get users started with Wi-Fi HaLow on Zephyr.</p>
<h2 id="preparing-the-mm8108-ekh05-for-golioth">Preparing the MM8108-EKH05 for Golioth</h2>
<p>Adding Wi-Fi HaLow support to a Zephyr-based Golioth project is simple. The manifest repository mentioned above doubles as a west module, which can be added to your west manifest to extend your Golioth west workspace. If you start from the</p>
<p><a href="https://github.com/golioth/golioth-firmware-sdk">Golioth Firmware SDK</a> as a base project, you can target the variety of Golioth examples to talk to the Cloud. Add the following to your west.yml, under projects</p>
<pre class="EnlighterJSRAW" data-enlighter-language="yaml">- name: morsemicro
  path: modules/lib/morsemicro
  revision: main
  url: https://github.com/MorseMicro/mm-iot-zephyr</pre> Now run an update to pull in the Morse Micro Zephyr module. It’s important to fetch the binaries too. These binaries include the necessary MM8108 chip firmware, radio configuration binaries, and the Wi-Fi HaLow driver library – libmorse.a 
<pre class="EnlighterJSRAW" data-enlighter-language="bash">west update
west blobs fetch morsemicro</pre> Now build the Golioth project and target the mm8108_ekh05 board included in the Zephyr module, and flash! 
<pre class="EnlighterJSRAW" data-enlighter-language="bash">west build -p -b mm8108_ekh05 examples/zephyr/hello
west flash</pre> The board will boot, but won’t perform any application-level functions yet. To get it up and running, it’s simply a matter of creating the required Golioth credentials using the web console, opening a serial terminal, and provisioning the device with those credentials. 
<pre class="EnlighterJSRAW" data-enlighter-language="bash">wifi cred add -k 1 -s your_ssid -p your_wifi_password
settings set golioth/psk-id your-golioth-psk-id
settings set golioth/psk your-golioth-psk
kernel reboot</pre> Using 
<p><a href="https://github.com/golioth/golioth-firmware-sdk/tree/main/examples/zephyr/stream">the Golioth stream example</a>, you can start sending data right away through Pipelines to a variety of end points. Or try <a href="https://github.com/golioth/golioth-firmware-sdk/tree/main/examples/zephyr/fw_update">an Over-The-Air firmware update</a>, done at high throughput using a sub-gig based modem. Walls are no match for this technology stack!</p>
<h2 id="and-just-like-that">And just like that…</h2>
<p>If you are instead adding Wi-Fi HaLow to a board which isn’t the MM8108-EKH05,  then you’ll need to modify your board’s devicetree to include a node for the Morse Micro HaLow Wi-Fi interface. The MM8108 is a module talking to the STM32 on the Morse Micro development board, so it could also move onto your custom hardware as a peripheral for any other Zephyr enabled processor. You can add it as a child node to the SPI master you’re attaching to. See</p>
<p><a href="https://github.com/MorseMicro/mm-iot-zephyr/blob/main/boards/morsemicro/mm8108_ekh05/mm8108_ekh05.dts">the devicetree implementation for the MM8108-EKH05 board in the Morse Micro Zephyr library</a> for an example on how this node would look.</p>
]]></content:encoded></item><item><title>When Rapid Prototyping becomes Rapid Deployment</title><link>https://blog.golioth.io/when-rapid-prototyping-becomes-rapid-deployment/</link><guid>https://blog.golioth.io/when-rapid-prototyping-becomes-rapid-deployment/</guid><pubDate>Thu, 15 Jan 2026 21:20:57 +0000</pubDate><dc:creator>Chris Gammell</dc:creator><category>Culture</category><category>Golioth</category><category>Tools</category><description> This is an excerpt from our bi-monthly newsletter. You can sign up for future newsletters here. Imagine rolling out a new product idea to your fleet in a day. How about a couple hours? Now imagine rolling out an entire fleet in that same amount of time, and pulling in multiple connectivity types. Engineers are being asked to do more with less. Many organizations are mandating that their employees “use AI”. Not the most specific dictum, but the sentiment is “be more productive”. And for all the thought pieces on how useful AI coding tools are or are not, the reality is they are improving enough that you can spin up ideas quickly. We continue to try out the process during our own hackathons. So now you have the core of an idea, a new feature or capability for your customers. For a long time, that meant field engineers plugging a cable into the back of every product and manually updating each device, dealing with the variety of firmware versions that are out there. Connectivity improved some of the “how do I know what’s going on” problems, but not the the “reliable infrastructure to connect with devices” problems. Golioth does that, day in and day out.</description><content:encoded><![CDATA[<div class="golioth-callout-banner">
  <em>This is an excerpt from our bi-monthly newsletter. You can <a href="https://hslp.golioth.io/newsletter">sign up for future newsletters</a> here.</em>
</div> Imagine rolling out a new product idea to your fleet in a day. How about a couple hours? Now imagine rolling out an entire fleet in that same amount of time, and pulling in multiple connectivity types. Engineers are being asked to do more with less. Many organizations are mandating that their employees “use AI”. Not the 
<p><em>most</em> specific dictum, but the sentiment is “be more productive”. And for all the thought pieces on how useful AI coding tools are or are not, the reality is they are improving enough that you can spin up ideas quickly. We continue to try out the process <a href="https://blog.golioth.io/a-physical-meme-terminal-for-distributed-teams/">during our own hackathons</a>. So now you have the core of an idea, a new feature or capability for your customers. For a long time, that meant field engineers plugging a cable into the back of every product and manually updating each device, dealing with the variety of firmware versions that are out there. Connectivity improved some of the “how do I know what’s going on” problems, but not the the “reliable infrastructure to connect with devices” problems. Golioth does that, day in and day out.</p>
<h2 id="relying-on-sturdy-infrastructure">Relying on sturdy infrastructure</h2>
<p class="p2">
  Golioth has always been focused on delivering secure, reliable infrastructure to IoT device teams, and that continues into 2026. We spent last year improving resilience of our own internal infrastructure and hardening the already-rock-solid base we had in years past. We made changes to our security capabilities, introducing more ways to provision and deploy certificates and manage certificate infrastructure. Importantly, our engineering team also focused on the ease of use aspect of doing so, in order to make it less hassle to hardware and firmware teams that might not be used to X.509 certificate infrastructure. These improvements are ongoing, including <a href="https://blog.golioth.io/introducing-certificate-rotation-with-hosted-pki-providers/">a greater focus on large scale PKI providers.</a>
</p> If you decide to build a feature for your device with an LLM, you benefit from our teams’ focus on clear documentation and a range of examples to build upon. 
<p><a href="https://blog.golioth.io/getting-started-with-the-golioth-management-api/">Our Management API</a> is the control panel for you or your generative interface in order to orchestrate actions for your fleet; our Console is a practical starting place if you want a standard interface, as well. You can introduce new firmware images directly through the REST API, place devices into test cohorts (critical for those one-off test runs), and deploy and monitor devices as they go through the update process. If your new device feature is generating a new type of data, you can route it efficiently using our ever-improving Pipelines. <a href="https://blog.golioth.io/new-pipelines-data-destination-amazon-kinesis/">New destination examples</a> arise as new services arise, including the bevy of AI processing tools out in the world. Pipelines have continued to take an important role on internal tooling as well, <a href="https://blog.golioth.io/device-logs-anywhere-with-golioth-pipelines/">introducing destinations for LightDB State and Logs</a>, as well.</p>
<h2 id="the-abundance-of-choice-of-connectivity">The abundance of choice (of connectivity)</h2>
<p>If your next rapid deployment is a device (instead of only a feature for an existing device), Golioth continues to introduce new connectivity options for building out a product. <a href="https://blog.golioth.io/bluetooth-at-golioth-so-far-2025/">2025 was the year we officially introduced Bluetooth connectivity to the platform</a>. More generally, this introduced a new distinction between “directly connected” and “indirectly connected” devices. The former normally are devices that have an IP address assigned (think cellular, Wi-Fi, Ethernet, and Thread devices) and the latter are those that require a gateway…but also covers just about every other type of connectivity. We continue to look at transport options for “indirectly connected” devices like CAN, serial, LoRa, and more. Bluetooth has been a particularly interesting target for new devices, simply because of the range of chipsets and vendors providing solutions. We continue to build upon and fawn over the Zephyr Project, since it enables compatibility across so many solutions. It also is a common RTOS for Bluetooth projects, having its own Bluetooth stack and vendors that build against it. Golioth continues to refine the capabilities for indirectly connected devices, including our Pouch protocol and the associated SDK. On the cloud side, our APIs are evolving to serve devices that are offline-first, such as Bluetooth devices that <a href="https://blog.golioth.io/send-when-idle-using-zephyr-and-golioth-pouch/">only check-in when there is user activity</a>. The examples we build around these application spaces will enable users looking to spin up generative tooling around a Bluetooth application quick, efficient, and easy to deploy.</p>
]]></content:encoded></item><item><title>'New Pipelines Data Destination: Amazon Kinesis'</title><link>https://blog.golioth.io/new-pipelines-data-destination-amazon-kinesis/</link><guid>https://blog.golioth.io/new-pipelines-data-destination-amazon-kinesis/</guid><pubDate>Tue, 13 Jan 2026 14:45:10 +0000</pubDate><dc:creator>Dan Mangum</dc:creator><category>Cloud</category><category>Featured</category><description>A new Pipelines data destination for Amazon Kinesis is now generally available for Golioth users. Amazon Kinesis Data Streams is a massively scalable, highly durable data ingestion and processing service optimized for streaming data.
How It Works The aws-kinesis data destination is similar to the aws-sqs destination for Amazon Simple Queue Service (SQS). However, besides allowing you take advantage of the unique capabilities of Kinesis Data Streams, the aws-kinesis data destination will pass along the exact data delivered to it regardless of format. This gives you more control on the data that ends up in your data stream, but also means that you’ll need to use a transformer like inject-metadata if you want device ID, project ID, and other metadata included in each message. The device ID is also used as the data stream partition key. Like other AWS destination, the aws-kinesis data destination accepts access_key, access_secret, and region parameters. It also accepts a stream_arn parameter formatted as an Amazon Resource Name (ARN).</description><content:encoded><![CDATA[<p>A new <a class="notion-link-token notion-focusable-token notion-enable-hover" tabindex="0" href="https://docs.golioth.io/data-routing" rel="noopener noreferrer" data-token-index="1"><span class="link-annotation-unknown-block-id-33100344">Pipelines</span></a> data destination for <a href="https://aws.amazon.com/kinesis/">Amazon Kinesis</a> is now generally available for Golioth users. Amazon Kinesis Data Streams is a massively scalable, highly durable data ingestion and processing service optimized for streaming data.<!-- notionvc: 6f9cdd1c-6fa3-46a8-8b89-ba977770966a --></p>
<h2 id="how-it-works">How It Works</h2>
<p>The
<code>aws-kinesis</code> data destination is similar to the <a href="https://docs.golioth.io/data-routing/destinations/aws-sqs"><code>aws-sqs</code> destination</a> for <a href="https://aws.amazon.com/sqs/">Amazon Simple Queue Service (SQS)</a>. However, besides allowing you take advantage of the unique capabilities of <a href="https://docs.aws.amazon.com/streams/latest/dev/key-concepts.html#stream">Kinesis Data Streams</a>, the <code>aws-kinesis</code> data destination will pass along the exact data delivered to it regardless of format. This gives you more control on the data that ends up in your data stream, but also means that you’ll need to use a transformer like <a href="https://docs.golioth.io/data-routing/transformers/inject-metadata"><code>inject-metadata</code></a> if you want device ID, project ID, and other metadata included in each message. The device ID is also used as the data stream <a href="https://docs.aws.amazon.com/streams/latest/dev/key-concepts.html#partition-key">partition key</a>. Like other AWS destination, the <code>aws-kinesis</code> data destination accepts <code>access_key</code>, <code>access_secret</code>, and <code>region</code> parameters. It also accepts a <code>stream_arn</code> parameter formatted as an <a href="https://docs.aws.amazon.com/IAM/latest/UserGuide/reference-arns.html">Amazon Resource Name (ARN)</a>.</p>
<pre class="EnlighterJSRAW" data-enlighter-language="yaml">filter:
  path: "*"
steps:
  - name: step0
    destination:
      type: aws-kinesis
      version: v1
      parameters:
        stream_arn: arn:aws:kinesis:us-east-1:123456789:stream/pipelines
        access_key: $AWS_ACCESS_KEY
        access_secret: $AWS_ACCESS_SECRET
        region: us-east-1</pre>
<!-- notionvc: 4da26f0e-d40d-4b5b-89c3-d9c378242d52 -->
<blockquote>
<p>Click <a href="https://console.golioth.io/pipeline?name=AWS%20Kinesis&amp;pipeline=ZmlsdGVyOgogIHBhdGg6ICIqIgogIGNvbnRlbnRfdHlwZTogYXBwbGljYXRpb24vanNvbgpzdGVwczoKICAtIG5hbWU6IHN0ZXAwCiAgICBkZXN0aW5hdGlvbjoKICAgICAgdHlwZTogYXdzLWtpbmVzaXMKICAgICAgdmVyc2lvbjogdjEKICAgICAgcGFyYW1ldGVyczoKICAgICAgICBzdHJlYW1fYXJuOiBhcm46YXdzOmtpbmVzaXM6dXMtZWFzdC0xOjEyMzQ1Njc4OTpzdHJlYW0vcGlwZWxpbmVzCiAgICAgICAgYWNjZXNzX2tleTogJEFXU19BQ0NFU1NfS0VZCiAgICAgICAgYWNjZXNzX3NlY3JldDogJEFXU19BQ0NFU1NfU0VDUkVUCiAgICAgICAgcmVnaW9uOiB1cy1lYXN0LTE=&amp;enabled=1">here</a> to use this pipeline in your Golioth project! For more details on the <code>aws-kinesis</code> data destination, go to the <a href="https://docs.golioth.io/data-routing/destinations/aws-kinesis">documentation</a>. <!-- notionvc: 22815813-2444-4cef-81d8-e463fa6180cc --></p></blockquote>
<h2 id="whats-next">What’s Next</h2>
<p>Amazon Kinesis is especially well-suited for high volume data and applications in which there are multiple consumers of the streamed data. It also offers a
<a href="https://aws.amazon.com/kinesis/data-streams/integrations/">wide range of integrations with other AWS services</a>, making it a foundational component for customers already leveraging AWS. Reach out to us on the <a href="https://forum.golioth.io/">forum</a> if you have any questions on the <code>aws-kinesis</code> data destination or are using an alternative service that does not currently have a native integration in Golioth Pipelines! <!-- notionvc: 935faee9-81eb-4e38-81b3-c10c5cef91e1 --></p>
]]></content:encoded></item><item><title>Getting Started with the Golioth Management API</title><link>https://blog.golioth.io/getting-started-with-the-golioth-management-api/</link><guid>https://blog.golioth.io/getting-started-with-the-golioth-management-api/</guid><pubDate>Thu, 08 Jan 2026 16:00:07 +0000</pubDate><dc:creator>Trond Snekvik</dc:creator><category>Golioth API</category><description>Integrating with an external API doesn’t have to mean hand-writing HTTP requests, guessing payload shapes, or chasing runtime errors. In this post, we’ll walk through how to get started with the Golioth Management API from a TypeScript-based Node.js backend using auto generated types from our OpenAPI definition. By the end, you’ll have:</description><content:encoded><![CDATA[<p>Integrating with an external API doesn’t have to mean hand-writing HTTP requests, guessing payload shapes, or chasing runtime errors. In this post, we’ll walk through how to get started with the <a href="https://docs.golioth.io/reference/management-api/">Golioth Management API</a> from a TypeScript-based Node.js backend using auto generated types from our OpenAPI definition. By the end, you’ll have:</p>
<ul>
<li>A Node.js project written in TypeScript</li>
<li>Fully typed API bindings generated from Golioth’s OpenAPI spec</li>
<li>API authentication handled cleanly with a <code>.env</code> file</li>
<li>A small example that talks to the Golioth Management API</li>
</ul>
<h2 id="why-openapi--typescript">Why OpenAPI + TypeScript?</h2>
<p>Golioth Management API is published as an</p>
<p><a href="https://www.openapis.org/">OpenAPI definition</a>. This means we can automatically generate:</p>
<ul>
<li>Strongly typed request and response objects</li>
<li>Autocomplete in your editor</li>
<li>Compile-time errors instead of runtime surprises For backend developers, this removes a lot of guesswork and makes refactoring much safer.</li>
</ul>
<h2 id="1-create-a-new-nodejs--typescript-project">1. Create a new Node.js + TypeScript project</h2>
<p>Before we start, you’ll need:</p>
<ul>
<li>Node.js 18+ installed</li>
<li>Basic familiarity with TypeScript</li>
<li>A Golioth account and an <a href="https://docs.golioth.io/reference/management-api/auth">API key</a> Start by creating a new project directory and initializing it:</li>
</ul>
<pre><code class="language-bash">mkdir golioth-demo
cd golioth-demo
npm init -y
</code></pre> Install the basic dependencies: 
<pre><code class="language-bash">npm install dotenv
npm install --save-dev typescript tsx @types/node
</code></pre> Initialize TypeScript: 
<pre><code class="language-bash">npx tsc --init
</code></pre> We now have a minimal TypeScript-enabled Node.js project, let’s start integrating! 
<h2 id="2-store-your-api-key-context-using-aenvfile">2. Store your API key context using a <code>.env</code> file</h2>
<p>The Golioth API requires an API key to be passed in a header. You should never hardcode a secret key into your code, as it can easily fall into the wrong hands. Instead, you can set up your application to pull the secret from the execution environment, which allows you to keep your API keys stored in your deployment environment, and use different credentials in different environments. API keys can only be used to access APIs for the project they’re attached to. To keep things portable, we’ll put the project and organization IDs in the environment as well, allowing our application to work with any project without having to change the code. A common solution for managing growing execution environments is to add a</p>
<p><code>.env</code> file in the project root:</p>
<pre><code class="language-bash">GOLIOTH_API_KEY=your-api-key
GOLIOTH_ORG=your-org-id
GOLIOTH_PROJECT=your-project-id
</code></pre> Then load it in your application’s 
<p><code>src/index.ts</code> using <a href="https://github.com/motdotla/dotenv"><code>dotenv</code></a>:</p>
<pre><code class="language-tsx">import 'dotenv/config';
</code></pre> Now your API key is available at runtime as: 
<pre><code class="language-tsx">process.env.GOLIOTH_API_KEY
</code></pre>
<div class="golioth-callout-banner">
  ⚠️ <strong>Warning:</strong> The <code>.env</code> file should <strong>never</strong> be committed to source control in your repository, and it’s best to add it to your <code>.gitignore</code> right away.
</div>
<h2 id="3-generate-typescript-bindings-from-the-openapi-spec">3. Generate TypeScript bindings from the OpenAPI spec</h2>
<p>To generate the TypeScript bindings for the Golioth API, we’ll use the</p>
<p><a href="https://github.com/acacode/swagger-typescript-api">Swagger TypeScript API</a> package. This provides a flexible set of configuration parameters that allows us to fine tune the generated TypeScript code to make the Golioth API integration as simple as possible. First, we’ll need to install the generator:</p>
<pre><code class="language-bash">npm install --save-dev swagger-typescript-api
</code></pre> Although 
<p><code>swagger-typescript-api</code> can be run as a command line tool with <code>npx</code>, it’s easier to set up its configuration by wrapping it up in a little script that runs the code generation. It’ll also allow us to tweak the generated code a bit. Create a <code>scripts</code> directory at the root of your repository, and add the following to a TypeScript file named <code>golioth.ts</code>:</p>
<pre class="EnlighterJSRAW" data-enlighter-language="typescript">import path from "node:path";
import { generateApi } from "swagger-typescript-api";

const fromEnv = {
    projectId: "GOLIOTH_PROJECT",
    organizationId: "GOLIOTH_ORG",
};
type PName = keyof typeof fromEnv;

generateApi({
    url: "<https://api.golioth.io/openapi.json>",
    apiClassName: "Golioth",
    output: path.resolve("src/api"),
    fileName: "golioth.ts",

    // split API into modules:
    moduleNameFirstTag: true,
    // Unwrap the json data in the response:
    unwrapResponseData: true,

    // Skip predefined parameters:
    hooks: {
        onCreateRoute(routeData) {
            routeData.request.parameters =
                routeData.request.parameters?.filter(
                    (p) => !fromEnv[(p as { name: PName }).name]
                ) ?? [];
            return routeData;
        },
        onInsertPathParam(paramName) {
            if (paramName in fromEnv) {
                return `process.env.${fromEnv[paramName as PName]}`;
            }
        },
    },
});</pre>   The configuration is fairly straight forward, but the hooks are an important addition here: The keys in the 
<p><code>fromEnv</code> object will be replaced with their mapped environment variables. We’ll get to why that’s important in a little bit! Finally, add this as a script in your <code>package.json</code>:</p>
<pre class="EnlighterJSRAW" data-enlighter-language="json">// ...
    "scripts": {
        // ...
        "generate:golioth": "tsx scripts/golioth.ts"
    }
}
</pre>   And run the script to generate the TypeScript bindings: 
<pre><code class="language-bash">npm run generate:golioth
</code></pre> After this completes, a new 
<p><code>golioth.ts</code> file gets added to <code>src/api</code>, containing several thousand lines of type definitions, including a <code>Golioth</code> class, which we’ll use to instantiate our API.</p>
<div class="golioth-callout-banner">
  💡 <strong>Note:</strong> It’s good practice to commit the generated file to source control, as the Golioth API is actively being developed, with new methods and parameters being added continuously. Although generating the file locally (as a <code>postinstall</code> script, for example) would let your type definition stay up to date with the latest additions to the Golioth API, it adds a risk of having different type definitions in different development environments. By committing the generated output and making a habit of regenerating the API at regular intervals, you’ll ensure a reproducible environment for your development and testing.
</div>
<h2 id="4-create-and-configure-the-golioth-api-client">4. Create and configure the Golioth API client</h2>
<p>With the generated bindings and API key in place, creating a client is straightforward. We find it’s cleanest to instantiate the Golioth client in a separate file, and export it as a</p>
<p><code>const</code>. Create a new file called <code>src/golioth.ts</code>:</p>
<pre class="EnlighterJSRAW" data-enlighter-language="typescript">import { Golioth } from "./api/golioth";

export const golioth = new Golioth({
    baseApiParams: {
        headers: {
            // Pulling the API key from the execution environment:
            "X-Api-Key": process.env.GOLIOTH_API_KEY,
        },
    },
});</pre> At this point, you already have: 
<ul>
<li>Authentication wired up</li>
<li>A fully typed API client</li>
<li>Editor autocomplete for all endpoints</li>
</ul>
<h2 id="5-make-your-first-api-call">5. Make your first API call</h2>
<p>Let’s make a simple request to verify everything works. For example, fetch a list of devices in your project:</p>
<pre class="EnlighterJSRAW" data-enlighter-language="typescript">import 'dotenv/config';
import { golioth } from "./golioth"

async function listDevices() {
    const devices = await golioth.devices.devicesList();

    devices.list?.forEach(device => {
        console.log(`- ${device.id}: ${device.name}`);
    })
}

listDevices().catch(console.error);</pre> Notice a few things: 
<ul>
<li>The method names come directly from the OpenAPI spec</li>
<li>Response objects are fully typed</li>
<li>TypeScript helps you discover fields without checking the docs This is also where the hooks in the configuration come in to make life a little easier for us! All Golioth APIs are prefixed with a project ID (and occasionally, an organization ID), and the hooks we added let us skip these parameters in the function call.</li>
</ul>
<h2 id="summary">Summary</h2>
<p>In just a few steps, we:</p>
<ul>
<li>Created a TypeScript Node.js project</li>
<li>Generated strongly typed API bindings from Golioth’s OpenAPI definition</li>
<li>Secured our API key using environment variables</li>
<li>Made our first request to the Golioth Management API If you’re building a backend service and want a </li>
</ul>
<p><strong>type-safe, low-friction</strong> way to integrate with Golioth, we think this is the fastest way to get started. Happy building! <!-- notionvc: 11689c02-db2a-4521-bf43-eb80cb8cbb79 --></p>
]]></content:encoded></item><item><title>Find Breaking Changes in Zephyr Using Git Bisect</title><link>https://blog.golioth.io/find-breaking-changes-in-zephyr-using-git-bisect/</link><guid>https://blog.golioth.io/find-breaking-changes-in-zephyr-using-git-bisect/</guid><pubDate>Tue, 06 Jan 2026 15:33:02 +0000</pubDate><dc:creator>Mike Szczys</dc:creator><category>How To</category><description>One of Zephyr’s greatest strengths is the large number of maintainers and contributors and the frequency with which they are merging changes. In the past four years that I’ve been working with Zephyr, it’s become remarkably stable and feature-filled. However, the increased stability you get with each new release sometimes comes with breaking changes. Tracking those down quickly is a skill that will lower your stress level. Let’s discuss one approach to finding and resolving breaking changes in Zephyr: using git bisect.</description><content:encoded><![CDATA[<p>One of Zephyr’s greatest strengths is the large number of maintainers and contributors and the frequency with which they are merging changes. In the past four years that I’ve been working with Zephyr, it’s become remarkably stable and feature-filled. However, the increased stability you get with each new release sometimes comes with breaking changes. Tracking those down quickly is a skill that will lower your stress level. Let’s discuss one approach to finding and resolving breaking changes in Zephyr: using <code>git bisect</code>.</p>
<h2 id="what-is-git-bisect">What is Git Bisect?</h2>
<p><a href="https://git-scm.com/docs/git-bisect">Git bisect</a> is a command built into git that pinpoints the commit at which a breaking change was introduced. Basically, you tell Git where the last time the code was working is located, where the non-working code is located. Git will then checkout new commits for you to label good or bad. Just run your test, label the commit, and git does the rest. The astute reader will recognize this as a binary search. You take all of the commits and test one in the middle. Depending on whether that test succeeds or fails you eliminate half of the commits before or after and then start the process again on the smaller set.</p>
<h2 id="difference-when-using-git-bisect-with-zephyr">Difference When Using Git Bisect with Zephyr</h2>
<p>Git bisect is nearly a perfect troubleshooting tool for Zephyr. The maintainers of the project are strict about requiring clean and granular commits, in order to improve consistency during the review process. As a result, every commit should build and run. Ideally you’ll have very few cases where you get a false positive while bisecting. However, the manifest system that Zephyr is based upon means you need to take a few extra steps while bisecting. That’s because git will change the Zephyr hash, but it will not run a
<code>west update</code> to ensure all the modules match the current hash of Zephyr. Let’s jump in and look at a few approaches that you can use.</p>
<h2 id="your-project-setup-matters">Your Project Setup Matters</h2>
<p>This article came to mind when I worked on updating which version of Zephyr the
<a href="https://github.com/golioth/golioth-firmware-sdk/">Golioth Firmware SDK</a> uses. Our SDK includes Zephyr as a module, which means we have our own <code>west.yml</code> (actually <code>west-zephyr.yml</code>) that includes a hash (revision) to lock Zephyr to a specific version.</p>
<pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-group="1" data-enlighter-title="Golioth Firmware SDK west-zephyr.yml">manifest:
  projects:
    - name: zephyr
      revision: v4.3.0
      url: https://github.com/zephyrproject-rtos/zephyr
      west-commands: scripts/west-commands.yml
      import: true

  self:
    path: modules/lib/golioth-firmware-sdk</pre>
<p><a href="https://blog.golioth.io/manifests-project-sanity-in-the-ever-changing-zephyr-world/">This is a great approach to use with your own projects</a>, as it ensures you always know which version of Zephyr (and its inherited modules) was used for any given commit in your project. But, when using <code>git bisect</code> on the Zephyr tree of this project, you can’t run <code>west update</code> because it will just check out the <code>revision</code> stored in this file. You can take two approaches to overcome this situation:</p>
<ol>
<li>Manually change the number in your manifest file to match each hash selected by git bisect
<ul>
<li>This is prone to user error if you forget to change the hash or make a mistake when updating.</li>
<li>It doesn’t require any other changes to your project.</li>
<li>This approach is used in the walkthrough below.</li>
</ul>
</li>
<li>Change your project to use the west manifest in the Zephyr tree
<ul>
<li>Add any additional configuration for your project into a manifest that you place in Zephyr’s <code>submanifests/</code> folder. This can be complicated if your project manifest is rather involved.</li>
<li>The manual work of changing your project manifest file for each git bisect step is no longer needed. Today we’re going to stick to the manual version. Let’s dive in!</li>
</ul>
</li>
</ol>
<h2 id="walkthrough-git-bisect-with-zephyr">Walkthrough: Git Bisect with Zephyr</h2>
<p>For today’s walkthrough, let’s assume you have the v0.21.1 of the Golioth Firmware SDK set up as a manifest repository by
<a href="https://github.com/golioth/golioth-firmware-sdk/tree/v0.21.1/examples/zephyr#using-the-golioth-firmware-sdk-as-a-west-manifest-repository">following the directions in the README</a>. The west manifest for this project is located in <code>your_workspace_folder/modules/lib/golioth-firmware-sdk/west-zephyr.yml</code> and the Zephyr tree is found in <code>your_workspace_folder/zephyr</code>.</p>
<h3 id="start-the-bisect">Start the Bisect</h3>
<p>We’re going to find where the Twister tests for the SDK broke between Zephyr v4.2.1 and Zephyr v4.3.0 so we open a terminal in the Zephyr tree to conduct the bisect:</p>
<pre class="EnlighterJSRAW" data-enlighter-language="generic">$ git bisect start
status: waiting for both good and bad commits

$ git bisect good
status: waiting for bad commit, 1 good commit known

$ git bisect bad v4.3.0
Bisecting: a merge base must be tested
[413b789deb391d3a37d06b463288a5fe765ee57e] release: Zephyr v4.2.0
</pre> The process starts by running 
<p><code>git bisect start</code>, and you may abort this process at any time by running <code>git bisect reset</code>. We then constrain the bisect process by labeling a good commit with <code>git bisect good</code> (using the hash of currently checked out commit) and a bad commit with <code>git bisect bad v4.3.0</code> (here we use a tag but you can also use a hash). Now that we’ve labelled both good and bad commits, git picks out the next commit to test. Our case is a special one: git moved to an earlier commit which was just some internal git housekeeping.</p>
<h2 id="test-the-commit-but-update-modules-first">Test the Commit, but Update Modules First!</h2>
<p>Here’s where the special step for Zephyr comes in. We need to update the Zephyr modules. If you’re bisecting a project that uses the west manifest inside the Zephyr tree (
<code>zephyr/west.yml</code>), you can update modules directly. But I need to manually update the Zephyr hash in my project manifest file.</p>
<pre class="EnlighterJSRAW" data-enlighter-language="generic"># Find the hash of this commit
$ git rev-parse HEAD
413b789deb391d3a37d06b463288a5fe765ee57e

# Update ../modules/lib/golioth-firmware-sdk/west-zephyr.yml with the above commit for Zephyr

# Update Zephyr modules
west update

# Test this commit (change this for your use case!!)
$ scripts/twister --platform frdm_rw612 -T ../modules/lib/golioth-firmware-sdk/examples/zephyr/logging --prep-artifacts-for-testing
$ if test -e twister-out/frdm_rw612_rw612/zephyr/sample.golioth.logging/zephyr/zephyr.hex; echo "PASS"; else echo "FAIL"; end
PASS</pre> Here you can see that I ask git for the hash of this commit. This is used to 
<p><a href="https://github.com/golioth/golioth-firmware-sdk/blob/5fe666035867e6e390981e0b7a944166384a46ef/west-zephyr.yml#L4">update the Zephyr revision in the west manifest file for the project</a>. With that updated, we run <code>west update</code>, which will not change the Zephyr tree, but it will/may change what version of modules are checkout out in <code>modules/lib</code>, etc. The test step is going to be different every time you bisect. In my case, I know that <a href="https://docs.zephyrproject.org/latest/develop/test/twister.html">Twister</a> tests place the compiled files in a different directory than our CI is expecting. So I’m just using a fish shell one-liner to test if the hex file is where I expect. In this case the first test passed!</p>
<h3 id="mark-the-commit-good-or-bad-to-continue-the-bisect">Mark the Commit Good or Bad to Continue the Bisect</h3>
<p>Based on the results of the test from the previous section, mark the commit as good or bad and Git will continue the bisect process.</p>
<pre class="EnlighterJSRAW" data-enlighter-language="generic">$ git bisect good
Bisecting: 3871 revisions left to test after this (roughly 12 steps)
[3a6ae2b01c9babd8be320a6912123de53ebba748] driver: sdhc: add sdmmc for sama7g5
</pre> Since my test from the previous section passed, I mark the commit as good and Git checks out a new commit. It also helpfully informs me that I will need to perform about 12 more tests to pinpoint the issue. Just run the same steps from the previous section again. In my case, the twister build failed on the second step of the bisect so that is definitely a bad commit! 
<pre class="EnlighterJSRAW" data-enlighter-language="generic">$ git bisect bad
Bisecting: 1933 revisions left to test after this (roughly 11 steps)
[56a527321ed977338d6ca4f0892114f988bf845a] include: drivers: input: enhance Doxygen documentation for CY8CMBR3xxx
</pre> Git has checked out another commit, so keep doing the test-and-mark loop until you get to the end of the bisect process. 
<h3 id="finishing-the-bisect">Finishing the Bisect</h3>
<p>When you get through all the bisect steps, git should tell you which commit is the first one with a breaking change.</p>
<pre class="EnlighterJSRAW" data-enlighter-language="generic">$ git bisect good
9219c81b66f48523f0cd3b7b7cac8cf3dadb84b2 is the first bad commit
commit 9219c81b66f48523f0cd3b7b7cac8cf3dadb84b2
Author: Anas Nashif <anas.nashif@intel.com>
Date:   Fri Jul 11 11:36:03 2025 -0400

    twister: make no-detailed-test-id default

    Make this option default and reduce amount of details displayed by
    twister.
    This options has been enabled in CI for a while now with the intention
    of making it default.

    Signed-off-by: Anas Nashif <anas.nashif@intel.com>

 .github/workflows/clang.yaml                    | 2 +-
 .github/workflows/twister.yaml                  | 6 +++---
 .github/workflows/twister_tests_blackbox.yml    | 8 ++++----
 scripts/ci/test_plan.py                         | 4 ++--
 scripts/pylib/twister/twisterlib/environment.py | 4 ++--
 scripts/tests/twister/conftest.py               | 1 +
 6 files changed, 13 insertions(+), 12 deletions(-)
</pre> In my case, this is a perfect outcome, so perfect that I immediately knew I should blog about this. I was trying to find out what in Zephyr v4.3.0 broke all of the Twister tests that we were running in v4.2.1 and 
<p><em>here is the smoking gun</em>! As you can tell from the excellent commit message, one of the Twister default values was changed. By looking at the code changes in this commit, I was able to discover that all of our tests now need to specify the <code>--detailed-test-id</code> flag. All that’s left now is to return our Zephyr repository to normal functionality by resetting the bisect.</p>
<pre class="EnlighterJSRAW" data-enlighter-language="generic">$ git bisect reset
Previous HEAD position was b68058787e9 counter: cmsdk_apb_dualtimer: Use clock freq from DT clocks
HEAD is now at 64e98eb5278 release: Zephyr v4.2.1
</pre> Don’t forget that if you have been manually updating your project manifest for each bisect hash, that change will need to be reverted as well. 
<h2 id="a-strong-incentive-for-disciplined-commits">A Strong Incentive for Disciplined Commits</h2>
<p>This is a great argument for using strict discipline in your git repositories. Ensure that no commit breaks the build, that commits are granular, and commit messages are verbose. If you’re successful at this, then
<code>git bisect</code> will someday help you pinpoint problems and deliver useful information in the process.</p>
<h2 id="bonus-automation-using-git-bisect-run">Bonus: Automation Using Git Bisect Run</h2>
<p>I asked for feedback from the Golioth engineering team before I started writing this, to see if there were any tips on using bisect. Marcin Niestroj is the one who suggested using the Zephyr submanifests mentioned earlier in this post. Sam Friedman suggested using
<code>git bisect run</code> which was also new to me. Certainly if you take the time to write a short script the run command is pretty powerful. I was able to pinpoint the breaking change by running just four commands:</p>
<pre class="EnlighterJSRAW" data-enlighter-language="generic">$ git bisect start
$ git bisect good
$ git bisect bad v4.3.0
$ git bisect run bash bisect_test.sh
</pre> Literally, the rest happens automatically and the offending commit just pops up on the screen! Of course you need to write the test script which I did using bash shell this time. 
<pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-group="bisect_test.sh" data-enlighter-title="2">#!/bin/bash

# Get the hash
this_hash=$(git rev-parse HEAD)

# Write hash to project manifest for Zephyr revision number
sed -i "4s/.*/      revision: ${this_hash}/" ../modules/lib/golioth-firmware-sdk/west-zephyr.yml

# Update the modules
west update

# Build the twister test
scripts/twister --platform frdm_rw612 -T ../modules/lib/golioth-firmware-sdk/examples/zephyr/logging --prep-artifacts-for-testing

# Test to see if build worked
if [[ -f "twister-out/frdm_rw612_rw612/zephyr/sample.golioth.logging/zephyr/zephyr.hex" ]]; then
    exit 0
else
    exit 1
fi</pre> Really the only special sauce here is the 
<p><code>sed</code> command which replaces line 4 of the manifest file. The rest is the same commands we ran manually, with the exception that we exit with an error code instead of echoing out a PASS/FAIL. This is slick if you take the time to set up your test!</p>
]]></content:encoded></item><item><title>A Physical Meme Terminal for Distributed Teams</title><link>https://blog.golioth.io/a-physical-meme-terminal-for-distributed-teams/</link><guid>https://blog.golioth.io/a-physical-meme-terminal-for-distributed-teams/</guid><pubDate>Tue, 23 Dec 2025 17:21:40 +0000</pubDate><dc:creator>Chris Gammell</dc:creator><category>Culture</category><category>Prototyping</category><category>SDK</category><category>Solutions</category><description>What happens when you’re hundreds or thousands of miles/kms from your coworkers, but still want to goof around and send them silly meme images? Usually…you use Slack. But what if you are an IoT company with advanced infrastructure to deliver data, firmware, and troubleshoot hardware all over the globe…and you want to send your coworkers silly images? Well, you put that infrastructure to work and start dogfooding your own platform. This small, internet-connected displays allows us to try out new things during the holiday season and keep in touch with colleagues across the globe by pushing memes directly to each other’s desks. There’s more explanation below, but we also recorded a video for the nitty-gritty details of how Mike and Dylan built the MePaper 2.0 on a hack day in early December 2025:</description><content:encoded><![CDATA[<p>What happens when you’re hundreds or thousands of miles/kms from your coworkers, but still want to goof around and send them silly meme images? Usually…you use Slack. But what if you are an IoT company with advanced infrastructure to deliver data, firmware, and troubleshoot hardware all over the globe…and you want to send your coworkers silly images? Well, you put that infrastructure to work and start dogfooding your own platform. This small, internet-connected displays allows us to try out new things during the holiday season and keep in touch with colleagues across the globe by pushing memes directly to each other’s desks. There’s more explanation below, but we also recorded a video for the nitty-gritty details of how Mike and Dylan built the MePaper 2.0 on a hack day in early December 2025: <div style="position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden;">
      <iframe allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share; fullscreen" loading="eager" referrerpolicy="strict-origin-when-cross-origin" src="https://www.youtube.com/embed/1Y4xenHONB4?autoplay=0&amp;controls=1&amp;end=0&amp;loop=0&amp;mute=0&amp;start=0" style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; border:0;" title="YouTube video"></iframe>
    </div>
</p>
<h2 id="the-origins-of-the-mepaper">The Origins of the MePaper</h2>
<p>The
<a href="https://blog.golioth.io/move-over-internet-of-things-i-built-the-internet-of-memes/">MePaper project started in 2022</a>, also around the holidays. While we continue to support our customers with deployed IoT devices 24/7, the working world seems to slow a bit towards the end of the year. We focus on personal development projects and planning as we move towards a new year. But we also continue to hack and try out new things in hardware and firmware. <a href="https://blog.golioth.io/author/Mike/">Mike</a> noticed that we had this great platform in <a href="https://www.adafruit.com/product/4800">the</a> <span style="font-family: Verdana, BlinkMacSystemFont, -apple-system, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;"><a href="https://www.adafruit.com/product/4800">Adafruit MagTag</a>, a development board that is standard-issue for employees at the company. We had previously used it for in-person and remote training for Zephyr, but <em>hey, how about that ePaper screen on the front??</em> The board features an ESP32-S2 module, a 2.96-inch ePaper display, an accelerometer, a speaker, and four physical buttons. Because the display is ePaper, the image remains visible even when the device is not actively drawing power, making it an ideal “passive” desk accessory.</span> The original version of MePaper was a side project that relied on a Slack bot. While it worked, the process of sending a meme was difficult. Users had to manually edit images in software like GIMP to ensure they were the correct size, rotated 90 degrees, and converted to a 1-bit black-and-white format. These files were then hosted on Google Drive and distributed via a complex set of commands.</p>
<h2 id="modernizing-the-software-and-hardware">Modernizing the Software and Hardware</h2>
<p>To make the system more usable for coworkers, the project was rebooted during a company hack day into version 2.0. This involved a significant overhaul of the firmware and the user interface for creating and distributing images. But Dylan and Mike wanted to continue to increase usage of platform features. The firmware was updated from an older version of
<a href="https://github.com/golioth/golioth-firmware-sdk">the Golioth Firmware SDK</a> to a modern release (0.5.0 -&gt; 0.21.0). This required moving to newer peripheral drivers provided by the ESP-IDF framework. One of the primary technical hurdles was memory management. The on-chip SRAM of the ESP32-S2 was not large enough to hold the framebuffer for the images. To solve this, Mike utilized the 2MB of external <a href="https://docs.espressif.com/projects/esp-idf/en/stable/esp32/api-guides/external-ram.html">SPI PSRAM available</a> on the MagTag board/ESP32 module, allowing for smooth image processing and display updates. The re-written hardware drivers control things like the screen (of course), the buttons, the buzzer, the LEDs. The USB interface, including the serial terminal out of the device, is all captured within the ESP32-S2 module. For the image format, the project continues to use t<a href="https://en.wikipedia.org/wiki/Netpbm">he Portable Bit Map (PBM) specification</a>. This is a 1-bit color depth format that maps perfectly to the requirements of the ePaper display’s byte-ordering. By using this format, the firmware can simply download the file, strip the simple header, and pipe the data directly to the screen without needing complex decoding libraries.</p>
<h2 id="building-the-vibe-coded-web-application">Building the Vibe-Coded Web Application</h2>
<p>The most visible change in version 2.0 is the introduction of a professional-looking web application. This interface was built using “vibe-coding” techniques, where AI agents and large language models were used to generate the front-end code and back-end image databases. This allowed the team to create a sophisticated meme builder without the overhead of manual traditional coding. Compared to 2023, this is much improved. The new web app eliminates the need for external photo editors. Users can simply upload any image file, and the app provides tools to zoom, reposition, and add custom text to the top or bottom of the meme. Once the user is happy with the preview, they click a button to “beam” the meme to the fleet. The application handles the technical conversion into the PBM format automatically, ensuring the image looks correct on the low-resolution ePaper screen.</p>
<h2 id="leveraging-global-iot-infrastructure">Leveraging Global IoT Infrastructure</h2>
<p>MePaper 2.0 is more than just a toy; it is a demonstration of how enterprise IoT features can be used for creative applications. The project utilizes several core platform features:</p>
<ul>
<li><strong>REST API</strong> – The web app uses <a href="https://docs.golioth.io/reference/management-api/">the Golioth management REST API</a> to upload completed meme files as artifacts from the web backend built for creating/viewing memes</li>
<li><strong>Device Settings</strong> – To avoid the inefficiency of using the full firmware update system for every image, the team uses <a href="https://docs.golioth.io/device-management/settings/#pay-attention-to-the-precedence-order-of-the-overrides">device settings</a>. This allows them to push specific image versions to the entire fleet or target specific individuals and groups.</li>
<li><strong>Asset storage</strong> – In addition to firmware images that are delivered to the fleet of devices as <a href="https://docs.golioth.io/device-management/ota/">Over-The-Air firmware updates</a>, the formatted images (in PBM format) are stored as assets on the Golioth platform and then referenced with settings, mentioned above.</li>
<li><a href="https://docs.golioth.io/data-routing/#pipelines"><strong>Golioth Pipelines</strong></a> – The four buttons on the MagTag were turned into a gamified voting system. When a user receives a meme, they can press a button to rate it. This sends an integer through a pipeline to a real-time leaderboard on the web gallery, fostering a sense of competition among the “memers” in the company.</li>
</ul>
<h2 id="seamless-setup-and-security">Seamless Setup and Security</h2>
<p>Security is important at Golioth. But traditionally (and not just at Golioth), the most secure methods didn’t translate to IoT very well due to overhead and processing power. So we put
<a href="https://blog.golioth.io/new-console-feature-the-certificate-generator/">improved Golioth workflows around certificates</a> and some existing Espressif tooling to improve the process. <span style="font-family: Verdana, BlinkMacSystemFont, -apple-system, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;"><a href="https://blog.golioth.io/author/Dylan/">Dylan</a> leaned on </span><a style="font-family: Verdana, BlinkMacSystemFont, -apple-system, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;" href="https://espressif.github.io/esptool-js/">esptool-js</a><span style="font-family: Verdana, BlinkMacSystemFont, -apple-system, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;">, which allows communication to the device using Web Serial. The vibe coded front-end interacts directly with the device and prompts the user for all the steps they need to get the device online. First there is an erase cycle for the device’s flash memory. Then an installation initiates using a standard device firmware image and a device certificate that is provisioned for each user. Since it’s a closed system only available to Golioth employees, we provision a device certificate onto the same project and pair it up with the latest firmware at a memory offset (the cert is stored in a LittleFS partition). Once that bundle of device firmware and certificate is installed into the device, the user can directly configure Wi-Fi credentials without ever opening a terminal or a command-line tool. This ensures that the “Internet of Memes” remains secure while remaining easy for non-technical staff to deploy.</span></p>
<h2 id="dogfooding-golioth-features-even-during-the-holidays">Dogfooding Golioth features, even during the holidays</h2>
<p>Sometimes the best way to check in on a platform that we have been building for 5 years is to put it to the test; we created a facsimile of a connected product that our customers might build (with some obvious silly vibes mixed in). MePaper 2.0 represents the company’s commitment to “
<a href="https://blog.golioth.io/demo-culture-at-golioth/">Demo Culture</a>“. That is, the idea of testing products in real-world scenarios to identify pain points, find creative solutions, and share with coworkers. By turning the MagTag into a dedicated social terminal, the team has created a way to share joy and maintain a cohesive culture in a fully remote environment. It serves as a reminder that even the most advanced IoT infrastructure can be used to build something as simple and human as a shared laugh.</p>
]]></content:encoded></item><item><title>Golioth Firmware SDK v0.22.0</title><link>https://blog.golioth.io/golioth-firmware-sdk-v0-22-0/</link><guid>https://blog.golioth.io/golioth-firmware-sdk-v0-22-0/</guid><pubDate>Thu, 18 Dec 2025 21:57:43 +0000</pubDate><dc:creator>Sam Friedman</dc:creator><category>Featured</category><category>SDK</category><description>Earlier this week, we released v0.22.0 of the Golioth Firmware SDK, our sixth and final release of 2025. Alongside our usual bevy of bugfixes and minor improvements, this version introduces support for rotating certificates via Golioth’s integration with external PKI providers, routes logs through Pipelines, and reorganizes our firmware update reference implementation.</description><content:encoded><![CDATA[<p>Earlier this week, we released <a href="https://github.com/golioth/golioth-firmware-sdk/releases/tag/v0.22.0">v0.22.0</a> of the <a href="https://github.com/golioth/golioth-firmware-sdk">Golioth Firmware SDK</a>, our sixth and final release of 2025. Alongside our usual <a href="https://github.com/golioth/golioth-firmware-sdk/compare/v0.21.1...v0.22.0">bevy of bugfixes and minor improvements</a>, this version introduces support for <a href="https://blog.golioth.io/introducing-certificate-rotation-with-hosted-pki-providers/">rotating certificates via Golioth’s integration with external PKI providers</a>, routes <a href="https://blog.golioth.io/device-logs-anywhere-with-golioth-pipelines/">logs through Pipelines</a>, and reorganizes our firmware update reference implementation.</p>
<h2 id="certificate-rotation-weve-long-advocated-for-the-use-of-certificates-and-public-key-infrastructure-pki-to-secure-and-identify-devices-compared-to-pre-shared-keys-psks-the-other-form-of-authentication-supported-by-golioth-pki-provides-advantages-in-scale-and-security-by-removing-the-need-to-explicitly-create-and-store-secrets-in-golioth-but-pki-comes-with-additional-operational-challenges-compared-to-psks-which-can-be-especially-difficult-in-an-embedded-context-chief-among-those-is-the-need-to-periodically-rotate-certificates-on-devices-in-the-field-earlier-this-week-we">Certificate Rotation We’ve long advocated for the use of certificates and public key infrastructure (PKI) to secure and identify devices. Compared to pre-shared keys (PSKs), the other form of authentication supported by Golioth, PKI provides advantages in scale and security by removing the need to explicitly create and store secrets in Golioth. But PKI comes with additional operational challenges compared to PSKs, which can be especially difficult in an embedded context. Chief among those is the need to periodically rotate certificates on devices in the field. Earlier this week, we</h2>
<p><a href="https://blog.golioth.io/introducing-certificate-rotation-with-hosted-pki-providers/">announced</a> support for external PKI providers to make certificate rotation easier than ever. In this release of the Golioth Firmware SDK, we’ve added a <a href="https://github.com/golioth/golioth-firmware-sdk/blob/main/include/golioth/pki.h">new service</a> that allows devices to upload a certificate signing request (CSR) to Golioth, where we’ll forward it on to your PKI provider and then send down the resulting signed certificate back to the device. There’s a new <a href="https://github.com/golioth/golioth-firmware-sdk/tree/main/examples/zephyr/certificate_rotation">example</a> in the SDK that shows how it all works. In this example, we take an existing key stored in the filesystem and create a CSR in standard x.509 format using mbedTLS, but the SDK is agnostic to how keys and CSRs are created and stored. For example, you could generate a new key-pair in a secure element on the device, exporting only the CSR while the private key remains in secure storage. We’ll have more to share on secure elements in the new year.</p>
<h2 id="logs-through-pipelines-traditionally-the-golioth-firmware-sdk-has-delivered-device-logs-emitted-by-customer-firmware-directly-to-the">Logs Through Pipelines Traditionally, the Golioth Firmware SDK has delivered device logs emitted by customer firmware directly to the</h2>
<p><a href="https://docs.golioth.io/device-management/logging/">Golioth Logs service</a>. With this release, logs are now delivered to Golioth Pipelines by default, allowing users to transform and route logs to the destination of their choice, including the Golioth Logs service. The interaction with the logging service in the SDK is unchanged – either using the <code>GLTH_LOGx</code> macros or plugging into the platform’s logging subsystem. To preserve logging functionality for existing projects, users will need to either enable <code>CONFIG_GOLIOTH_LOG_LEGACY</code> in the SDK configuration, or add a pipeline to route logs to the destination of their choice, such as this one that <a href="https://console.golioth.io/pipeline?name=Default%20CBOR%20Logs&amp;pipeline=ZmlsdGVyOgogIHBhdGg6ICIvbG9ncyIKICBjb250ZW50X3R5cGU6IGFwcGxpY2F0aW9uL2Nib3IKc3RlcHM6CiAgLSBuYW1lOiBzdGVwLTAKICAgIHRyYW5zZm9ybWVyOgogICAgICB0eXBlOiBjYm9yLXRvLWpzb24KICAgICAgdmVyc2lvbjogdjEKICAgIGRlc3RpbmF0aW9uOgogICAgICB0eXBlOiBsb2dzCiAgICAgIHZlcnNpb246IHYx&amp;enabled=1">sends logs to the Golioth Logs service</a>:</p>
<pre><code class="language-jsx">filter:
  path: "/logs"
  content_type: application/cbor
steps:
  - name: step-0
    transformer:
      type: cbor-to-json
      version: v1
    destination:
      type: logs
      version: v1
</code></pre> You can read more about sendings logs through Pipelines 
<p><a href="https://blog.golioth.io/device-logs-anywhere-with-golioth-pipelines/">here</a>.</p>
<h2 id="firmware-update-reference-golioth-ota-makes-it-easy-to-send-the-right-artifacts-to-devices-in-the-field-once-the-artifacts-get-there-the-device-will-need-to-do">Firmware Update Reference Golioth OTA makes it easy to send the right artifacts to devices in the field. Once the artifacts get there, the device will need to do</h2>
<p><em>something</em> with them, but what exactly is heavily dependent on the specific application running on that device. The SDK has long included a reference firmware update implementation that looks for updates to a single firmware image, and downloads and applies the update using the platform’s default storage partitions and bootloader. For many users, this is all the functionality they need and they can use this implementation as is. Users with more complex application structures, such as <a href="https://blog.golioth.io/how-to-update-a-tensorflow-lite-model-remotely/">AI models</a>, <a href="https://blog.golioth.io/ota-working-with-multiple-images/">multiple MCUs</a> or <a href="https://blog.golioth.io/download-images-to-iot-devices-using-ota/">graphical assets</a>, can build their own update mechanism leveraging the Golioth OTA service. To better reflect the reference nature of the firmware update implementation in the SDK, we’ve moved it out of the core SDK and into the samples. This change also allows us to remove abstractions in the reference implementation and instead use platform-specific API calls, making the sample more useful as a instructional tool. <!-- notionvc: f2fc5e3f-6364-4494-8e34-d9bb5a17a432 --></p>
]]></content:encoded></item><item><title>Device Logs Anywhere with Golioth Pipelines</title><link>https://blog.golioth.io/device-logs-anywhere-with-golioth-pipelines/</link><guid>https://blog.golioth.io/device-logs-anywhere-with-golioth-pipelines/</guid><pubDate>Wed, 17 Dec 2025 16:58:24 +0000</pubDate><dc:creator>Dan Mangum</dc:creator><category>Cloud</category><category>Device Management</category><description>Since the earliest days of the platform, Golioth has supported ingesting device logs and querying them via the console and management API. Combined with the Golioth Firmware SDK’s native integrations with RTOS logging subsystems, this functionality has made it easy to get started with basic device observability. Today we are expanding this capability by introducing support for routing device logs to any destination using Golioth Pipelines, relaxing the requirements on log format and enabling the use of your favorite SaaS and open source logging platforms.</description><content:encoded><![CDATA[<p>Since the earliest days of the platform, Golioth has supported ingesting device logs and querying them via the console and management API. Combined with the Golioth Firmware SDK’s native integrations with RTOS logging subsystems, this functionality has made it easy to get started with basic device observability. Today we are expanding this capability by introducing support for routing device logs to any destination using <a href="https://docs.golioth.io/data-routing">Golioth Pipelines</a>, relaxing the requirements on log format and enabling the use of your favorite SaaS and open source logging platforms.</p>
<h2 id="the-old-way-traditionally-the-golioth-firmware-sdk-has-delivered-device-logs-emitted-by-customer-firmware-to-the">The Old Way Traditionally, the Golioth Firmware SDK has delivered device logs emitted by customer firmware to the</h2>
<p><a href="https://docs.golioth.io/device-management/logging/">Golioth Logs service</a>. Developers do not need to directly interact with the service API, but rather use C macros, such as <code>GLTH_LOGI</code>, to emit messages at their desired log level. When using a supported RTOS, such as <a href="https://github.com/zephyrproject-rtos/zephyr">Zephyr</a>, developers can also leverage the SDK’s <a href="https://docs.zephyrproject.org/latest/services/logging/index.html">logging subsystem</a> integration to deliver log messages from libraries and drivers to the Golioth platform. Behind the scenes, the Golioth Firmware SDK structures and encodes the log messages in a format that is compatible with the Golioth Logs service. It then buffers and sends the logs to the <code>/logs</code> endpoint of the Golioth device API. After the logs are delivered to Golioth, customers can query messages at the device and project level, filtering by metadata and time buckets.</p>
<h2 id="the-new-way-since-introducing-golioth-pipelines-in">The New Way Since introducing Golioth Pipelines in</h2>
<p><a href="https://blog.golioth.io/announcing-pipelines-stream-transform-and-route-iot-data-anywhere/">May 2024</a>, we have seen a wide range of customer product use cases be unlocked by the flexibility of the Golioth platform. The ability to easily send data in any format to any destination means that customers can easily integrate with existing cloud systems, drive down bandwidth costs using compact data encoding schemes, and preserve battery life by batching data payloads and delivering as bulk uploads. While many customers enjoy using Golioth’s internal data destinations, such as LightDB Stream for timeseries data and LightDB State for device status, we have always focused on solving the difficult problem of getting data to the cloud, then letting you use the <a href="https://blog.golioth.io/using-the-best-tool-for-the-job/">best tool for the job</a>. In order to bring this same level of freedom to device logs, the <a href="https://github.com/golioth/golioth-firmware-sdk/releases/tag/v0.22.0">latest version of the Golioth Firmware SDK</a> updates the log processing functionality to route messages to Golioth Pipelines. We have introduced a new Golioth Logs data destination, which handles incoming logs on Pipelines. To replicate the previous functionality, customers <a href="https://console.golioth.io/pipeline?name=Default%20CBOR%20Logs&amp;pipeline=ZmlsdGVyOgogIHBhdGg6ICIvbG9ncyIKICBjb250ZW50X3R5cGU6IGFwcGxpY2F0aW9uL2Nib3IKc3RlcHM6CiAgLSBuYW1lOiBzdGVwLTAKICAgIHRyYW5zZm9ybWVyOgogICAgICB0eXBlOiBjYm9yLXRvLWpzb24KICAgICAgdmVyc2lvbjogdjEKICAgIGRlc3RpbmF0aW9uOgogICAgICB0eXBlOiBsb2dzCiAgICAgIHZlcnNpb246IHYx&amp;enabled=1">can create the following Pipeline</a> in their Golioth project to convert the CBOR encoded log messages to JSON then deliver them to the internal logs service. These logs are then viewable on the console or via the management API.</p>
<pre class="EnlighterJSRAW" data-enlighter-language="yaml">filter:
  path: "/logs"
  content_type: application/cbor
steps:
  - name: step-0
    transformer:
      type: cbor-to-json
      version: v1
    destination:
      type: logs
      version: v1</pre> The 
<p><code>path</code> used for logs delivery can be changed using <code>CONFIG_GOLIOTH_LOG_PIPELINES_PATH</code> and customers who wish to upgrade to <code>v0.22.0</code> of the Golioth Firmware SDK while preserving the previous behavior can set <code>CONFIG_GOLIOTH_LOG_LEGACY=y</code>. With device logs flowing through Pipelines, messages can be transformed and routed to any of the <a href="https://docs.golioth.io/data-routing/destinations/">existing data destinations</a>, such as <a href="https://docs.golioth.io/data-routing/destinations/influxdb">InfluxDB</a>, and we’ll be introducing new destinations for more external logging services, as well as standardized vendor-neutral protocols, such as OpenTelemetry’s <a href="https://opentelemetry.io/docs/specs/otlp/">OTLP</a>, in the coming weeks.</p>
<h2 id="getting-started-to-get-started-add-a-pipeline-to-your-existing-golioth-project-or">Getting Started To get started, add a Pipeline to your existing Golioth project, or</h2>
<p><a href="https://console.golioth.io/">create a new project</a> on Golioth (a logs Pipeline will be automatically created for you), then check out any of the example applications in the Golioth Firmware SDK. If you are interested in support for a specific logging service, let us know by creating a post on the <a href="https://forum.golioth.io/">Golioth Forum</a>.</p>
]]></content:encoded></item><item><title>Introducing Certificate Rotation with Hosted PKI providers</title><link>https://blog.golioth.io/introducing-certificate-rotation-with-hosted-pki-providers/</link><guid>https://blog.golioth.io/introducing-certificate-rotation-with-hosted-pki-providers/</guid><pubDate>Tue, 16 Dec 2025 20:00:24 +0000</pubDate><dc:creator>Trond Snekvik</dc:creator><category>Device Management</category><category>Featured</category><category>Security</category><description>Managing device identity securely at scale is one of the hardest challenges in IoT. Certificates expire, credentials need to be rotated, and operational complexity often forces teams to choose between security best practices and shipping products on time. Today, we’re excited to announce a major step forward: External Public Key Infrastructure (PKI) provider integration in Golioth, starting with support for AWS Private Certificate Authority (Private CA). This new capability enables devices to perform certificate rotation directly through the Golioth Device API, dramatically simplifying secure device lifecycle management.</description><content:encoded><![CDATA[<p>Managing device identity securely at scale is one of the hardest challenges in IoT. Certificates expire, credentials need to be rotated, and operational complexity often forces teams to choose between security best practices and shipping products on time. Today, we’re excited to announce a major step forward: External Public Key Infrastructure (PKI) provider integration in Golioth, starting with support for AWS Private Certificate Authority (Private CA). This new capability enables devices to perform certificate rotation directly through the Golioth Device API, dramatically simplifying secure device lifecycle management.<img src="https://blog.golioth.io/wp-content/uploads/2025/12/golioth_certificate_rotation_aws_pca.png" alt=""></p>
<h2 id="why-certificate-rotation-matters">Why Certificate Rotation Matters</h2>
<p>Golioth supports two device authentication methods: Pre-Shared Keys (PSKs) and X.509 certificates. While PSKs can be appropriate for early development and constrained use cases, short-lived certificates are the recommended approach for production deployments. The reason is simple: PSKs are static secrets. If a PSK is ever extracted from a device, leaked from a manufacturing system, or reused across devices, there is no clean way to limit the blast radius without manually replacing credentials, which often requires physical access to devices already in the field. Short-lived certificates dramatically reduce this risk:</p>
<ul>
<li><strong>Compromise is time-bounded:</strong> Even if a certificate is exposed, it automatically expires.</li>
<li><strong>No shared secrets:</strong> The device’s private key never leaves its secure hardware module.</li>
<li><strong>Routine rotation becomes normal</strong> rather than exceptional.</li>
<li><strong>Revocation and recovery</strong> are built into the model. In other words, certificate rotation shifts security from a “hope it never leaks” posture to a “assume compromise and limit impact” strategy, which is the foundation of modern, large-scale IoT security.</li>
</ul>
<blockquote>
<p>Until now, many teams stuck with PSKs not because they were more secure, but because certificate rotation was operationally hard. With Golioth’s new hosted PKI integration, that trade-off disappears.</p></blockquote>
<h2 id="whats-new-hosted-pki-integration">What’s New: Hosted PKI Integration</h2>
<p>From today, Golioth supports integration with external, hosted PKI providers, allowing Golioth to maintain an automatically updated list of Certificate Authority certificates for your project. The integration also supports issuing device certificates through Golioth’s device API, enabling seamless certificate rotation for your devices in the field. For the initial launch, Golioth integrates with</p>
<p><a href="https://docs.aws.amazon.com/privateca/latest/userguide/PcaWelcome.html">AWS Private CA</a>, a fully managed certificate authority service from AWS. We have designed the external PKI integration to be extensible, and we plan to add support for other hosted PKI providers in the near future.</p>
<h2 id="how-it-works">How It Works</h2>
<p>At a high level, the certificate rotation workflow looks like this:</p>
<ol>
<li>A device running the Golioth Firmware SDK initiates a certificate rotation request.</li>
<li>Golioth securely brokers the request to the configured external PKI provider.</li>
<li>A new device certificate is issued by the external PKI provider (e.g. AWS Private CA)</li>
<li>Golioth sends the new certificate to the device, which can install and start using it without any downtime. All of this happens using Golioth’s existing infrastructure, allowing certificate rotation to happen securely, without the need for glue code, custom services, or bespoke provisioning pipelines.</li>
</ol>
<h2 id="whats-next">What’s Next?</h2>
<p>While AWS Private CA is the first supported provider, we plan to add support for other hosted PKI providers in the near future. We will also be partnering with silicon vendors to offer secure provisioning services. With Golioth’s unified certificate rotation API, you should be able to choose the PKI provider that fits your organization, without changing your device firmware or security model.</p>
<p><a href="https://forum.golioth.io">Let us know</a> which PKI providers you would like to integrate with to simplify your certificate management!</p>
<h2 id="get-started-today">Get Started Today</h2>
<p>If you’re already using Golioth, you can start experimenting with hosted PKI-backed certificate rotation today using the Golioth Firmware SDK and AWS Private CA.</p>
<ul>
<li>Read how to set up your integration in our <a href="https://docs.golioth.io/connectivity/credentials/pki">documentation</a></li>
<li>Try our <a href="https://github.com/golioth/golioth-firmware-sdk/tree/main/examples/zephyr/certificate_rotation">Certificate rotation firmware example</a> Security shouldn’t require extra infrastructure or custom code, and with seamless certificate rotation, we believe building a secure products will be a little easier.</li>
</ul>
]]></content:encoded></item><item><title>Bluetooth at Golioth, so far (2025)</title><link>https://blog.golioth.io/bluetooth-at-golioth-so-far-2025/</link><guid>https://blog.golioth.io/bluetooth-at-golioth-so-far-2025/</guid><pubDate>Thu, 11 Dec 2025 17:17:20 +0000</pubDate><dc:creator>Sam Friedman</dc:creator><category>Bluetooth</category><description>Golioth has introduced a ton of of new capabilities for users who want to use Bluetooth to connect their products to the Cloud. I thought I’d give a quick rollup of where we are with Golioth Connectivity and Pouch, which are how we enable indirectly connected devices to talk to the Cloud. We have been working on this capability for the past 2 years, and we launched ‘public beta’ back in August. This article will be a slice in time for the things we have done so far, so be sure to check out the above repositories for the latest in each area…or keep an eye on the blog for the latest Bluetooth news.</description><content:encoded><![CDATA[<p>Golioth has introduced a ton of of new capabilities for users who want to use Bluetooth to connect their products to the Cloud. I thought I’d give a quick rollup of where we are with <a href="https://golioth.io/connectivity">Golioth Connectivity</a> and <a href="https://github.com/golioth/pouch">Pouch</a>, which are how we enable indirectly connected devices to talk to the Cloud. We have been working on this capability for the past 2 years, and <a href="https://blog.golioth.io/bluetooth-open-beta/">we launched ‘public beta’ back in August</a>. This article will be a slice in time for the things we have done so far, so be sure to check out the above repositories for the latest in each area…or keep an eye on the blog for the latest Bluetooth news.</p>
<h2 id="currently-available-with-golioth-connectivity--bluetooth">Currently available with Golioth Connectivity / Bluetooth</h2>
<p>As of December 2025, we have a fully functional system that security transits Bluetooth packets between low power devices up to the Cloud and back again. This includes our streaming data feature (tied to Golioth Pipelines), the Golioth Settings Service, and Over-The-Air Firmware Updates. This allows our users to create fully featured demos and products, and many are already moving towards productization.</p>
<h3 id="chip-support">Chip support</h3>
<p>Our current implementation is built on top of Zephyr and their popular Bluetooth support throughout the industry. That means we support a wide range of vendors, almost with no customization needed between them. We have shown support for many of the Bluetooth chip vendors in Zephyr</p>
<ul>
<li><a href="https://forum.golioth.io/t/enabling-espressif-parts-with-golioth-s-bluetooth-support/1650">Espressif</a></li>
<li><a href="https://forum.golioth.io/t/enabling-nordic-semiconductor-parts-with-golioths-bluetooth-support/1652">Nordic</a></li>
<li><a href="https://forum.golioth.io/t/enabling-nxp-parts-with-golioth-s-bluetooth-support/1649">NXP</a></li>
<li><a href="https://forum.golioth.io/t/enabling-renesas-dialog-parts-with-golioths-bluetooth-support/1651">Renesas</a></li>
<li><a href="https://forum.golioth.io/t/enabling-silabs-parts-with-golioths-bluetooth-support/1648">SiLabs</a></li>
<li><a href="https://forum.golioth.io/t/enabling-st-parts-with-golioths-bluetooth-support/1642">ST Micro</a> This is not all the device types that</li>
</ul>
<p>_can _utilize Golioth capabilities, but instead are the ones we have tested and continue to test.</p>
<h3 id="gateway-reference-implementation">Gateway Reference Implementation</h3>
<p>In order for indirectly connected devices to talk back to the cloud, they need to transit through a trusted or semi-trusted gateway. We have built a reference implementation that does just that, supporting different connectivity methods to target price points and application spaces that match our customers’ needs. We offer precompiled binaries for our reference implementation for the following boards</p>
<ul>
<li><a href="https://www.nxp.com/design/design-center/development-boards-and-designs/FRDM-RW612">NXP’s FRDM-RW612</a> Ethernet/Wifi dev board (~$30)</li>
<li><a href="https://www.nordicsemi.com/Products/Development-hardware/Nordic-Thingy-91-X">Nordic Semiconductor’s Thingy 91:x</a> Cellular platform (~$99)</li>
<li><a href="https://www.nordicsemi.com/Products/Development-hardware/nRF9160-DK">Nordic Semiconductor’s nRF9160-DK</a> Cellular dev board (~$129)</li>
<li><a href="https://www.ezurio.com/iot-devices/bluetooth-iot-devices/sentrius-mg100-gateway-lte-mnb-iot-and-bluetooth-5">Ezurio’s Sentrius MG100</a> Cellular product (~$150)</li>
</ul>
<h3 id="secure-by-default">Secure by default</h3>
<p>As in any Golioth endeavor, security is a first concern, not something left until the end of the development cycle. End-to-end encryption support is default and neither the gateway nor a bad actor with an SDR can peer in on the packets transiting through to the Cloud. We’ve always required that traffic going to Golioth is encrypted, but the new requirement is that any device using Pouch must also utilize certificates (instead of PSKs). Using certificates ensures that Golioth is never in possession of private key material for the device, and supports provisioning fleets at scale.
<span style="font-family: Verdana, BlinkMacSystemFont, -apple-system, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;">Certs are easier than ever on our platform, including <a href="https://blog.golioth.io/new-console-feature-the-certificate-generator/">a new client side tool to generate temporary device certs</a>. </span><span style="font-family: Verdana, BlinkMacSystemFont, -apple-system, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;">We have also been doing <a href="https://blog.golioth.io/unwrapping-certificates/">education around how certs work</a>. </span><span style="font-family: Verdana, BlinkMacSystemFont, -apple-system, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;">Future education will be around the private key never leaving the device, yet still having accessible sane platforms for manufacturing devices (since that’s the goal for all of us)</span></p>
<h3 id="over-the-air-ota-firmware-update-works-out-of-the-box">Over-The-Air (OTA) firmware update works out of the box</h3>
<p>By far one of our most popular features, we made sure firmware update is baked in for our standard BLE_GATT demo in the Pouch repository.
<a href="https://www.youtube.com/watch?v=4PSd-HgMh4w">You can see it working in this video</a>. For a more in-depth look, you can also see how we<span style="font-family: Verdana, BlinkMacSystemFont, -apple-system, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;"> </span><a style="font-family: Verdana, BlinkMacSystemFont, -apple-system, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;" href="https://blog.golioth.io/visualizing-bluetooth-ota-firmware-updates/">utilized the LED display on our demo board to showcase OTA working</a>. The main takeaway is that your Bluetooth updates are already working as soon as you choose to use Golioth Connectivity.</p>
<h3 id="ready-made-examples">Ready-made examples</h3>
<p>In addition to the demos above, we have new Reference Designs that highlight real-world use cases. Our
<span style="font-family: Verdana, BlinkMacSystemFont, -apple-system, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;"> </span><a style="font-family: Verdana, BlinkMacSystemFont, -apple-system, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;" href="https://blog.golioth.io/visualizing-bluetooth-ota-firmware-updates/">Digital Signage</a> design shows how you can talk to smaller Bluetooth devices from a gateway and send out live commands to reconfigure devices in the field, especially over a short area. For people looking to use a single Bluetooth device with multiple gateways, we also show how we can <a style="font-family: Verdana, BlinkMacSystemFont, -apple-system, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;" href="https://blog.golioth.io/bluetooth-roaming-with-golioth-connectivity/">roam between gateways</a>, a key feature in asset tracking. In the future we expect to see more demos revolving around asset tracking and mobile app + gateway combinations. Pouch already supports large payload upload/download, so you could reasonably expect to see more AI/ML examples coming down the pipe too.</p>
<h2 id="pattern-matching-for-your-next-bluetooth-project">Pattern matching for your next Bluetooth project</h2>
<p>We’ve been thinking about how our customers start up new Bluetooth projects or look to update existing projects. Sure, we want to make sure we serve the broadest Bluetooth market, but we also want to make sure we are covering use cases our customers are encountering on a daily basis. As mentioned above, we are thinking more about the mobile app in relation to Bluetooth and Cloud capable devices. G
<span style="font-family: Verdana, BlinkMacSystemFont, -apple-system, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;">olioth Connectivity can live alongside other GATT profiles and enables cloud capabilities without another full cycle of development. Basically, “Add cloud management and reporting to your Bluetooth device”. </span><span style="font-family: Verdana, BlinkMacSystemFont, -apple-system, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;">This includes adding OTA to any Bluetooth product, which is unfortunately an afterthought in many product design cycles. If you’re not already discussing compliance with </span><a style="font-family: Verdana, BlinkMacSystemFont, -apple-system, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;" href="https://blog.golioth.io/an-introduction-to-the-cyber-resilience-act-cra-with-kate-stewart-of-the-zephyr-project/">the EU’s new Cyber Resilience Act</a> <span style="font-family: Verdana, BlinkMacSystemFont, -apple-system, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;">(CRA), you might want to start. Our platform helps to satisfy many of the requirements for the 2027 due dates. </span> <span style="font-family: Verdana, BlinkMacSystemFont, -apple-system, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;">Overall, Golioth cuts down on development requirements for Bluetooth applications. Fewer app developers are required to stand up a prototype, and the communication between app and device is clearer with Golioth Connectivity. Whether you’re a hardware/firmware team looking to do less on the app/cloud side or you’re a cloud team looking to quickly enable Bluetooth applications with remote control and secure updates, we have something for everyone. </span></p>
<h2 id="whats-coming-in-2026">What’s coming in 2026</h2>
<p>We already have changes in our internal testing setup and we are validating them in an on-going basis. Here are some of the high level changes you can expect in the new year:</p>
<ul>
<li><strong>Performance improvements</strong> – A new transport protocol between devices and gateways, resulting in significant increases in transfer speeds without adjusting Bluetooth radio parameters that impact power.</li>
<li><strong>Better customization</strong> – Pouch has a specific way of advertising and syncing, but future improvements will allow choosing which devices to sync, when to sync them, and when to disconnect from them. This will drive better bandwidth utilization for large networks and more efficient operations.</li>
<li><strong>Gateways everywhere</strong> – Pouch protocol and the ability to shuttle packets up to the Cloud already is working on <a href="#gateway-reference-implementation">the embedded hardware listed above</a>. This code can also be shrunk down to fit in other form factors than standalone gateways. We’ll show how to put gateway functionality into more devices, for better connectivity of all devices.</li>
<li><strong>More hardware</strong> – As more chip vendors join the Zephyr ecosystem, our hardware choices become more bountiful. We will continue testing for different hardware options and sharing about any required changes to the underlying firmware to make it work with Golioth.</li>
<li><strong>Improved transport-level security</strong> – We already offer/require end-to-end encryption from the device to the cloud. In the new year, we’ll also enable Bluetooth pairing to encrypt and optionally authenticate communication between devices and gateways. Security is a first principle at Golioth. Imagine: these are just the things we’re willing to tell you about! Many more are planned for the new year and are already in early-concept phases. If you have any suggestions, drop them on</li>
</ul>
<p><a href="https://forum.golioth.io">the Golioth Forums</a> or let our <a href="mailto:devrel@golioth.io">devrel team</a> know if you need any help getting started today.</p>
]]></content:encoded></item><item><title>Using Custom Work Queues for Sensor Readings in Zephyr</title><link>https://blog.golioth.io/using-custom-work-queues-for-sensor-readings-in-zephyr/</link><guid>https://blog.golioth.io/using-custom-work-queues-for-sensor-readings-in-zephyr/</guid><pubDate>Tue, 09 Dec 2025 19:07:41 +0000</pubDate><dc:creator>Mike Szczys</dc:creator><category>How To</category><description>Real-time operating systems (RTOS) are incredible at taking a single processor and wisely handling a number of concurrent operations. However, they aren’t headache-free. When it comes to controlling access to hardware peripherals, the Zephyr RTOS offers a number of subsystems that make it easier to handle concurrency issues. Today I’ll discuss using a custom work queue to ensure the sensor readings can be requested at any time, from any thread, while avoiding hard faults.</description><content:encoded><![CDATA[<p>Real-time operating systems (RTOS) are incredible at taking a single processor and wisely handling a number of concurrent operations. However, they aren’t headache-free. When it comes to controlling access to hardware peripherals, the Zephyr RTOS offers a number of subsystems that make it easier to handle concurrency issues. Today I’ll discuss using a <a href="https://docs.zephyrproject.org/latest/kernel/services/threads/workqueue.html">custom work queue</a> to ensure the sensor readings can be requested at any time, from any thread, while avoiding hard faults.</p>
<h2 id="sensor-readings-and-concurrency">Sensor Readings and Concurrency</h2>
<p>The generic “sensor reading” term is a bit sloppy. What we’re actually talking about here is a bus with one or more devices connected to it. We want to make sure that there aren’t multiple threads trying to access the bus at the same time. For the purposes of this example, we’re going to set up a custom work queue to take sensor readings for a sensor connected via i2c. With this approach we can create a different instance of work for each device on the bus and the queue will ensure that they are read in the order each request is received. The queue will be the only thing accessing this bus. Zephyr has a built-in work queue enabled by default called the system work queue. This runs on the main system thread. Since we’re using blocking sensor reads when calling the fetch command, it’s a good idea to separate these calls from the main thread so we’ll stand up our own custom work queue.</p>
<h2 id="a-work-queue-to-take-sensor-readings">A Work Queue to Take Sensor Readings</h2>
<p>When renewing your license you wait in line at the Department of Motor Vehicles (DMV). The work queue functions in the same way. However, what we’re doing under the hood is creating a new thread whose sole purpose is to service a work item until the queue is empty. We’re creating the DMV itself (the work queue) and the people in line (the work). When creating a thread, we need a stack to handle memory and we need to assign a priority to the thread so the scheduler knows how important our work is. In order to use the queue, we need to have work items that tell the queue which functions to run when processing a queue item.</p>
<pre class="EnlighterJSRAW" data-enlighter-language="c">const struct device *const temp_sensor = DEVICE_DT_GET(DT_ALIAS(ambient_temp0));

#define SENSOR_WORKQ_STACK_SIZE 512
#define SENSOR_WORKQ_PRIO 5
struct k_work_q sensor_workq;
struct k_work sensor_work;

K_THREAD_STACK_DEFINE(sensor_workq_stack, SENSOR_WORKQ_STACK_SIZE);

static void sensor_work_handler(struct k_work *work) {
    int err = 0;

    err = sensor_sample_fetch(temp_sensor);
    if (0 != err) {
        LOG_ERR("Failed to fetch sensor reading: %d", err);
        return;
    }


    struct sensor_value temperature;
    err = sensor_channel_get(temp_sensor, SENSOR_CHAN_AMBIENT_TEMP, &temperature);
    if (0 != err) {
        LOG_ERR("Failed to get sensor value: %d", err);
        return;
    }

    LOG_INF("Temperature reading: %d.%06d", temperature.val1, abs(temperature.val2));
}

void sensor_init(void) {
    k_work_queue_init(&sensor_workq);
    k_work_queue_start(&sensor_workq,
                       sensor_workq_stack,
                       K_THREAD_STACK_SIZEOF(sensor_workq_stack),
                       SENSOR_WORKQ_PRIO,
                       NULL);
    k_work_init(&sensor_work, sensor_work_handler);
}</pre> The sample details the following: 
<ul>
<li>Values for the work queue stack size and priority</li>
<li>A struct for the work queue itself, and a struct for the work we will submit to it</li>
<li>A macro to allocate the stack memory</li>
<li>The work handler function that will be called when the work item is processed</li>
<li>An initialization function to init/start the queue and init the work This is the meat and potatoes of the operation, and most of our job is already done. The</li>
</ul>
<p><code>sensor_work_handler()</code> uses a familiar pattern to fetch the sensor value from the hardware, and then read the value out of the <code>sensor_value</code> struct where it was stored. To use the new work queue, we run the initialization function and then we can begin submitting work.</p>
<pre class="EnlighterJSRAW" data-enlighter-language="c">sensor_init();

while (true)
{
    k_work_submit_to_queue(&sensor_workq, &sensor_work);
    k_msleep(5000);
}
</pre> If you’re already familiar with the system work queue you may notice that instead of 
<p><code>k_work_submit()</code> we’re calling <code>k_work_submit_to_queue()</code>. This allows us to specify which work queue the work should be sent to. Note that we declared our work item globally. If it’s already in the queue and we try to submit it again, nothing will happen. If you need to submit the same work multiple times you need to initialize multiple instances, each with their own pointers. There are also specialized approaches for rescheduling and delaying work items. For a guided tour of those topics, check out Chris Gammell’s post on <a href="https://blog.golioth.io/send-when-idle-using-zephyr-and-golioth-pouch/">Send-when-idle using Zephyr and Golioth Pouch</a>. When we build and run the code we can see that it outputs temperature readings on a regular cadence.</p>
<pre class="EnlighterJSRAW" data-enlighter-language="generic">[01:05:31.971,657] <inf> hello_zephyr: Temperature reading: 24.750000
[01:05:36.971,771] <inf> hello_zephyr: Temperature reading: 24.812500
[01:05:41.971,855] <inf> hello_zephyr: Temperature reading: 24.875000
[01:05:46.971,960] <inf> hello_zephyr: Temperature reading: 24.937500
[01:05:51.972,050] <inf> hello_zephyr: Temperature reading: 24.875000
[01:05:56.972,149] <inf> hello_zephyr: Temperature reading: 24.937500</pre> What about memory usage? In the shell we can view the memory usage for our work queue by using the 
<p><code>kernel thread stacks</code> command. Keep reading to see the output of that command.</p>
<h2 id="adding-a-stack-name-for-better-logs-and-debugging">Adding a Stack Name for Better Logs and Debugging</h2>
<pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-highlight="1">0x10045870                                  (real size  512):   unused   96     usage  416 /  512 (81 %)
0x1005a0d8 coap_client                      (real size 6144):   unused 4312     usage 1832 / 6144 (29 %)
0x100468c0 net_socket_service               (real size 1200):   unused  728     usage  472 / 1200 (39 %)
0x10046b58 rx_q[0]                          (real size 1792):   unused 1040     usage  752 / 1792 (41 %)
0x10046a68 net_mgmt                         (real size  800):   unused  552     usage  248 /  800 (31 %)
0x10045e40 shell_uart                       (real size 2048):   unused  888     usage 1160 / 2048 (56 %)
0x100470c8 sysworkq                         (real size 1024):   unused  496     usage  528 / 1024 (51 %)
0x10046d48 ENET_RX                          (real size 1600):   unused 1200     usage  400 / 1600 (25 %)
0x10045948 logging                          (real size 1536):   unused  664     usage  872 / 1536 (56 %)
0x10046e80 idle                             (real size  320):   unused  256     usage   64 /  320 (20 %)
0x10046f38 main                             (real size 4096):   unused 3264     usage  832 / 4096 (20 %)
0x100539a8 IRQ 00                           (real size 2048):   unused 1832     usage  216 / 2048 (10 %)</pre> There are a couple of things that I pay attention to based on the 
<p><code>kernel thread stacks</code> output. First, our thread doesn’t name its stack so we have to deduce that the top line without a name is the memory usage for our sensor work queue. The second is that 81% of that stack is used, which makes me feel just a bit claustrophobic. In this example we only have one instance of work that is being submitted to the queue, so our stack usage is not going to grow beyond what is shown after the work has run at least once. If there were other types of work, or if you have work handlers that use a variable amount of stack space, it’s important to do some testing to ensure you have enough head room to avoid an overflow.</p>
<pre class="EnlighterJSRAW" data-enlighter-language="c">void sensor_init(void) {
    k_work_queue_init(&sensor_workq);

    struct k_work_queue_config sensor_workq_stack_config = {.name = "sensor_workq"};
    k_work_queue_start(&sensor_workq,
                       sensor_workq_stack,
                       K_THREAD_STACK_SIZEOF(sensor_workq_stack),
                       SENSOR_WORKQ_PRIO,
                       &sensor_workq_stack_config);
    k_work_init(&sensor_work, sensor_work_handler);
}
</pre> Naming the stack is as simple as supplying a 
<p><code>k_work_queue_config</code> struct with the <code>name</code> member set. Once recompiled you can view the stacks with our brand new name showing proudly.</p>
<pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-highlight="1">0x10045870 sensor_workq                     (real size  512):   unused   96     usage  416 /  512 (81 %)
0x1005a0d8 coap_client                      (real size 6144):   unused 4312     usage 1832 / 6144 (29 %)
0x100468c0 net_socket_service               (real size 1200):   unused  728     usage  472 / 1200 (39 %)
0x10046b58 rx_q[0]                          (real size 1792):   unused 1040     usage  752 / 1792 (41 %)
0x10046a68 net_mgmt                         (real size  800):   unused  552     usage  248 /  800 (31 %)
0x10045e40 shell_uart                       (real size 2048):   unused  888     usage 1160 / 2048 (56 %)
0x100470c8 sysworkq                         (real size 1024):   unused  496     usage  528 / 1024 (51 %)
0x10046d48 ENET_RX                          (real size 1600):   unused 1200     usage  400 / 1600 (25 %)
0x10045948 logging                          (real size 1536):   unused  664     usage  872 / 1536 (56 %)
0x10046e80 idle                             (real size  320):   unused  256     usage   64 /  320 (20 %)
0x10046f38 main                             (real size 4096):   unused 3264     usage  832 / 4096 (20 %)
0x100539a8 IRQ 00                           (real size 2048):   unused 1832     usage  216 / 2048 (10 %)
</pre> Setting this name is not just a handy way to understand the shell output for stack sizes. This will also be used for thread-aware debugging and shown when there is a hard-fault. 
<pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-highlight="8">[00:04:12.155,700] <err> os: ***** USAGE FAULT *****
[00:04:12.155,717] <err> os:   Stack overflow (context area not valid)
[00:04:12.155,744] <err> os: r0/a1:  0x18026768  r1/a2:  0x61000000  r2/a3:  0xaaaaaaaa
[00:04:12.155,750] <err> os: r3/a4:  0x00000002 r12/ip:  0xaaaaaaaa r14/lr:  0xaaaaaaaa
[00:04:12.155,754] <err> os:  xpsr:  0xaaaaaa00
[00:04:12.155,781] <err> os: Faulting instruction address (r15/pc): 0xaaaaaaaa
[00:04:12.155,812] <err> os: >>> ZEPHYR FATAL ERROR 2: Stack overflow on CPU 0
[00:04:12.155,840] <err> os: Current thread: 0x10045870 (sensor_workq)
[00:04:12.290,057] <err> os: Halting system
</pre> It’s very easy to see from the ouptut above that the 
<p><code>sensor_workq</code> overflowed. If we hadn’t added the name, <code>unknown</code> would have been shown at the end of the <code>Current thread</code> log output.</p>
<h2 id="go-beyond-logging">Go Beyond Logging</h2>
<p>It’s easy to envision a system that:</p>
<ol>
<li>Takes readings on a regular cadence</li>
<li>Takes additional readings based on stimulus like motion or a threshold-based trigger</li>
<li>Takes additional readings on demand (from a button or network-based request) The work queue is essential for coordinating between different threads (or hardware interrupts) from which these triggers arise. However, simply logging the data is not very useful. There are two very handy approaches to recording those readings that are a natural follow to today’s demonstration. The first is</li>
</ol>
<p><a href="https://blog.golioth.io/zephyr-threads-work-queues-message-queues-and-how-we-use-them/">using Zephyr message queues to cache sensor readings</a> and <a href="https://blog.golioth.io/how-to-send-iot-data-in-batches/">sending them to the cloud as batch data</a>. The second is using <a href="https://blog.golioth.io/how-to-use-zephyr-zbus-to-communicate-between-threads/">Zephyr’s ZBUS system to publish each reading</a> to other processes listening/observing in the system. Stay tuned for more feature highlights from Zephyr and Golioth applications that put them to work.</p>
]]></content:encoded></item><item><title>Send-when-idle using Zephyr and Golioth Pouch</title><link>https://blog.golioth.io/send-when-idle-using-zephyr-and-golioth-pouch/</link><guid>https://blog.golioth.io/send-when-idle-using-zephyr-and-golioth-pouch/</guid><pubDate>Thu, 04 Dec 2025 15:42:32 +0000</pubDate><dc:creator>Chris Gammell</dc:creator><category>Bluetooth</category><category>Zephyr</category><description>I have been building lots of new Bluetooth applications around Golioth Connectivity and Pouch. This is what our users (me included!) use to send data over a Bluetooth link, up through a gateway, and out to the Cloud. As someone who regularly works with the Golioth Firmware SDK and Zephyr, the data handling in the Pouch SDK feels very familiar, despite the link to the cloud being over Bluetooth. I get to treat the connection like any other network connection I normally have when working with Golioth. But I found a new trick / design pattern that I thought was interesting. It simplifies sending up data once device activity is no longer happening. This fits in well with Golioth’s Pouch protocol and applications we are targeting, so let’s take a look at how to put it all together.</description><content:encoded><![CDATA[<p>I have been building lots of new Bluetooth applications around <a href="https://golioth.io/connectivity">Golioth Connectivity</a> and <a href="https://github.com/golioth/pouch/">Pouch</a>. This is what our users (me included!) use to send data over a Bluetooth link, up through a gateway, and out to the Cloud. As someone who regularly works with the Golioth Firmware SDK and Zephyr, the data handling in the Pouch SDK feels <em>very</em> familiar, despite the link to the cloud being over Bluetooth. I get to treat the connection like any other network connection I normally have when working with Golioth. But I found a new trick / design pattern that I thought was interesting. It simplifies sending up data once device activity is no longer happening. This fits in well with Golioth’s Pouch protocol and applications we are targeting, so let’s take a look at how to put it all together.</p>
<h2 id="sending-data-when-the-device-is-idle">Sending data when the device is idle</h2>
<p>Most of the demos I have been working on for the past few months are based on the Pouch SDK, which enables Bluetooth-to-Cloud bidirectional data flow via Golioth Connectivity.
<a href="https://github.com/golioth/pouch/tree/main/examples/ble_gatt">The BLE-GATT demo in the Pouch SDK repository</a> is my starting point for most of the things I am building. Out of the gate, I get data uplink (sending time series data up to Golioth), settings downlink (configuration data synced to each device), and Over-The-Air firmware updates. I also get to <a href="https://blog.golioth.io/bluetooth-roaming-with-golioth-connectivity/">roam between different gateways</a> thanks to how Pouch is architected. I have also been working on waking on movement using the LIS2DH12 accelerometer on <a href="https://blog.golioth.io/open-sourcing-the-tikk-demo-board/">the Tikk board</a>. When we wake up the board, I want to take a reading and quickly go back to sleep. That’s relatively straightforward. But when should we send that data up to the cloud? Immediately? On a set schedule? For the asset tracking applications I am targeting, I see a couple of themes:</p>
<ul>
<li>Efficient battery operation</li>
<li>Low number of overall events</li>
<li>Large time gaps between events (irregular intervals)</li>
<li>Small data payloads I think it makes the most sense for my battery and for data bandwidth to send data when the devices are no longer moving.</li>
</ul>
<h2 id="design-patterns-for-send-when-idle">Design patterns for send-when-idle</h2>
<p>This situation
<em>could be</em> a good use case for an event-driven state machine, which you can build using built-in Zephyr features. <a href="https://docs.zephyrproject.org/latest/services/smf/index.html">The State Machine Framework (SMF)</a> gives you all the tools you need to build a state machine that covers a variety of scenarios. But given how this sample requires relatively few states, a state machine is likely overkill. Instead, I will lean on the idea of “<a href="https://docs.zephyrproject.org/latest/kernel/services/threads/workqueue.html#delayable-work">delayable work</a>” (insert joke here about me procrastinating writing this article). These are part of <a href="https://docs.zephyrproject.org/latest/kernel/services/threads/workqueue.html">the Workqueue feature of Zephyr</a> that <a href="https://blog.golioth.io/zephyr-threads-work-queues-message-queues-and-how-we-use-them/">we have written about before.</a> I think about them as “thread-like behavior without all the dedicated memory”. Honestly the setup of a thread is pretty easy in Zephyr, but workqueues make it even easier.</p>
<h2 id="using-k_work_schedule-to-schedule-bluetooth-syncs-pouch-initiates-a-connection-to-the-cloud-by-advertising-to-the-gateway-that-there-is-data-available-at-which-point-a-connection-starts-normally-in-our-sample">Using <code>k_work_schedule</code> to schedule Bluetooth syncs Pouch initiates a connection to the cloud by advertising to the gateway that there is data available, at which point a connection starts. Normally in our sample,</h2>
<p><a href="https://github.com/golioth/pouch/blob/b4c0c7966c1203c97d07d85b3788fcea7cab57b4/examples/ble_gatt/src/main.c#L197">the device starts out requesting a sync on a delay</a> before going into the main loop. When a Pouch session has ended, we schedule it again for another 20 seconds later:</p>
<pre class="EnlighterJSRAW" data-enlighter-language="c" data-enlighter-highlight="18">static void pouch_event_handler(enum pouch_event event, void *ctx)
{
    if (POUCH_EVENT_SESSION_START == event)
    {
        pouch_uplink_entry_write(".s/sensor",
                                 POUCH_CONTENT_TYPE_JSON,
                                 "{\"temp\":22}",
                                 sizeof("{\"temp\":22}") - 1,
                                 K_FOREVER);

        golioth_sync_to_cloud();
    }

    if (POUCH_EVENT_SESSION_END == event)
    {
        service_data.data.flags &= ~POUCH_GATT_ADV_FLAG_SYNC_REQUEST;
        bt_le_adv_update_data(ad, ARRAY_SIZE(ad), NULL, 0);
        k_work_schedule(&sync_request_work, K_SECONDS(20));
    }
}</pre> The 
<p><code>sync_request_work</code> function is just using the <code>K_WORK_DELAYABLE_DEFINE</code> macro to set up the workqueue type and list the handler. In this case, all it’s doing is setting up advertising for the device (which is, of course, configurable for your specific project).</p>
<pre class="EnlighterJSRAW" data-enlighter-language="generic">void sync_request_work_handler(struct k_work *work)
{
    service_data.data.flags |= POUCH_GATT_ADV_FLAG_SYNC_REQUEST;
    bt_le_adv_update_data(ad, ARRAY_SIZE(ad), NULL, 0);
}

K_WORK_DELAYABLE_DEFINE(sync_request_work, sync_request_work_handler);</pre>
<h2 id="waiting-for-downtime-using-k_work_reschedule-in-my-case-i-want-to-call-that-same-delayable">Waiting for downtime using <code>k_work_reschedule</code> In my case, I want to call that same delayable</h2>
<p><code>sync_request_work</code> function. This time, not on a fixed schedule, but when there is no more data coming in from the accelerometer on my board. If I used <code>k_work_schedule</code> each time the accelerometer fired (in an ISR), it would simply execute however many seconds later I set the design (say 20 seconds like in the sample shown above). But that’s not what I want because I would basically get a single accelerometer reading every 20 seconds, executed in the workqueue. For a large number of events in a small amount of time, I would be waiting forever to get all that data up to the cloud. It would be single readings lined up to be transmitted in 20 second increments into the future. Instead, I want to bundle all the readings. Each time I get a hardware interrupt from the sensor and service the ISR, I instead <strong>reset</strong> the 20 second timer (or whatever your delay is). I can do this by firing <code>k_work_reschedule</code> instead of <code>k_work_schedule</code>. That “starts the timer over” each time it is called, effectively kicking the proverbial can down the road. It only advertises that data is ready 20 seconds after the last reading. If an accelerometer event happens 21 seconds after the previous reading…no problem! It starts a new 20 second timer and waits for that data to no longer fire.</p>
<pre class="EnlighterJSRAW" data-enlighter-language="c" data-enlighter-highlight="7">void lis2dh_int_callback(const struct device *dev, struct gpio_callback *cb, uint32_t pins) {

    wake_event_count++;
    current_time_us = k_uptime_get();
    LOG_DBG("wake = %d, time = %" PRIu64 " ms", wake_event_count, current_time_us);

    k_work_reschedule(&sync_request_work, K_SECONDS(20)); // schedule all events to be sent 10 seconds from now, resets if the event happens again

}</pre>
<h2 id="future-work">Future work</h2>
<p>Already, I can hear the howls about all of the corner cases this presents, such as a hyperactive sensor that is taking tons of readings and never has a chance to synchronize with the cloud and offload its data. The howling is in my head as well! But there are a few things we can do. First, I didn’t even mention how the data is being stored while we wait for a Bluetooth sync to occur. That can take
<em>many</em> forms, including a <a href="https://blog.golioth.io/zephyr-threads-work-queues-message-queues-and-how-we-use-them/">message queue</a>, FIFO, ZBus listener, or just shoving into memory somewhere. That is likely the next blog post in this journey. But wherever we <strong>put</strong> the data, we need to reliably <strong>pull</strong> it back out. If I really wanted to stir the pot, I might be so bold to even suggest we offload data to local storage like an SD card (gasp/shock/horror). If we want to set up for a higher data rate scenario, we could also put soft and hard limits on the number of data points that are allowed on the device before we initiate a sync (regardless of motion). Say we get to 100 data points (or some other definable limit) before we actually start advertising. Then the RTOS scheduler would be balancing between handling interrupts from the accelerometer and sending data over Bluetooth. RTOSes are made for such a task! Speaking of definable limits, I also plan to include individual settings that can be configured on each device using <a href="https://blog.golioth.io/a-settings-system-for-any-bluetooth-fleet/">Pouch downlink and Golioth Settings service</a>. These can be tweaked on a per project, per Blueprint, or per device basis. But more importantly, the configurability means that I am not trying to tweak the setting on my bench; I can just live update the values for things like:</p>
<ul>
<li>Delay after the last datapoint</li>
<li>Upper limit on the number of datapoints before we advertise to the gateway</li>
<li>Whether we display activity to the user with LEDs Finally, we will be reconstituting this data cloud-side using</li>
</ul>
<p><a href="https://blog.golioth.io/announcing-pipelines-stream-transform-and-route-iot-data-anywhere/">Golioth Pipelines.</a> There are a lot of interesting things we can do to ensure the data looks like any other data coming from a Golioth device. Did we pique your interest? Are you ready to start taking data and send it at a convenient time? Head over to our forum to ask more about Bluetooth and share your next project idea.</p>
]]></content:encoded></item><item><title>'Using Snippets in Zephyr: a Shorthand for Changing Build Configuration'</title><link>https://blog.golioth.io/using-snippets-in-zephyr-a-shorthand-for-changing-build-configuration/</link><guid>https://blog.golioth.io/using-snippets-in-zephyr-a-shorthand-for-changing-build-configuration/</guid><pubDate>Tue, 02 Dec 2025 15:13:05 +0000</pubDate><dc:creator>Mike Szczys</dc:creator><category>How To</category><description>Zephyr includes a concept called Snippets to assign a nickname to a set of configurations that may include both Kconfig and Devicetree changes. These are quite useful when you need to change more than one symbol and/or tweak your devicetree nodes for a particular build or test. Let’s take a tour of Zephyr Snippets today by focusing on a configuration change that enables the shell and logging.</description><content:encoded><![CDATA[<p>Zephyr includes a concept called <a href="https://docs.zephyrproject.org/latest/build/snippets/index.html">Snippets</a> to assign a nickname to a set of configurations that may include both Kconfig and Devicetree changes. These are quite useful when you need to change more than one symbol and/or tweak your devicetree nodes for a particular build or test. Let’s take a tour of Zephyr Snippets today by focusing on a configuration change that enables the shell and logging.</p>
<h2 id="production-versus-testing">Production Versus Testing</h2>
<p>Snippets make a lot of sense in a bimodal atmosphere, such as the difference between production and testing. It’s unlikely that your production devices will have logging enabled, and you’ll likely disable the shell as well.</p>
<pre class="EnlighterJSRAW" data-enlighter-language="generic">west build -p -b frdm_rw612 examples/zephyr/hello -S shell-logs</pre> With snippets, you can configure your project to build with logging and shell disabled by default, then call your snippet to enable them for a debug session. The example above uses the 
<p><code>-S</code> flag to include a snippet called <code>shell-logs</code> in the build. Let’s look at exactly what makes up this snippet.</p>
<h2 id="snippet-common-configuration-in-a-nice-package">Snippet: Common Configuration in a Nice Package</h2>
<p>The example above calls a snippet that is made up of this file hierarchy.</p>
<pre class="EnlighterJSRAW" data-enlighter-language="generic">snippets
└── shell_log
    ├── shell_log.conf
    └── snippet.yml
</pre> The 
<p><code>snippet.yml</code> file assigns the name of the snippet and adds the <code>shell_log.conf</code> file to the build as an extra config file.</p>
<pre class="EnlighterJSRAW" data-enlighter-language="yaml" data-enlighter-group="1" data-enlighter-title="snippets/shell_log/snippet.yml">name: shell-log
append:
  EXTRA_CONF_FILE: shell_log.conf</pre> If we look in the config file, we see the symbols that are selected by this snippet: 
<pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-group="2" data-enlighter-title="snippets/shell_log/shell_log.conf">CONFIG_LOG=y
CONFIG_LOG_MAX_LEVEL=4

CONFIG_SHELL=y
CONFIG_SHELL_BACKENDS=y
CONFIG_SHELL_BACKEND_SERIAL=y</pre> The final piece in the puzzle is telling Zephyr where to find your snippet files. Do this by 
<p><a href="https://docs.zephyrproject.org/latest/develop/modules.html#module-integration-files-external">using a Zephyr module yaml file</a> to specify where to look for the snippets folder, relative to your project’s root directory.</p>
<pre class="EnlighterJSRAW" data-enlighter-language="yaml" data-enlighter-group="3" data-enlighter-title="zephyr/module.yml">build:
  settings:
    snippet_root: .
</pre> While there is nothing magical happening in snippets, enabling this set of symbols with one human-readable (and memorable) snippet name is pretty nice. The same could be done by adding 
<p><code>-- -DEXTRA_CONF_FILE=snippets/shell_log/shell_log.conf</code> or by adding the symbols to the prj.conf file. But you can see why including the snippet argument is more convenient and less error-prone While it is not demonstrated in this example, the concept is quite extensible. A Devicetree overlay file may be added alongside, or instead of the Kconfig file by adding <code>EXTRA_DTC_OVERLAY_FILE: your_devicetree.overlay</code> in the snippet file append section. Build flags are another option, and all of the additions presented by the snippet may be gated/conditionally included based on board name and domain (eg: when using sysbuild). In fact, let’s jump into some of the quirks that sysbuild brings and how to deal with them.</p>
<h2 id="using-snippets-with-sysbuild-multi-domain-builds">Using Snippets with Sysbuild (multi-domain builds)</h2>
<p>The same snippet example above can be applied to multi-domain builds like
<a href="https://github.com/golioth/golioth-firmware-sdk/tree/main/examples/zephyr/fw_update">Golioth’s firmware update sample</a>.  The syntax looks very much the same but has some unintended consequences.</p>
<pre class="EnlighterJSRAW" data-enlighter-language="generic">west build -p -b frdm_rw612 examples/zephyr/fw_update/ --sysbuild -S shell-log</pre> If we look at the summary of the build for the mcuboot domain, we see our snippet grew the resource usage by quite a bit. 
<pre class="EnlighterJSRAW" data-enlighter-language="generic"># mcuboot without shell-log snippet
Memory region         Used Size  Region Size  %age Used
           FLASH:       51996 B       128 KB     39.67%
             RAM:       44096 B       960 KB      4.49%
            SMU1:        510 KB       510 KB    100.00%
            SMU2:        140 KB       140 KB    100.00%
        IDT_LIST:          0 GB        32 KB      0.00%

# mcuboot with shell-log snippet
Memory region         Used Size  Region Size  %age Used
           FLASH:       77608 B       128 KB     59.21%
             RAM:       49424 B       960 KB      5.03%
            SMU1:        510 KB       510 KB    100.00%
            SMU2:        140 KB       140 KB    100.00%
        IDT_LIST:          0 GB        32 KB      0.00%</pre> Flash usage for the bootloader is a whopping 50% more when including the snippet in the build command! Hey, that’s fine if you have the space available, which we do in this sample. However, the bootloader partition is commonly sized for the build without extra space and this snippet would cause the build to fail. Most likely you are turning on shell and logging just to work with the application. But snippets are included in all domains during a sysbuild. The solution is to namespace your snippet inclusion. 
<pre class="EnlighterJSRAW" data-enlighter-language="generic">west build -p -b frdm_rw612 examples/zephyr/fw_update/ --sysbuild -- -Dfw_update_SNIPPET="shell-log"</pre> This is a slightly different approach which leverages cmake to add a list of snippets targeting one specific domain. In this case that’s 
<p><code>fw_update</code>, but could also have targeted only the bootloader by using <code>-Dmcuboot_SNIPPET=&quot;name-of-your-snippet&quot;</code>. During the build, Zephyr will report the conf and overlay files included in the build so you can check for the expected files to confirm the snippet worked.</p>
<pre class="EnlighterJSRAW" data-enlighter-language="generic">Parsing /home/mike/golioth-compile/golioth-firmware-sdk/modules/lib/golioth-firmware-sdk/examples/zephyr/fw_update/Kconfig
Loaded configuration '/home/mike/golioth-compile/golioth-firmware-sdk/zephyr/boards/nxp/frdm_rw612/frdm_rw612_defconfig'
Merged configuration '/home/mike/golioth-compile/golioth-firmware-sdk/modules/lib/golioth-firmware-sdk/examples/zephyr/fw_update/prj.conf'
Merged configuration '/home/mike/golioth-compile/golioth-firmware-sdk/modules/lib/golioth-firmware-sdk/examples/zephyr/fw_update/boards/frdm_rw612.conf'
Merged configuration '/home/mike/golioth-compile/golioth-firmware-sdk/modules/lib/golioth-firmware-sdk/snippets/shell_log/shell_log.conf'
Merged configuration '/home/mike/golioth-compile/golioth-firmware-sdk/modules/lib/golioth-firmware-sdk/build/fw_update/zephyr/.config.sysbuild'
Configuration saved to '/home/mike/golioth-compile/golioth-firmware-sdk/modules/lib/golioth-firmware-sdk/build/fw_update/zephyr/.config'
Kconfig header saved to '/home/mike/golioth-compile/golioth-firmware-sdk/modules/lib/golioth-firmware-sdk/build/fw_update/zephyr/include/generated/zephyr/autoconf.h'
</pre>
<h2 id="using-multiple-snippets">Using Multiple Snippets</h2>
<p>You may add as many snippets to your snippet directory as you like.</p>
<pre class="EnlighterJSRAW" data-enlighter-language="generic">snippets
├── dev_sectag
│   ├── dev_sectag.conf
│   └── snippet.yml
└── shell_log
    ├── shell_log.conf
    └── snippet.yml</pre> And then use more than one in your build. 
<pre class="EnlighterJSRAW" data-enlighter-language="generic">west build -p -b frdm_rw612 examples/zephyr/hello -S shell-logs -S dev-sectag</pre>
<p><a href="https://github.com/zephyrproject-rtos/zephyr/tree/main/snippets">Zephyr even has some snippets built-in</a>. Many of them are tightly coupled to vendor-specific configurations. But some are quite useful, like using <code>-S rtt-console</code> to turn on console output when using <a href="https://blog.golioth.io/use-rtt-with-zephyr-when-console-output-via-uart-just-isnt-convenient/">Segger RTT</a>. What do you have planned for your own snippets? We’d love to hear about it, <a href="https://forum.golioth.io/">post your details in the Golioth forum</a>!</p>
]]></content:encoded></item><item><title>'Now on NXP’s Application Code Hub: Golioth Connectivity via the FRDM-MCXW71'</title><link>https://blog.golioth.io/now-on-nxps-application-code-hub-golioth-connectivity-via-the-frdm-mcxw71/</link><guid>https://blog.golioth.io/now-on-nxps-application-code-hub-golioth-connectivity-via-the-frdm-mcxw71/</guid><pubDate>Tue, 25 Nov 2025 16:14:05 +0000</pubDate><dc:creator>Chris Gammell</dc:creator><category>Bluetooth</category><category>Partners</category><description>Golioth’s Bluetooth example using Pouch on the FRDM-MCXW71 and the FRDM-RW612 is now available on [NXP’s Application Code Hub (ACH)][1]. The demo makes it easy to show how a Bluetooth device can connect to the cloud through a standard gateway implementation. ACH makes it easy for NXP developers to boot up examples via VS Code.</description><content:encoded><![CDATA[<p>Golioth’s Bluetooth example using Pouch on the FRDM-MCXW71 and the FRDM-RW612 is now available on [NXP’s Application Code Hub (ACH)][1]. The demo makes it easy to show how a Bluetooth device can connect to the cloud through a standard gateway implementation. ACH makes it easy for NXP developers to boot up examples via VS Code.</p>
<h2 id="the-way-things-were">The way things were</h2>
<p>Getting started in any new ecosystem can be tough. Vendor tooling like ACH gives users a way to quickly find and try out an application and find all the required dependencies. Pairing the ACH with
[NXP’s VS Code plugin][2] can help ease the complexity of the Zephyr toolchain, which can be useful for engineers transitioning to a new ecosystem. My earliest memories of interacting with an IDE includes the confusing setup of an Eclipse environment and then using the built-in tooling to pull down a set of examples that are able to run on target hardware. To its credit, when it worked, this ecosystem approach made it simpler to have all the required libraries and build systems to start with an example; often it would not work or I’d be mired in the setup doldrums for days on end.</p>
<h2 id="how-the-demo-works">How the demo works</h2>
<p>[The MCX-W71 is a Bluetooth and 802.15.4 chipset][3] meant for low power applications in industrial and smart home applications normally powered via Matter. For this demo, we are accessing this chip via the FRDM-MCXW71, their development platform. We were immediately interested in this chipset for the integrated Cortex-M33 and variety of other powerful NXP peripherals and the affordable pricetag. The chip is running a slightly modified version of the Pouch BLE GATT demo, which wraps up data in end-to-end encryption and also makes it possible to send data to the Cloud via a Pouch Gateway. The FRDM-MCXW71 in this demo also has a “Weather Click” plugged into it, which is a BME280 sensor on a MikroBus compatible breakout board. The Pouch Gateway for this demo is a precompiled binary that goes onto the FRDM-RW612, a chipset that has Ethernet, Wi-Fi, Bluetooth, and 802.15.4 connectivity options. It’s targeted at Matter Border Router applications, but also works great as a Pouch Gateway (in fact, it’s one of our standard recommended options). The FRDM-MCXW71 wakes up on a thread every second and takes a reading from the BME280 and puts the reading onto the ZBus. Every 20 seconds, the Bluetooth will advertise that it has a reading and once it connects to the gateway, it will grab the latest reading off of ZBus and publish that using Golioth Stream over Pouch. The Pouch is encrypted and sent up to the cloud via the gateway with no application-side hassles.</p>
<pre class="EnlighterJSRAW" data-enlighter-language="generic">static void pouch_event_handler(enum pouch_event event, void *ctx)
{
    if (POUCH_EVENT_SESSION_START == event)
    {
        char buf[52];
        get_latest_weather(buf,sizeof(buf));
        LOG_DBG("Returned JSON: %s",buf);
        if (POUCH_EVENT_SESSION_START == event)
        {
            pouch_uplink_entry_write(".s/sensor",
                POUCH_CONTENT_TYPE_JSON,
                buf,
                strlen(buf),
                K_FOREVER);
            )
            golioth_sync_to_cloud();
        }
    }
</pre> Much like how Golioth Stream writes to the Cloud in our Firmware SDK, the Pouch uplink simply requires that you pass in data you’d like to transmit up to the cloud. Once the data gets to the Cloud, it is routed through Golioth Pipelines to an end destination such as our timeseries product LightDB Stream. This is a database where you can view the output on the Console and also query that data using Golioth’s REST API. 
<h2 id="hear-more-about-the-setup">Hear more about the setup</h2>
<p>Golioth joined Bridgette and Kyle from NXP to talk through the demo and how Bluetooth devices can talk to the Cloud on NXP’s podcast, the Edgeverse Tech Cast. <div style="position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden;">
      <iframe allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share; fullscreen" loading="eager" referrerpolicy="strict-origin-when-cross-origin" src="https://www.youtube.com/embed/sn4P4uYkjgU?autoplay=0&amp;controls=1&amp;end=0&amp;loop=0&amp;mute=0&amp;start=0" style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; border:0;" title="YouTube video"></iframe>
    </div>

[1]: <a href="https://mcuxpresso.nxp.com/appcodehub">https://mcuxpresso.nxp.com/appcodehub</a>
[2]: <a href="https://www.nxp.com/design/design-center/software/eiq-ai-development-environment/application-code-hub:APP-CODE-HUB">https://www.nxp.com/design/design-center/software/eiq-ai-development-environment/application-code-hub:APP-CODE-HUB</a>
[3]: <a href="https://www.nxp.com/products/MCX-W71">https://www.nxp.com/products/MCX-W71</a></p>
]]></content:encoded></item><item><title>Curl Tricks for the Golioth REST API</title><link>https://blog.golioth.io/?p=10314/</link><guid>https://blog.golioth.io/?p=10314/</guid><pubDate>Tue, 18 Nov 2025 15:07:24 +0000</pubDate><dc:creator>Mike Szczys</dc:creator><category>Device Management</category><description>When people think of IoT fleets, they usually think of devices. But your hardware in the field is only one part of making the Internet of Things work. One of the other huge lifts when standing up a reliable fleet is device management. Golioth has already built that part, and not only is it ready for you to use, it’s ready for you to use in any way you need. All of our device management features are available in a web interface, but also via our extensive REST API. Today let’s look at using the REST API via the curl command. This is useful for adding device management to your continuous integration (CI) and also handy during development. Specifically, we’ll cover OTA packages and deployments but the approach applies to all device management activities.</description><content:encoded><![CDATA[<p>When people think of IoT fleets, they usually think of devices. But your hardware in the field is only one part of making the Internet of Things work. One of the other huge lifts when standing up a reliable fleet is device management. Golioth has already built that part, and not only is it ready for you to use, it’s ready for you to use in any way you need. All of our device management features are available in a web interface, but also via our extensive REST API. Today let’s look at using the REST API via the curl command. This is useful for adding device management to your continuous integration (CI) and also handy during development. Specifically, we’ll cover OTA packages and deployments but the approach applies to all device management activities.</p>
<h2 id="how-to-discover-the-golioth-rest-api-first-and-foremost-everything-you-can-do-in-the">How to Discover the Golioth REST API First and foremost, everything you can do in the</h2>
<p><a href="https://console.golioth.io">Golioth web console</a> is available to do via the REST API. A great place to start learning is <a href="https://docs.golioth.io/reference/management-api/">the Management API docs</a>. These include <a href="https://docs.golioth.io/reference/management-api/openapi">an OpenAPI section</a> where you can interact with the api from your browser. This page will generate and display example curl commands which I used to build the examples in this post. Another useful approach is to use the developer console in your web browser. By loading the network tab of developer tools you can view the REST API calls being made when navigating the web console. Select the XHR tab and perform the action in the console that you want to recreate with your own REST API call. <a href="https://blog.golioth.io/wp-content/uploads/2025/11/web-console-rest-api.png"><img src="https://blog.golioth.io/wp-content/uploads/2025/11/web-console-rest-api-1030x263.png" alt="Firefox developer tools showing REST API commands"></a>The image above shows the Firefox developer console inspecting the process of creating a new cohort using the web console. The body of the request may be viewed by clicking the Request tab. Let’s dive in and use curl to upload and deploy a new OTA artifact.</p>
<h2 id="uploading-an-ota-artifact-with-curl-golioth-over-the-air-ota-updates-are">Uploading an OTA artifact with Curl Golioth Over-the-Air (OTA) updates are</h2>
<p><a href="https://blog.golioth.io/golioth-upgrades-ota/">managed using cohorts, deployments, and packages</a>.</p>
<ul>
<li>A <code>cohort</code> is a group of devices</li>
<li>A <code>deployment</code> is a set of update packages available to a given cohort</li>
<li>A <code>package</code> is a distinct binary file available in a deployment It may be useful to upload a new package version as part of your CI workflow, or just from your build directory as you develop. Assuming you already have a Package group, upload an artifact (a new version of the package) using this curl command:</li>
</ul>
<pre class="EnlighterJSRAW" data-enlighter-language="generic">curl -X 'POST' \
      'https://api.golioth.io/v1/artifacts' \
      -H 'accept: application/json' \
      -H 'x-api-key: 8huiDpFplrcixyC5UwYpd9nQyPpTGJmW' \
      -H 'Content-Type: application/json' \
      -d '{
  "projectId": "your-project-id",
  "content": "'$(base64 test_artifact.bin)'",
  "version": "3.4.5",
  "package": "main"
}'</pre> The magic in this command is the value of the 
<p><code>content</code> member of the JSON body. We use the Linux <code>base64</code> command to encode the binary data as… you guessed it… base64 to send via curl. You will need to supply your own <code>projectId</code>, an <code>x-api-key</code>, and the name of your package. All of these maybe found/created in the Golioth web console. <a href="https://blog.golioth.io/wp-content/uploads/2025/11/main-package-3.4.5.png"><img src="https://blog.golioth.io/wp-content/uploads/2025/11/main-package-3.4.5.png" alt="Golioth web console showing version 3.4.5 of main package"></a>After running the command, verify by viewing in the web console.</p>
<h2 id="creating-deployments-with-curl-in-the-previous-step-we-uploaded-the-ota-binary-but-to-get-it-to-your-device-fleet-we-need-to-create-a-deployment-the-response-to-the-curl-command-that-uploaded-the-binary-includes-a-key-piece-of-information-you-need-to-retain-the-artifact-id">Creating Deployments with Curl In the previous step we uploaded the OTA binary, but to get it to your device fleet we need to create a deployment. The response to the curl command that uploaded the binary includes a key piece of information you need to retain: the artifact ID.</h2>
<pre class="EnlighterJSRAW" data-enlighter-language="generic">{
  "data": {
    "id": "691b4cbfbb9860973efc2d0d",
    "version": "3.4.5",
    "package": "main",
    "createdAt": "2025-11-17T16:26:39.167Z",
    "updatedAt": "2025-11-17T16:26:39.167Z",
    "binaryInfo": {
      "digests": {
        "sha256": {
          "digest": "5525d41439400d0f7d066551f8e4668189d80d87d14940f15b18dcc36680b13c",
          "size": 32,
          "type": "sha256"
        }
      },
      "size": 21,
      "type": "default"
    },
    "size": "21"
  }
}</pre> This artifact was assigned an id by the Golioth server. We need this unique ID to add the binary to our deployment. Let’s use this ID to create a new deployment. 
<pre class="EnlighterJSRAW" data-enlighter-language="generic">curl -X 'POST' \
  'https://api.golioth.io/v1/organizations/your-org-id/projects/your-project-id/cohorts/my_test_cohort/deployments' \
  -H 'accept: application/json' \
  -H 'x-api-key: 8huiDpFplrcixyC5UwYpd9nQyPpTGJmW' \
  -H 'Content-Type: application/json' \
  -d '{
  "name": "my-fw-update-'$(date +"%Y%m%d%H%M")'",
  "artifactIds": [
    "691b4cbfbb9860973efc2d0d"
  ]
}'</pre> There are a number of things going on in this curl command. First, the URL path for the POST command includes three pieces of information you need to customize: 
<ul>
<li><code>your-org-id</code>: click the gear icon next to your organization name in the top bar of the web console to locate your Organization ID</li>
<li><code>your-project-id</code>: find it by clicking settings in the left sidebar of the web console</li>
<li><code>my_test_cohort</code>: the name of your cohort, which you’ll find in the Cohorts entry on the left sidebar of the web console You, of course, need to supply the correct</li>
</ul>
<p><code>x-api-key</code> for your project. The JSON payload of this command requires a name for the deployment. You decided the name up, here I’ve included a human-readable timestamp but the web console will also indicate the time at which this deployment was created. In this case I’m deploying a single artifact, but you may specify multiple artifacts for a deployment. <a href="https://blog.golioth.io/wp-content/uploads/2025/11/golioth-test-deployment.png"><img src="https://blog.golioth.io/wp-content/uploads/2025/11/golioth-test-deployment.png" alt="OTA deployment shown in the Golioth web console"></a>We can verify the deployment was sent to our desired cohort by viewing it in the web console.</p>
<h2 id="bonus-downloading-an-ota-artifact-using-curl-while-artifact-download-is-meant-for-your-ota-fleet-you-can-leverage-curl-to-download-any-package-from-a-project-by-using-a-set-of-device-credentials-first-acquire-a-psk-idpsk-by-creating-a-new-device-on-the-golioth-web-console-or-using-an-existing-one-and-clicking-on-the-credentials-tab">Bonus: Downloading an OTA artifact using Curl While artifact download is meant for your OTA fleet, you can leverage curl to download any package from a project by using a set of device credentials. First, acquire a PSK-ID/PSK by creating a new device on the Golioth web console (or using an existing one) and clicking on the credentials tab.</h2>
<p><a href="https://blog.golioth.io/wp-content/uploads/2025/11/golioth-web-console-psk.png"><img src="https://blog.golioth.io/wp-content/uploads/2025/11/golioth-web-console-psk-1030x259.png" alt="Device credentials (PSK-ID and PSK) shown on the Golioth Web Console"></a>With our credentials in hand we can retrieve a binary file using the package name and version number.</p>
<pre class="EnlighterJSRAW" data-enlighter-language="generic">curl 'https://gw.golioth.io/.u/c/main@3.4.5' -u 20251117171208-amaranth-watery-landfowl@your-project-id -o my_package.bin
</pre> After hitting enter, curl will ask for your host password, which is the PSK from the device credentials. Note that the package name and version number are built into the URL for this request. We can examine the output of the downloaded binary using a hex editor. 
<p><a href="https://blog.golioth.io/wp-content/uploads/2025/11/ota-artifact-hex-view.png"><img src="https://blog.golioth.io/wp-content/uploads/2025/11/ota-artifact-hex-view.png" alt="Hexdump of a binary file. The ASCII representation to the right side reads: &ldquo;this is my test file.&rdquo;"></a>The astute reader may have already noticed that I uploaded a binary file of only 21 bytes. It is worth noting that Golioth treats your artifacts as binary data. While it’s easy to think of OTA as a firmware delivery mechanism, with Golioth deployments you deliver any files you like, such as machine learning models, audio/video assets, and firmware too.</p>
<h2 id="post-your-rest-api-questions-wed-love-to-hear-how-youre-using-the-golioth-rest-api-whether-its-sharing-your-success-or-asking-about-how-to-get-unstuck-please-post-your-thoughts">Post Your REST API Questions We’d love to hear how you’re using the Golioth REST API. Whether it’s sharing your success, or asking about how to get unstuck, please post your thoughts</h2>
<p><a href="https://forum.golioth.io/">on the Golioth forum</a>!</p>
]]></content:encoded></item><item><title>Building Zephyr for the Raspberry Pi Pico2 W</title><link>https://blog.golioth.io/building-zephyr-for-the-raspberry-pi-pico2-w/</link><guid>https://blog.golioth.io/building-zephyr-for-the-raspberry-pi-pico2-w/</guid><pubDate>Tue, 11 Nov 2025 15:04:08 +0000</pubDate><dc:creator>Mike Szczys</dc:creator><category>Engineering</category><category>How To</category><description>The Raspberry Pi Pico2 W is the one with a microcontroller and Wi-Fi. When most people think of Raspberry Pi as a single-board computer running Linux, this model is a microcontroller with a slew of impressive peripherals. While the original Pico (based on the RP2040) has had Zephyr support for a long time, the Pico2 is build on an RP2350 and the W variant delivers Wi-Fi support which lands in Zephyr with the upcoming release of v4.3.0. We love taking new IoT hardware for a spin on Golioth and couldn’t resist adding the Pico2 W to our list of “works with Golioth” boards. In fact, we like this one so much we’re planning to add it to our hardware-in-the-loop (HIL) rigs to test on the hardware with each PR and push to main.</description><content:encoded><![CDATA[<p>The <a href="https://www.raspberrypi.com/products/raspberry-pi-pico-2/">Raspberry Pi Pico2 W</a> is the one with a microcontroller and Wi-Fi. When most people think of Raspberry Pi as a single-board computer running Linux, this model is a microcontroller with a slew of impressive peripherals. While the original Pico (based on the RP2040) has had Zephyr support for a long time, the Pico2 is build on an RP2350 and the W variant delivers Wi-Fi support which lands in Zephyr with the upcoming release of v4.3.0. We love taking new IoT hardware for a spin on Golioth and couldn’t resist adding the Pico2 W to our list of “works with Golioth” boards. In fact, we like this one so much we’re planning to add it to our hardware-in-the-loop (HIL) rigs to test on the hardware with each PR and push to main.</p>
<h2 id="state-of-pico2-w-support-in-zephyr">State of Pico2 W Support in Zephyr</h2>
<p>The RP2350 has had support in Zephyr for some time now, but the option of using the Wi-Fi–available thanks to the Infineon chip on the Pico2 W board–is brand new. For now, you’ll need to build on v4.3.0-rc2 to use the Wi-Fi. Also of note is that the last stable release of OpenOCD (
<a href="https://github.com/openocd-org/openocd/releases/tag/v0.12.0">0.12.0 release January 2023</a>) does not support the RP2350. Since this is the version included in the Zephyr skd-ng, you’ll need to build OpenOCD from source (or find a newer build) in order to flash/debug the Pico2 W. I found that building OpenOCD was pretty straight-forward. Follow <a href="https://github.com/openocd-org/openocd">the compilation instructions</a> on the README by running the following commands:</p>
<pre class="EnlighterJSRAW" data-enlighter-language="generic">./bootstrap
./configure
make
sudo make install</pre> I didn’t actually run the install command, instead pointing Zephyr’s 
<p><code>west</code> tool to the binary and the scripts directory. More on that later. While there is <a href="https://www.raspberrypi.com/news/develop-embedded-firmware-for-pico-using-rust-or-zephyr-with-pico-vscode/">a VScode plugin for working with the Pico2 in Zephyr</a>, I have not tried it and am unable to report as to the state of OpenOCD with that approach.</p>
<h2 id="preparing-golioth-for-the-pico2-w-build">Preparing Golioth for the Pico2 W Build</h2>
<p>The latest release of the Golioth Firmware SDK (v0.21.1) supports Zephyr v4.2.1. Find the
<code>west.yml</code> file and update <a href="https://github.com/golioth/golioth-firmware-sdk/blob/9dac4b57332ab66ea4785b679a28836c133e4598/west-zephyr.yml#L4">the Zephyr version</a> to <code>v4.3.0-rc2</code>, then run an update to pull in the new version:</p>
<pre class="EnlighterJSRAW" data-enlighter-language="generic">west update</pre> That Infineon module has some secret sauce that makes the Wi-Fi work. We need to pull in the binary blobs to take advantage of that. We can do this the easy way or the hard way. While the hard way is cooler, let’s start with the easy way: 
<pre class="EnlighterJSRAW" data-enlighter-language="generic">west blobs fetch hal_infineon</pre> That will take a few minutes as all available binary blobs from Infineon are downloaded. However, when bringing this board up I also discovered that the fetch command supports regex! Using this we can filter just for the blobs we need: 
<pre class="EnlighterJSRAW" data-enlighter-language="generic">west blobs fetch hal_infineon --allow-regex='img/whd/resources/(firmware/COMPONENT_43439|clm/COMPONENT_43439/COMPONENT_MURATA-1YN)/*'</pre>
<h2 id="building-and-running-golioth-hello-for-the-pico2-w">Building and Running Golioth Hello for the Pico2 W</h2>
<p>As with most new things, I want to run
<a href="https://github.com/golioth/golioth-firmware-sdk/tree/main/examples/zephyr/hello">hello world</a> as a first test. With much trial and error I boiled the necessary configuration down as follows:</p>
<pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-group="1" data-enlighter-title="boards/rpi_pico2_rp2350a_m33_w.conf">CONFIG_NETWORKING=y
CONFIG_TEST_RANDOM_GENERATOR=y

CONFIG_NET_DHCPV4=y

CONFIG_NET_IPV4=y
CONFIG_NET_IPV6=n

CONFIG_WIFI=y
CONFIG_WIFI_LOG_LEVEL_ERR=y
CONFIG_NET_L2_WIFI_SHELL=y
CONFIG_GOLIOTH_SAMPLE_WIFI=y</pre> Enabling networking, DHCP, and IPv4 is pretty boilerplate for Zephyr. What’s not is turning on the test random number generator. While the RP2340 has a TRNG hardware peripheral (a great improvement over the RP2040 which did not), it is not yet supported in Zephyr. Turning on Wi-Fi is expected. The Golioth sample Wi-Fi handles auto connection for the Golioth sample applications. You may also just use the Wi-Fi shell commands to set your SSID/PSK and connect to an AP if you prefer. For the Golioth samples, the last piece of the puzzle is to add a storage partition. The vast majority of Zephyr boards I’ve worked with enable partitions by default in the board definition but the Pico2 is an outlier in a couple of ways I’ll discuss later. For now, add a board overlay to enable a storage partition. 
<pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-group="2" data-enlighter-title="boards/rpi_pico2_rp2350a_m33_w.overlay">/* Partitioning for the RP2350A-M33 board using 4M flash.
 */
#include <raspberrypi/partitions_4M_sysbuild.dtsi></pre> With this we can build and run the application: 
<pre class="EnlighterJSRAW" data-enlighter-language="generic">west build -p -b rpi_pico2/rp2350a/m33/w examples/zephyr/hello
west flash</pre>
<h3 id="oh-no-it-didnt-work">Oh no, it didn’t work!</h3>
<pre class="EnlighterJSRAW" data-enlighter-language="generic">Can't find target/rp2350.cfg
FATAL ERROR: command exited with status 1: /home/mike/zephyr-sdk-0.17.4/sysroots/x86_64-pokysdk-linux/usr/bin/openocd -s /home/mike/golioth-compile/golioth-firmware-sdk/zephyr/boards/raspberrypi/rpi_pico2/support -s /home/mike/zephyr-sdk-0.17.4/sysroots/x86_64-pokysdk-linux/usr/share/openocd/scripts -f /home/mike/golioth-compile/golioth-firmware-sdk/zephyr/boards/raspberrypi/rpi_pico2/support/openocd.cfg -c 'source [find interface/cmsis-dap.cfg]' -c 'source [find target/rp2350.cfg]' -c 'set_adapter_speed_if_not_set 5000' '-c init' '-c targets' -c 'reset init' -c 'flash write_image erase /home/mike/golioth-compile/golioth-firmware-sdk/modules/lib/golioth-firmware-sdk/build/zephyr/zephyr.hex' -c 'reset run' -c shutdown</pre> As mentioned before, we are missing official support. Let’s build again and tell Zephyr where our custom OpenOCD is located: 
<pre class="EnlighterJSRAW" data-enlighter-language="generic">west build -p -b rpi_pico2/rp2350a/m33/w examples/zephyr/hello -- -DOPENOCD=/home/mike/golioth-compile/openocd/src/openocd -DOPENOCD_DEFAULT_PATH=/home/mike/golioth-compile/openocd/tcl
west flash</pre> This time we’re in business. Now it’s just a matter of 
<p><a href="https://docs.golioth.io/connectivity/credentials/psk">creating some Golioth credentials</a> using <a href="https://console.golioth.io">the web console</a>, opening up a serial terminal, and provisioning the device:</p>
<pre class="EnlighterJSRAW" data-enlighter-language="generic">wifi cred add -k 1 -s your_ssid -p your_wifi_password
settings set golioth/psk-id your-golioth-psk-id
settings set golioth/psk your-golioth-psk
kernel reboot</pre> The three values for the Wi-Fi credentials are set using Zephyr’s built-in settings. The 
<p><code>-k</code> is encryption types, while <code>-s</code> and <code>-p</code> are SSID and PSK. From there, set your Golioth PSK-ID/PSK as normal and reboot the chip.</p>
<h3 id="sample-output">Sample Output</h3>
<p>The entire process of provisioning and connecting to Golioth will look about like this:</p>
<pre class="EnlighterJSRAW" data-enlighter-language="generic">[00:00:03.112,000] <inf> fs_nvs: 8 Sectors of 4096 bytes
[00:00:03.112,000] <inf> fs_nvs: alloc wra: 0, fe8
[00:00:03.112,000] <inf> fs_nvs: data wra: 0, 0
*** Booting Zephyr OS build v4.3.0-rc1 ***
*** Golioth Firmware SDK v0.21.1-24-g5d0365be5a81 ***
[00:00:03.113,000] <wrn> net_sock_tls: No entropy device on the system, TLS communication is insecure!
[00:00:03.113,000] <inf> golioth_settings_autoload: Initializing settings subsystem
[00:00:03.113,000] <inf> golioth_settings_autoload: Loading settings
[00:00:03.113,000] <dbg> hello_zephyr: main: start hello sample
[00:00:03.113,000] <inf> golioth_samples: Bringing up network interface
[00:00:03.113,000] <inf> golioth_samples: Waiting to obtain IP address
[00:00:03.113,000] <inf> golioth_wifi: Connecting to stored WiFi network
uart:~$ wifi cred add -k 1 -s golioth -p your-wifi-password
uart:~$ settings set golioth/psk-id rpi-pico2w@iot_fleet
Setting golioth/psk-id to rpi-pico2w@iot_fleet
Setting golioth/psk-id saved as rpi-pico2w@iot_fleet
uart:~$ settings set golioth/psk 840e6b5b64f23e0f2cd794051642c0aa
Setting golioth/psk to 840e6b5b64f23e0f2cd794051642c0aa
Setting golioth/psk saved as 840e6b5b64f23e0f2cd794051642c0aa
uart:~$ kernel reboot

[3067] WLAN MAC Address : 2C:CF:67:DF:09:D9

[3070] WLAN Firmware    : wl0: Jun  5 2024 06:33:59 version 7.95.88 (cf1d613 CY) FWID 01-7b7cf51a

[3079] WLAN CLM         : API: 12.2 Data: 9.10.39 Compiler: 1.29.4 ClmImport: 1.36.3 Creation: 2024-04-16 21:20:55

[3088] WHD VERSION      : 3.3.3.26653
[3091]  : WIFI5-v3.3.3
[3093]  : GCC 12.2
[3095]  : 2025-04-14 03:18:50 +0000

[00:00:03.112,000] <inf> fs_nvs: 8 Sectors of 4096 bytes
[00:00:03.112,000] <inf> fs_nvs: alloc wra: 0, fa0
[00:00:03.112,000] <inf> fs_nvs: data wra: 0, 13e
*** Booting Zephyr OS build v4.3.0-rc1 ***
*** Golioth Firmware SDK v0.21.1-24-g5d0365be5a81 ***
[00:00:03.113,000] <wrn> net_sock_tls: No entropy device on the system, TLS communication is insecure!
[00:00:03.113,000] <inf> golioth_settings_autoload: Initializing settings subsystem
[00:00:03.113,000] <inf> golioth_settings_autoload: Loading settings
[00:00:03.113,000] <dbg> hello_zephyr: main: start hello sample
[00:00:03.113,000] <inf> golioth_samples: Bringing up network interface
[00:00:03.113,000] <inf> golioth_samples: Waiting to obtain IP address
[00:00:03.113,000] <inf> golioth_wifi: Connecting to stored WiFi network
Connected
[00:00:06.024,000] <inf> net_wifi_mgmt: Connection requested
[00:00:06.612,000] <inf> net_dhcpv4: Received: 192.168.200.43
[00:00:06.613,000] <inf> golioth_mbox: Mbox created, bufsize: 1320, num_items: 10, item_size: 120
[00:00:06.839,000] <inf> golioth_coap_client_zephyr: Golioth CoAP client connected
[00:00:06.839,000] <inf> hello_zephyr: Sending hello! 0
[00:00:06.839,000] <inf> hello_zephyr: Golioth client connected
[00:00:06.839,000] <inf> golioth_coap_client_zephyr: Entering CoAP I/O loop
[00:00:11.839,000] <inf> hello_zephyr: Sending hello! 1
[00:00:16.839,000] <inf> hello_zephyr: Sending hello! 2
[00:00:21.839,000] <inf> hello_zephyr: Sending hello! 3</pre> And when we look at the device logs on the Golioth cloud we see our hello world messages coming through from the Pico2 W! 
<p><a href="https://blog.golioth.io/wp-content/uploads/2025/11/golioth-logs-pico2w.png"><img src="https://blog.golioth.io/wp-content/uploads/2025/11/golioth-logs-pico2w.png" alt="Golioth web console showing log messages from a device: &ldquo;Sending Hello! 0&rdquo;, etc."></a></p>
<h2 id="zephyr-gotchas-with-the-pico2-w">Zephyr Gotchas with the Pico2 W</h2>
<p>There are a number of gotchas to following along with the post so far. Zephyr supports a number of runners for flashing this board, including the UF2 bootloader. However, I’m using a programmer because I don’t want to push a button (and replug USB) to flash the chip.
<del>That’s okay, because Zephyr doesn’t have USB-CDC support for this board either. This means you can’t get logs from the USB port.</del> I’m using the Raspberry Pi Debug Probe because it takes care of a number of these issues: directly program the chip, use the built-in UART to connect to the chip’s console, and I’m using a hack to power the Pico from the debug probe (do so at your own peril). <strong>Edit:</strong> Thanks to <a href="https://www.linkedin.com/in/cdwilson?lipi=urn%3Ali%3Apage%3Ad_flagship3_detail_base%3BR%2BQEynwJSeSkLMHMyv8L%2Fg%3D%3D">Chris Wilson</a> who <a href="https://www.linkedin.com/feed/update/urn:li:activity:7394042171064111104?commentUrn=urn%3Ali%3Acomment%3A%28activity%3A7394042171064111104%2C7394150838144819200%29&amp;dashCommentUrn=urn%3Ali%3Afsd_comment%3A%287394150838144819200%2Curn%3Ali%3Aactivity%3A7394042171064111104%29">pointed out</a> that USB-CDC worked on the Pico and should also work on the Pico2 w. I went back and tried again and was able to get it working on the <a href="https://docs.zephyrproject.org/latest/samples/subsys/usb/cdc_acm/README.html">usb-cdc sample</a> with <a href="https://gist.github.com/szczys/2d455cf39a7566dd5b4921cf125fadb2">a bit of devicetree work</a>.</p>
<div id='gallery-13' class='gallery galleryid-10292 gallery-columns-2 gallery-size-large'>
  <figure class='gallery-item'> 
  <div class='gallery-icon landscape'>
    <a href='https://blog.golioth.io/building-zephyr-for-the-raspberry-pi-pico2-w/raspberry-pi-pico2w-debug-probe/'>![Raspberry Pi Pico2w connected to a Raspberry Pi Debug Probe with a few wires](https://blog.golioth.io/wp-content/uploads/2025/11/raspberry-pi-pico2w-debug-probe-1030x1030.jpg)</a>
  </div><figcaption class='wp-caption-text gallery-caption' id='gallery-13-10295'> Programming the Pico2w with the Debug Probe </figcaption></figure><figure class='gallery-item'> 
  <div class='gallery-icon landscape'>
    <a href='https://blog.golioth.io/building-zephyr-for-the-raspberry-pi-pico2-w/raspberry-pi-debug-probe-test-points/'>![Underside of Raspberry Pi Debug Probe with a number of copper test points and two wires soldered to them](https://blog.golioth.io/wp-content/uploads/2025/11/raspberry-pi-debug-probe-test-points-1030x1030.jpg)</a>
  </div><figcaption class='wp-caption-text gallery-caption' id='gallery-13-10294'> Tapping into 5v/GND on the Debug Probe test points. </figcaption></figure>
</div> Using this arrangement, I only need to plug USB into the debug probe and I can flash, debug, and monitor serial output. The final gotcha with this board is that the MCUboot support takes a much different approach than I’m used to. I mentioned above that the partition tables are not included in the build by default. Instead of simply using 
<p><code>--sysbuild</code> to indicate the build wants a bootloader, <a href="https://github.com/zephyrproject-rtos/zephyr/blob/main/boards/raspberrypi/rpi_pico2/rpi_pico2_rp2350a_m33_w_mcuboot.dts">you must change the board name itself</a> to use an additional qualifier: <code>rpi_pico2/rp2350a/m33/w/mcuboot</code>. This presents a minor annoyance when I added this to our GitHub CI, but otherwise it’s just a different approach from what I’m accustomed to and still works well.</p>
<h2 id="take-golioth-for-a-spin-on-your-pico2-w">Take Golioth for a Spin on your Pico2 W!</h2>
<p>Official Golioth support for this board is coming soon. We can’t merge to main until Zephyr releases v4.3.0, expected this month. For now,
<a href="https://github.com/golioth/golioth-firmware-sdk/tree/szczys/pico2w">check out my working branch</a> and <a href="https://forum.golioth.io/">let us know how things go</a>!</p>
]]></content:encoded></item><item><title>How to Convince Your Manager to Use Golioth (As an Engineer)</title><link>https://blog.golioth.io/how-to-convince-your-manager-to-use-golioth-as-an-engineer/</link><guid>https://blog.golioth.io/how-to-convince-your-manager-to-use-golioth-as-an-engineer/</guid><pubDate>Thu, 06 Nov 2025 15:11:16 +0000</pubDate><dc:creator>Marko Puric</dc:creator><category>Culture</category><category>Engineering</category><category>Golioth</category><category>IoT Development</category><description>If you’ve ever built an IoT product from the ground up, you know how it starts. The first prototype is always easy: you wire up a few sensors, connect over Wi-Fi or cellular, and push some data to the cloud. It works — until someone says the words that change everything:</description><content:encoded><![CDATA[<p>If you’ve ever built an IoT product from the ground up, you know how it starts. The first prototype is always easy: you wire up a few sensors, connect over Wi-Fi or cellular, and push some data to the cloud. It works — until someone says the words that change everything:</p>
<blockquote>
<p>“Okay, now let’s scale it.” Suddenly, the “fun” part of building connected devices gives way to the <em data-start="427" data-end="441">messy middle:</em> authentication, provisioning, firmware updates, dashboards, logging, alerts, certificates, retries, and reboots. The firmware team focuses on getting data off the device, the cloud team focuses on ingesting and storing it, and the space in between becomes a gray area of responsibility. Integrating those layers takes more coordination than anyone expected, and over time, the team finds itself maintaining infrastructure that doesn’t add new value to the product — it just keeps the lights on. And that’s exactly where Golioth comes in.</p></blockquote>
<h2 id="the-messy-middle-problem">The “Messy Middle” Problem</h2>
<p>Every connected product, no matter how innovative, hits the same wall: getting reliable, secure data from devices into the cloud, and keeping it there at scale.</p>
<p data-start="360" data-end="695">
  Building firmware and product features your users will love, that’s exciting. But building and maintaining the infrastructure that gets data from the edge to the cloud? That’s the <em data-start="541" data-end="555">messy middle</em>. It’s the layer of plumbing between devices and cloud applications that nobody sets out to build, but every IoT product eventually needs.
</p>
<p data-start="697" data-end="1172">
  This is where most teams start losing time and momentum. What begins as a simple proof of concept gradually expands into a complex ecosystem of systems and dependencies that has to stay online, scale, and evolve. Each addition feels necessary at the time, but together they create an infrastructure that needs as much attention as the product itself. Before long, your team isn’t just building a product — you’re running an entire IoT platform.
</p> And the problem isn’t just technical, it’s organizational. Once you own that infrastructure, you’re responsible for it. You need engineers to operate it, DevOps to maintain it, QA to test it, and management to plan for its lifecycle. Those costs compound over time and quietly turn into a second product you never intended to build. This messy middle is what slows teams down, eats into budgets, and shifts focus away from innovation. It’s not what differentiates your product, but it’s what keeps it from moving forward when handled in-house. That’s exactly the layer Golioth abstracts away, so your team can focus on building great devices and meaningful experiences, not maintaining the infrastructure in between. 
<h2 id="focus-on-what-actually-matters">Focus on What Actually Matters</h2>
<p>When you use Golioth, you don’t have to think about:</p>
<ul>
<li>How to securely connect thousands of devices to the cloud</li>
<li>How to push firmware updates over the air</li>
<li>How to log, store, and route data to the right systems You just build your product. By leveraging Golioth’s</li>
</ul>
<p><a href="https://docs.golioth.io/device-management">Device Management</a>, <a href="https://docs.golioth.io/data-routing">Data Routing</a>, and <a href="https://docs.golioth.io/application-services">Application Services</a>, your team can focus on innovation and not infrastructure. You can iterate faster, run real-world tests earlier, and deploy with confidence knowing your foundation is solid. It’s the difference between spending six to twelve months building an IoT backbone versus spending six days integrating one.</p>
<h2 id="the-business-case-for-using-golioth">The Business Case for Using Golioth</h2>
<p>This is often where the conversation with your boss begins or where it gets stuck.</p>
<blockquote>
<p>“Why do we need another platform?” “Can’t we just build it ourselves?” The answer is: sure, you can. But should you? Every hour your team spends managing TLS certificates, maintaining cloud infrastructure, or troubleshooting firmware updates is an hour not spent improving your product’s value. Those tasks are <strong>operational costs, not competitive advantages</strong>. What often gets overlooked in the “build vs. buy” debate is the DevOps burden that comes with building it yourself.  Once you decide to roll your own IoT backend, you’re not just creating infrastructure; you’re committing to owning it. That means servers, monitoring, scaling, uptime, incident response, and ongoing maintenance. Golioth eliminates that DevOps cost and responsibility. We take care of the connectivity layer, device provisioning, scaling, and monitoring, all the operational work that keeps an IoT system running reliably. Your team doesn’t have to worry about uptime, infrastructure tuning, or maintenance; it’s all handled as part of the platform. And as your product matures, you’ll inevitably want to add new features for your customers. That also means new requirements for your infrastructure: new APIs, new scaling needs, and new integrations. If you’ve built your own stack, you’re now maintaining and evolving two products: your core product and the Golioth equivalent platform you’ve built on the side. Golioth turns all of that complexity into a managed service. We handle the operational layer, <strong>device connectivity, data ingestion, routing, and cloud integration</strong>, so your team can stay focused on your users, your data, and your differentiation.</p></blockquote>
<h2 id="engineering-freedom-not-lock-in">Engineering Freedom, Not Lock-In</h2>
<p>Golioth isn’t just an IoT “cloud” — it’s an IoT data company. Our core mission is to help you move data cleanly, securely, and reliably between the physical world (your devices) and your cloud infrastructure. We provide device management capabilities because they’re essential to making that data journey possible. You can register, provision, monitor, and update devices at scale with just a few lines of code. But that’s only part of the story. The real value is in the data itself, the insights your devices generate, and how you use them to shape your product, your operations, and your customer experience. Golioth is the vessel that carries that data from your devices to wherever it needs to go. Whether it’s streaming telemetry to analytics tools, routing events to webhooks, or archiving logs in cloud storage, we make sure your data arrives intact and ready for use, without locking you into a single ecosystem. You own the data. Always. It’s your differentiator, the reason your product stands out in the market. Our job is to make sure nothing gets in the way of you collecting it, moving it, and using it. Route your data to AWS, GCP, InfluxDB, or anywhere else your architecture requires. Golioth gives you the freedom to evolve your stack as your product grows, to adapt, migrate, and scale, without ever losing control of your most valuable asset: your data. We’re here to accelerate your roadmap, not dictate it.</p>
<h2 id="wrapping-it-up">Wrapping It Up</h2>
<p>Convincing your boss isn’t about buzzwords or hype. It’s about showing that engineering time is precious, and that the unsexy, repetitive parts of IoT don’t need to be reinvented for the hundredth time. With Golioth, your team can:</p>
<ul>
<li>Reduce development risk by starting from a proven IoT foundation</li>
<li>Speed up time to market by skipping infrastructure work</li>
<li>Optimize resources by focusing only on what differentiates your product If your goal is to launch within the next year, Golioth gives you the end-to-end infrastructure to get there — securely, efficiently, and without distraction. Let your engineers focus on what they do best: building great products. Let Golioth handle the rest.</li>
</ul>
]]></content:encoded></item><item><title>The So What of IoT</title><link>https://blog.golioth.io/the-so-what-of-iot/</link><guid>https://blog.golioth.io/the-so-what-of-iot/</guid><pubDate>Thu, 06 Nov 2025 15:00:32 +0000</pubDate><dc:creator>Brian Rucker</dc:creator><category>Golioth</category><category>IoT Development</category><description>Lately I’ve been thinking about what IoT really means. We talk a lot about devices, sensors, networks, and connectivity. But to me, those are just supporting actors. The real story is the data. When I think about the “Internet of Things,” I break it down: the Internet is about communication. The Things are physical devices that capture what’s happening in the real world. What makes IoT powerful isn’t just that these things exist. It’s that they let us capture tiny pieces of truth from the real world and move them into the digital one. Each data point is a small piece of reality. Consider what this looks like in practice. Our customer Slabsure captures tiny movements in building foundations. These measurements predict expensive failures before they happen. Another customer, Leosmak monitors 30-40 hour freeze-drying cycles across Europe. They turn those time measurements into useful notifications. Each sensor reading is like a breadcrumb showing the path from the physical world to where it matters. The value comes from moving that truth to where people can understand it and act on it. At Golioth, that’s how I think about our role. For a long time, IoT platforms were mainly device management platforms. That made sense before AI changed everything. Back then, the big challenge was keeping devices healthy and secure. How do I update devices remotely? How do I avoid sending technicians to every location? How do I keep my firmware safe? Those things still matter. But they’re not the end goal anymore. I often think of IoT as a highway system for information. Each connected device is a vehicle carrying truth from the physical world to the digital world and back. What matters most isn’t how many vehicles are on the road. It’s how well and how safely they can travel. Everything else—device management, connectivity, network optimization—is the supporting infrastructure. The asphalt that paves the road. The gas stations that keep things running. The guardrails that keep everyone safe. They’re all needed. But they exist for one reason: to move data smoothly from one place to another. Ribbit Network’s air quality sensors show this perfectly. They call them “Frogs.” Each solar-powered device is a vehicle on this highway. It carries air quality data from backyards and rooftops worldwide. The infrastructure—software updates, security, data pipelines—exists just to keep those vehicles moving reliably. Ribbit didn’t want to build roads. They wanted to measure air quality at scale. That’s the difference. From Old IoT to the Era of Data Mobility I don’t think the industry got IoT wrong. I just think most platforms were built in a different era. Five years ago, data mainly moved from device to cloud. You didn’t need constant, fast, two-way communication. Now AI models run at both the edge and in the cloud. Data needs to move freely in all directions. Constantly, securely, and fast. AL2Tech learned this working with Hubwater on smart water dispensers across Europe. They first considered AWS. But they realized they needed more than cloud storage. They needed data mobility. Water use data flows from card readers to CO2 bottle sensors, through the platform, and out to different people. Gym members track their usage. Facility managers monitor refills. Sustainability teams measure plastic waste reduction. The data doesn’t just move up to the cloud anymore. It flows through a whole ecosystem. That kind of flow doesn’t happen by accident. It required big changes to how we built our platform. In May 2024, we launched Pipelines. This completely changed how data moves through our infrastructure. Unlike the old pattern of device-to-cloud-to-storage, Pipelines let data flow freely in any direction. Shortly after, we added a feature letting devices send large files—like AI models or high-quality images—through the same connection they use for everything else. Suddenly, small devices running on battery power could work with advanced AI. Flox’s wildlife cameras capture images in remote forests. They process them right on the device. They play sounds to keep animals safe. Then they send training data back to improve their AI models. All this works because the data highway now runs both ways. This means IoT isn’t just a stack of technology anymore. It’s a living network. The devices at the physical edge, the AI models at the digital edge, and everything in between are connected by data highways. These highways are constantly being built, torn down, and rebuilt as new connections form. That’s where I see Golioth’s place: we’re not the destination. We’re the infrastructure that makes those connections possible. IoT as a Symphony, Not a Stack The most exciting thing about IoT is the whole ecosystem. It’s not about one company doing everything. It’s about how many different pieces work together. In Flox’s example, they run inference on devices, then send training data from the device, to cloud, and back again—all while running on battery power in areas with weak internet. The symphony idea became real for us in 2024 when we added Bluetooth support. Bluetooth devices now “roam” between gateways smoothly. Same tools, same dashboards, same software updates whether you’re on cellular, WiFi, Thread, or Bluetooth. One customer put sensors across a campus. Devices switch between gateways as people move through buildings. The devices don’t care which gateway they’re using. The music metaphorically keeps playing. Each layer of IoT is one piece of a larger puzzle. But when you zoom out, you see the full picture. IoT is a symphony of hardware, cloud, and intelligence all working together. Golioth’s role in that symphony is to keep the music playing. To make sure data moves where it needs to go, when it needs to get there. The Production Reality What stands out across these examples isn’t just the variety. It’s how quickly teams go from prototype to production when the infrastructure becomes invisible. Take Leosmak. They became Europe’s #1 freeze dryer maker with just one firmware engineer managing their entire IoT setup. They saved millions in development costs. Not by building less, but by building only what made their product different. The remote monitoring, software updates, and analytics that customers wanted? That innovation was a result of invisible infrastructure. We’ve done a lot of work to make this invisible infrastructure a reality. We unified our code base. No more maintaining separate versions for different platforms. We switched to usage-based pricing. Device management became basically free for small devices. We added location services using network-based positioning, instead of forcing devices to wait for GPS, to extend battery life and reduce hardware costs. Each of these changes had the same goal: make the infrastructure disappear faster. Fast forward to this year and we’ve released a browser-based Certificate Generator. Generating and signing certificates used to be cumbersome enough that teams would stick with less-secure PSK authentication rather than deal with it. Now you can create certificates in your browser in minutes. Security is built-in from day one. When the secure path is also the easy path, security stops being a choice and becomes infrastructure. We’re excited about this evolution and there’s so much more to come. Everyone Can Access Data For a long time, owning data was seen as the competitive advantage. But that’s changing. We’re entering an era where everyone can create their own unique data sources. Not just consume or resell someone else’s data. This shift is exciting. It means the value is moving away from owning data. It’s moving toward understanding it—toward what you do with it. Instead of a few companies competing over the same data, we’ll see a world of richer, more diverse data sources. The quality of insight becomes what matters. Whether it’s Slabsure predicting foundation failures from pressure sensors, or Ribbit Network’s citizen scientists building a distributed air monitoring network, the competitive advantage isn’t owning special data sources. It’s what you do with the truth you’ve captured. But here’s the thing: what makes this possible isn’t just access to sensors. It’s access to the infrastructure that makes sensor data useful. When Ovyl needed to test a Thread mesh network, they got devices connected and streaming data in days instead of weeks. They didn’t have to build authentication, storage, and data display from scratch. When AL2Tech added CO2 sensors to Hubwater’s dispensers for a new sparkling water service, they pushed the feature through a software update. No hardware recalls. No sending technicians to every location. The infrastructure that once took months to build now takes a few lines of code. That’s a better, healthier way to innovate. And that’s the “So What” of IoT. What it really means: it’s not about the devices or even the networks. It’s about creating a world where data can flow freely between the physical and digital worlds. This leads to better decisions, better products, and ultimately, a better understanding of the world around us.</description><content:encoded><![CDATA[<p><span style="font-weight: 300;">Lately I’ve been thinking about what IoT really means. We talk a lot about devices, sensors, networks, and connectivity. But to me, those are just supporting actors. The real story is the data.</span> <span style="font-weight: 300;">When I think about the “Internet of Things,” I break it down: the Internet is about communication. The Things are physical devices that capture what’s happening in the real world. What makes IoT powerful isn’t just that these things exist. It’s that they let us capture tiny pieces of truth from the real world and move them into the digital one. Each data point is a small piece of reality.</span> <span style="font-weight: 300;">Consider what this looks like in practice. Our customer </span><a href="https://golioth.io/case-studies/building-better-structures-with-slabsure"><span style="font-weight: 300;">Slabsure</span></a> <span style="font-weight: 300;">captures tiny movements in building foundations. These measurements predict expensive failures before they happen. Another customer, </span><a href="https://golioth.io/case-studies/leosmak-builds-europes-number-one-freeze-dryer-with-golioth"><span style="font-weight: 300;">Leosmak</span></a> <span style="font-weight: 300;">monitors 30-40 hour freeze-drying cycles across Europe. They turn those time measurements into useful notifications. Each sensor reading is like a breadcrumb showing the path from the physical world to where it matters. The value comes from moving that truth to where people can understand it and act on it.</span> <span style="font-weight: 300;">At Golioth, that’s how I think about our role. For a long time, IoT platforms were mainly device management platforms. That made sense before AI changed everything. Back then, the big challenge was keeping devices healthy and secure. How do I </span><a href="https://docs.golioth.io/device-management/ota/"><span style="font-weight: 300;">update devices remotely</span></a><span style="font-weight: 300;">? How do I avoid sending technicians to every location? How do I </span><a href="https://docs.golioth.io/connectivity/credentials/"><span style="font-weight: 300;">keep my firmware</span></a> <span style="font-weight: 300;">safe? Those things still matter. But they’re not the end goal anymore.</span> <span style="font-weight: 300;">I often think of IoT as a highway system for information. Each connected device is a vehicle carrying truth from the physical world to the digital world and back. What matters most isn’t how many vehicles are on the road. It’s how well and how safely they can travel. Everything else—</span><a href="https://docs.golioth.io/device-management"><span style="font-weight: 300;">device management</span></a><span style="font-weight: 300;">, </span><a href="https://docs.golioth.io/connectivity"><span style="font-weight: 300;">connectivity</span></a><span style="font-weight: 300;">, </span><a href="https://blog.golioth.io/the-taxonomy-of-connected-device-networks/"><span style="font-weight: 300;">network optimization</span></a><span style="font-weight: 300;">—is the supporting infrastructure. The asphalt that paves the road. The gas stations that keep things running. The guardrails that keep everyone safe. They’re all needed. But they exist for one reason: to move data smoothly from one place to another.</span> <a href="https://golioth.io/case-studies/ribbit-networks-automated-fleet-of-distributed-frogs-runs-on-golioth"><span style="font-weight: 300;">Ribbit Network’s air quality sensors</span></a> <span style="font-weight: 300;">show this perfectly. They call them “Frogs.” Each solar-powered device is a vehicle on this highway. It carries air quality data from backyards and rooftops worldwide. The infrastructure—software updates, security, data pipelines—exists just to keep those vehicles moving reliably. Ribbit didn’t want to build roads. They wanted to measure air quality at scale. That’s the difference.</span> <span style="font-weight: 400;">From Old IoT to the Era of Data Mobility</span> <span style="font-weight: 300;">I don’t think the industry got IoT wrong. I just think most platforms were built in a different era. Five years ago, data mainly moved from device to cloud. You didn’t need constant, fast, two-way communication. Now AI models run at both the edge and in the cloud. Data needs to move freely in all directions. Constantly, securely, and fast.</span> <a href="https://golioth.io/case-studies/how-al2tech-helps-hubwater-reduce-water-bottle-waste-across-europe"><span style="font-weight: 300;">AL2Tech</span></a> <span style="font-weight: 300;">learned this working with Hubwater on smart water dispensers across Europe. They first considered AWS. But they realized they needed more than cloud storage. They needed data mobility. Water use data flows from card readers to CO2 bottle sensors, through the platform, and out to different people. Gym members track their usage. Facility managers monitor refills. Sustainability teams measure plastic waste reduction. The data doesn’t just move up to the cloud anymore. It flows through a whole ecosystem.</span> <span style="font-weight: 300;">That kind of flow doesn’t happen by accident. It required big changes to how we built our platform. In May 2024, we </span><a href="https://blog.golioth.io/announcing-pipelines-stream-transform-and-route-iot-data-anywhere/"><span style="font-weight: 300;">launched Pipelines</span></a><span style="font-weight: 300;">. This completely changed how data moves through our infrastructure. Unlike the old pattern of device-to-cloud-to-storage, Pipelines let data flow freely in any direction. Shortly after, we added a feature letting devices send large files—like </span><a href="https://blog.golioth.io/golioth-for-ai/"><span style="font-weight: 300;">AI models</span></a> <span style="font-weight: 300;">or high-quality images—through the same connection they use for everything else. Suddenly, small devices running on battery power could work with advanced AI. </span><a href="https://golioth.io/case-studies/how-flox-robotics-is-keeping-animals-from-harm-using-ai-and-iot"><span style="font-weight: 300;">Flox’s wildlife cameras</span></a> <span style="font-weight: 300;">capture images in remote forests. They process them right on the device. They play sounds to keep animals safe. Then they send training data back to improve their AI models. All this works because the data highway now runs both ways.</span> <span style="font-weight: 300;">This means IoT isn’t just a stack of technology anymore. It’s a living network. The devices at the physical edge, the AI models at the digital edge, and everything in between are connected by data highways. These highways are constantly being built, torn down, and rebuilt as new connections form. That’s where I see Golioth’s place: we’re not the destination. We’re the infrastructure that makes those connections possible.</span> <span style="font-weight: 400;">IoT as a Symphony, Not a Stack</span> <span style="font-weight: 300;">The most exciting thing about IoT is the whole ecosystem. It’s not about one company doing everything. It’s about how many different pieces work together. In </span><a href="https://golioth.io/case-studies/how-flox-robotics-is-keeping-animals-from-harm-using-ai-and-iot"><span style="font-weight: 300;">Flox’s</span></a> <span style="font-weight: 300;">example, they run inference on devices, then send training data from the device, to cloud, and back again—all while running on battery power in areas with weak internet. </span> <span style="font-weight: 300;">The symphony idea became real for us in 2024 when we added Bluetooth support. Bluetooth devices now “roam” between gateways smoothly. Same tools, same dashboards, same software updates whether you’re on cellular, WiFi, Thread, or Bluetooth. One customer put sensors across a campus. Devices switch between gateways as people move through buildings. The devices don’t care which gateway they’re using. The music metaphorically keeps playing.</span> <span style="font-weight: 300;">Each layer of IoT is one piece of a larger puzzle. But when you zoom out, you see the full picture. IoT is a symphony of hardware, cloud, and intelligence all working together. Golioth’s role in that symphony is to keep the music playing. To make sure data moves where it needs to go, when it needs to get there.</span> <span style="font-weight: 400;">The Production Reality</span> <span style="font-weight: 300;">What stands out across these examples isn’t just the variety. It’s how quickly teams go from prototype to production when the infrastructure becomes invisible. Take Leosmak. They became Europe’s #1 freeze dryer maker with just one firmware engineer managing their entire IoT setup. They saved millions in development costs. Not by building less, but by building only what made their product different. The remote monitoring, software updates, and analytics that customers wanted? That innovation was a result of invisible infrastructure.</span> <span style="font-weight: 300;">We’ve done a lot of work to make this invisible infrastructure a reality. We unified our code base. No more maintaining separate versions for different platforms. We switched to usage-based pricing. </span><a href="https://blog.golioth.io/device-management-should-be-free/"><span style="font-weight: 300;">Device management became basically free</span></a> <span style="font-weight: 300;">for small devices. We added </span><a href="https://blog.golioth.io/golioth-location-ga/"><span style="font-weight: 300;">location services</span></a> <span style="font-weight: 300;">using network-based positioning, instead of forcing devices to wait for GPS, to extend battery life and reduce hardware costs. Each of these changes had the same goal: make the infrastructure disappear faster.</span> <span style="font-weight: 300;">Fast forward to this year and we’ve released a </span><a href="https://blog.golioth.io/new-console-feature-the-certificate-generator/"><span style="font-weight: 300;">browser-based Certificate Generator</span></a><span style="font-weight: 300;">. Generating and signing certificates used to be cumbersome enough that teams would stick with less-secure PSK authentication rather than deal with it. Now you can create certificates in your browser in minutes. Security is built-in from day one. When the secure path is also the easy path, security stops being a choice and becomes infrastructure. We’re excited about this evolution and there’s so much more to come. </span> <span style="font-weight: 400;">Everyone Can Access Data</span> <span style="font-weight: 300;">For a long time, owning data was seen as the competitive advantage. But that’s changing. We’re entering an era where everyone can create their own unique data sources. Not just consume or resell someone else’s data.</span> <span style="font-weight: 300;">This shift is exciting. It means the value is moving away from owning data. It’s moving toward understanding it—toward what you do with it. Instead of a few companies competing over the same data, we’ll see a world of richer, more diverse data sources. The quality of insight becomes what matters. Whether it’s Slabsure predicting foundation failures from pressure sensors, or Ribbit Network’s citizen scientists building a distributed air monitoring network, the competitive advantage isn’t owning special data sources. It’s what you do with the truth you’ve captured.</span> <span style="font-weight: 300;">But here’s the thing: what makes this possible isn’t just access to sensors. It’s access to the infrastructure that makes sensor data useful.</span> <span style="font-weight: 300;">When Ovyl needed to test a Thread mesh network, they got devices connected and streaming data in days instead of weeks. They didn’t have to build authentication, storage, and data display from scratch. When AL2Tech added CO2 sensors to Hubwater’s dispensers for a new sparkling water service, they pushed the feature through a software update. No hardware recalls. No sending technicians to every location. The infrastructure that once took months to build now takes a few lines of code.</span> <span style="font-weight: 300;">That’s a better, healthier way to innovate.</span> <span style="font-weight: 300;">And that’s the “So What” of IoT. What it really means: it’s not about the devices or even the networks. It’s about creating a world where data can flow freely between the physical and digital worlds. This leads to better decisions, better products, and ultimately, a better understanding of the world around us.</span></p>
]]></content:encoded></item><item><title>Signed URLs on ESP32</title><link>https://blog.golioth.io/signed-urls-on-esp32/</link><guid>https://blog.golioth.io/signed-urls-on-esp32/</guid><pubDate>Tue, 04 Nov 2025 14:59:06 +0000</pubDate><dc:creator>Dan Mangum</dc:creator><category>Data Management</category><category>Device Management</category><category>Platform</category><category>Security</category><description>A few weeks ago we announced support for signed URLs on embedded devices and the initial release of signy. This week we released v0.2.0 of signy, which expands support beyond Zephyr RTOS to Espressif’s ESP-IDF. In addition to bringing signed URLs to more embedded devices, ESP-IDF support is particularly useful due to the framework’s built-in OTA firmware update capabilities.</description><content:encoded><![CDATA[<p>A few weeks ago we announced <a href="https://blog.golioth.io/signed-urls-for-embedded-devices/">support for signed URLs on embedded devices</a> and the initial release of <a href="https://github.com/golioth/signy"><code>signy</code></a>. This week we <a href="https://github.com/golioth/signy/releases/tag/v0.2.0">released <code>v0.2.0</code></a> of <code>signy</code>, which expands support beyond <a href="https://www.zephyrproject.org/">Zephyr RTOS</a> to Espressif’s <a href="https://idf.espressif.com/">ESP-IDF</a>. In addition to bringing signed URLs to more embedded devices, ESP-IDF support is particularly useful due to the framework’s built-in OTA firmware update capabilities.</p>
<h2 id="signed-urls-recap-to-briefly-recap-signed-urls-are-time-limited-credentials-that-grant-access-to-a-remote-private-resource-while-they-are-typically-used-on-the-web-when-a-server-issues-a-signed-url-to-a-client-that-then-uses-it-to-download-an-asset-from-a-content-delivery-network-cdn-they-can-also-be-useful-in-the-context-of-embedded-devices-for-example-devices-with-multiple-microcontollers-mcus-can-store-private-keys-in-the-secure-processing-environment-spe-of-one-mcu-then-issue-signed-urls-to-the-other-this-is-especially-useful-if-one-of-the-processors-is-not-equipped-with-the-same-level-of-security-features-as-the-other-the-less-secure-processor-is-only-ever-issued-a-narrowly-scoped-ephemeral-credential">Signed URLs Recap To briefly recap, signed URLs are time-limited credentials that grant access to a remote private resource. While they are typically used on the web when a server issues a signed URL to a client that then uses it to download an asset from a Content Delivery Network (CDN), they can also be useful in the context of embedded devices. For example, devices with multiple microcontollers (MCUs) can store private keys in the secure processing environment (SPE) of one MCU, then issue signed URLs to the other. This is especially useful if one of the processors is not equipped with the same level of security features as the other. The less secure processor is only ever issued a narrowly scoped, ephemeral credential.</h2>
<h2 id="bringing-signed-urls-to-the-esp32">Bringing Signed URLs to the ESP32</h2>
<p><code>signy</code> leverages <a href="https://arm-software.github.io/psa-api/crypto/">Arm’s Platform Security Architecture (PSA) Crypto API</a>, which offers a consistent interface for common cryptographic operations across embedded hardware and software platforms. This made adding support for ESP-IDF relatively painless, as the vast majority of the functionality in <code>signy</code> involves invoking PSA APIs, such as <code>psa_hash_compute()</code> and <code>psa_sign_hash()</code>. In addition to ensuring that <code>signy</code> only used standardized APIs, we also packaged the library as an <a href="https://docs.espressif.com/projects/idf-component-manager/en/latest/guides/packaging_components.html">ESP-IDF component</a>, meaning that it can now be included in any project by adding the following to the projects’s <code>idf_component.yml</code>.</p>
<pre class="EnlighterJSRAW" data-enlighter-language="yaml">dependencies:
  signy:
    version: v0.2.0
    git: https://github.com/golioth/signy.git</pre>
<h2 id="leveraging-esp-idfs-ota-firmware-update-capabilities-the">Leveraging ESP-IDF’s OTA Firmware Update Capabilities The</h2>
<p><a href="https://github.com/golioth/golioth-firmware-sdk">Golioth Firmware SDK</a> offers support for downloading assets, including firmware images, over-the-air (OTA). It also includes a reference implementation for firmware update, which provides an abstraction layer over the functionality offered by the various supported platforms (see the <a href="https://github.com/golioth/golioth-firmware-sdk/tree/37510301abbb30202cabe0bb69e2afccfdb834c8/examples/esp_idf/fw_update">example</a> of its use with ESP-IDF). However, ESP-IDF also offers its own high-level interface for firmware update in its <a href="https://docs.espressif.com/projects/esp-idf/en/v5.5.1/esp32s3/api-reference/system/esp_https_ota.html"><code>esp_https_ota</code> component</a>. As the name suggests, it leverages HTTP instead of <a href="https://blog.golioth.io/why-golioth-uses-coap/">CoAP</a>, which can be desirable when transferring large amounts of data, especially for products that are communicating over higher bandwidth mediums, such as Wi-Fi or Ethernet. Using <code>esp_https_ota</code> alongside the Golioth Firmware SDK has been possible in the past, but doing so was non-trivial. With <code>signy</code>, performing an OTA update over HTTP is much simpler: sign a URL for the asset, then pass it in the HTTP client configuration.</p>
<pre class="EnlighterJSRAW" data-enlighter-language="c">signy_sign_url(CONFIG_SIGNY_EXAMPLE_BASE_URL,
                     strlen(CONFIG_SIGNY_EXAMPLE_BASE_URL),
                     signed_url,
                     sizeof(signed_url),
                     &signed_url_len);

esp_http_client_config_t config = {
    .url = signed_url,
    .cert_pem = (char *) server_cert_pem_start,
    .event_handler = _http_event_handler,
    .keep_alive_enable = true,
    .buffer_size_tx = 10240,
};

esp_https_ota_config_t ota_config = {
    .http_config = &config,
};

esp_https_ota(&ota_config);</pre> The 
<p><code>signy</code> repository now includes a <a href="https://github.com/golioth/signy/tree/6795db49a3f0d609b43459e0c939695dcd8d0e63/examples/esp-idf/ota">full OTA firmware update example</a> with ESP-IDF, which demonstrates how a new firmware image can be downloaded from Golioth, then used to replace the existing firmware on the device.</p>
<h2 id="whats-next-esp32-devices-are-used-in-many-different-products-and-projects-while-ota-firmware-updates-are-one-use-case-for-signed-urls-we-are-excited-how-else-they-can-be-leveraged-let-us-know-how-you-are-using-signed-urls-on-the">What’s Next? ESP32 devices are used in many different products and projects. While OTA firmware updates are one use case for signed URLs, we are excited how else they can be leveraged. Let us know how you are using signed URLs on the</h2>
<p><a href="https://forum.golioth.io/">Golioth forum</a>!</p>
]]></content:encoded></item><item><title>Bluetooth roaming with Golioth Connectivity</title><link>https://blog.golioth.io/bluetooth-roaming-with-golioth-connectivity/</link><guid>https://blog.golioth.io/bluetooth-roaming-with-golioth-connectivity/</guid><pubDate>Tue, 28 Oct 2025 15:08:47 +0000</pubDate><dc:creator>Chris Gammell</dc:creator><category>Bluetooth</category><description>Golioth Connectivity enables Bluetooth devices to ‘roam’ between gateways out of the box, something that many IoT Bluetooth networks couldn’t dream of. Roaming1 has value in asset tracking applications, sensor nodes that aren’t fixed in place, and honestly…it’s just cool. What’s more, roaming just works because of how Pouch is architected with end-to-end (E2E) encryption2. Let’s look at the counter-example first and then dig into the architecture a bit more.</description><content:encoded><![CDATA[<p><a href="https://golioth.io/connectivity">Golioth Connectivity</a> enables Bluetooth devices to ‘roam’ between gateways out of the box, something that many IoT Bluetooth networks couldn’t dream of. Roaming<a href="#roaming"><sup>1</sup></a> has value in asset tracking applications, sensor nodes that aren’t fixed in place, and honestly…it’s just cool. What’s more, roaming <em>just works</em> because of how Pouch is architected with end-to-end (E2E) encryption<a href="#end-to-end-encryption"><sup>2</sup></a>. Let’s look at the counter-example first and then dig into the architecture a bit more.</p>
<h2 id="how-roaming-between-gateways-has-traditionally-worked">How roaming between Gateways has traditionally worked</h2>
<p>For most models of Bluetooth devices talking through Gateways
<a href="#gateway"><sup>3</sup></a> up to the Cloud, the Gateway is a trusted source. When a Gateway talks to a Bluetooth node, it needs to first determine if/when/how it’s going to connect to a device over the Bluetooth link. Often this is based on the UUID, something in the advertising packet, or a specific GATT profile being transmitted. Next the pairing process must complete so the Gateway (normally Central) and the Node<a href="#node"><sup>4</sup></a> (normally Peripheral) can communicate. Next, the Gateway needs to determine if that device is allowed to transmit data up to the Cloud. This can be done with a Gateway connection to a Cloud backend that validates a newly connected Node at the application level and determines whether the device is allowed to transmit. If it is allowed, the data starts flowing through the Gateway up to the Cloud. For this example, lets say this is Bluetooth Node 123 talking through Gateway ABC.</p>
<h3 id="if-the-node-moves-to-a-different-location-now-what">If the Node moves to a different location, now what?</h3>
<p>First, Node 123 ‘sees’ a new Gateway, we’ll call the new one Gateway XYZ. Once again, the gateway needs to determine if/when/how to connect over Bluetooth to the Node. If this requires that the link is encrypted, once again there would need to be some kind of manual intervention to enable the pairing to happen. If there is no such requirement, then it’s likely the Gateway will validate at the application level, though this might mean that the Bluetooth link and the data contained within is visible to bad actors peeping into Bluetooth traffic with a sniffer. In order to validate Node 123, the new Gateway XYZ either needs to talk to the Cloud or directly to Gateway ABC (much less likely). If the new Gateway XYZ validates the Node then it will be able to ‘Roam’ and pass data through the new Gateway. The gateways themselves can have a variety of methods to block/allow Nodes to pass data, but the infrastructure and security requirements ramp rapidly for this setup if you want to have a seamless handoff between Gateways. For instance, if you want to have asymmetric (cert based) validation of the Nodes, you need to have a root CA on every Gateway for every class of cert that might be presented. This can quickly overwhelm an IT group and require that the Gateway be complicated enough that the device move into the ‘Linux class’ (MPU based), in order to handle the computing and Cloud interaction elements. If you have 1000 Gateways and 20 different root CA certs that are valid at any given time, all 1000 Gateways need to synchronize with the master list of certs so that a Node can talk through any of the 1000 Gateways. Of course, there are tradeoffs that allow an embedded (MCU based) gateway to also act in this manner, but for sufficiently large fleets, the storage capabilities of the MCU may start to limit the number of devices that can be connected.</p>
<h3 id="what-about-when-the-gateway-is-an-app">What about when the Gateway is an app?</h3>
<p>The most common use case for Bluetooth devices is directly interacting with a mobile phone app. The idea of ‘roaming’ hardly ever enters the conversation. In this case, the idea of “Node communicating with the Cloud” is often skipped entirely and the App talks directly with a Bluetooth device and takes over all transmission of data up to the Cloud (when/if needed).</p>
<h2 id="how-its-different-with-golioth-connectivity-and-pouch">How it’s different with Golioth Connectivity and Pouch</h2>
<p>Golioth Bluetooth Connectivity is built upon
<a href="http://github.com/golioth/pouch">Pouch</a>, our innovation that enables indirectly connected devices to talk to the Cloud and introduces end-to-end encryption of data from a Node to the Cloud. This also includes the associated Cloud-side tooling that makes it seamless to have those packets decrypted and assigned to the proper <a href="https://console.golioth.io">device record in Golioth</a>. The Node device needs to be running the Pouch SDK; the packets sent from that device transit through Gateways that are also running a portion of the Pouch SDK and the Golioth Firmware SDK. We have <a href="https://github.com/golioth/bluetooth-gateway">precompiled binaries of our reference Gateway implementation</a> on a board like the FRDM-RW612, which makes it very easy to create a group of Nodes and Gateways and see roaming in action. Let’s talk about the same Node 123 (such as <a href="https://blog.golioth.io/open-sourcing-the-tikk-demo-board/">the Tikk board</a>) talking through a Gateway ABC (FRDM-RW612). The device advertises that it has uplink data available, the Gateway (central) connects to the Node (peripheral), and the device certificate is presented through the Gateway up to the Cloud. After the device is authenticated onto the Cloud, the certificate is cached (on the Cloud), and the uplink/downlink data is able to flow. If you look at the hex dump of the uplink data on the Gateway, you’ll see…well nonsense! That’s because Node 123 is end-to-end encrypted with the <em>Cloud</em>, not with the Gateway. The Gateway has no way of decrypting the data passing through it. Now Node 123 moves to a different location and it is again advertising that it’s ready for uplink. Gateway XYZ (thingy91x) is also running the Golioth Gateway reference implementation and connects to the Node. Once again the certificate is passed through the gateway up to the Cloud and validated. No allowlist or credential passing down to the Gateway is required because the Cloud is the ultimate arbiter, having the root CA that generated each device certificate. Want to learn more about certificates? Read our post about <a href="https://blog.golioth.io/unwrapping-certificates/">how certificate based security works</a> or go use <a href="https://blog.golioth.io/new-console-feature-the-certificate-generator/">our brand new certificate generator</a> on the Console to quickly try out the newly optimized workflow. In the Golioth use case, a mobile phone app can <em>also</em> be a gateway, and one that still enables roaming. If the application is authenticated to talk to Golioth and also has access to the Bluetooth radio, the app can shuttle packets along in the same way that an MCU or MPU class device does. The difference is that the phone will not directly interact with data from the Bluetooth device, instead relying on accessing data from the Cloud or via an alternative channel, such as a separate GATT profile.</p>
<h2 id="roaming-unlocked-and-simplified">Roaming unlocked and simplified</h2>
<p>Now that Node 123 can validate between Gateways ABC and XYZ, it’s easy to imagine how this could be expanded to more and more gateways, as happens in an asset tracker scenario. For super high value asset tracker scenarios, it’s conceivable that there are even more gateways to track movement than there are the nodes themselves! Golioth’s Pouch SDK and reference gateway implementations make it easy to spool up a new application that hops between gateways and still sends data up and down to the Cloud, while still taking advantage of the low cost and low power nature of Bluetooth connectivity. Head over to the Golioth forum to ask more questions about how the technology works or to let us know what you’re building next.</p>
<h2 id="glossary">Glossary</h2>
<p>Let’s make sure we have a good definition of the terms we’re discussing here</p>
<li id="roaming">
  Roaming – The ability for a device to send data through multiple points back to the Cloud
</li>
<li id="end-to-end-encryption">
  End-to-end encryption – An encryption scheme between Node and Cloud that no intermediary can ‘peer into’
</li>
<li id="gateway">
  Gateway – An embedded (MCU), Linux based (MPU), or app based (mobile) device that is allowed to bidirectionally shuttle data to and from the Cloud. Needs to have multiple radios in order to communicate with both Bluetooth devices and the Cloud (usually over Wi-Fi, Cellular, or Ethernet)
</li>
<li id="node">
  Node – A Bluetooth based device that has data to send to/from the Cloud
</li>
]]></content:encoded></item><item><title>'New Console Feature: The Certificate Generator'</title><link>https://blog.golioth.io/new-console-feature-the-certificate-generator/</link><guid>https://blog.golioth.io/new-console-feature-the-certificate-generator/</guid><pubDate>Thu, 23 Oct 2025 18:07:37 +0000</pubDate><dc:creator>Trond Snekvik</dc:creator><category>Cloud</category><category>Platform</category><category>Security</category><description>The shortest path should be the most secure We’re not building games at Golioth, but today we are borrowing an idea from the gaming industry: the most optimal way of playing a game should also be the most fun. Players are always going to pick the shortest path to winning the game, so it’s the game developer’s job to make sure this path also makes the game as fun as possible. Extending this idea to IoT and building more secure products, a corollary would be: The shortest path to shipping products should also be the most secure.</description><content:encoded><![CDATA[<h2 id="the-shortest-path-should-be-the-most-secure">The shortest path should be the most secure</h2>
<p>We’re not building games at Golioth, but today we are borrowing an idea from the gaming industry:
<a href="https://www.youtube.com/watch?v=7L8vAGGitr8">the most optimal way of playing a game should also be the most fun</a>.  Players are always going to pick the shortest path to winning the game, so it’s the game developer’s job to make sure this path also makes the game as fun as possible. Extending this idea to IoT and building more secure products, a corollary would be: <strong>The shortest path to shipping products should also be the most secure</strong>.</p>
<h2 id="the-problem">The Problem</h2>
<p>Currently, we support two types of credentials on the Golioth Cloud: Pre-Shared Keys (PSKs) and Certificates. When starting a new project on Golioth, one of the first things our users do is add their device in the Golioth Console, and set up some credentials that lets their device authenticate to the cloud. This is really creating a cloud-side representation of the device, which has yet to connect to the platform. PSKs are easy to understand and easy to use, being similar to a username/password. But they’re less secure than certificates: PSKs have to be stored on both the device and in Golioth’s servers ahead of time, which increases the overhead to the manufacturing entity, as well as the number of attack vectors. Certificates only include the public part of the device’s encryption key, and Golioth does not need to know the contents of the device’s key to verify its identity. We want to encourage our users to use certificates for authentication, but unfortunately, generating and signing certificates is pretty cumbersome, especially in the early stages of development. Ideally, developers should be setting up their own Public Key Infrastructure with an external provider, but this can be both intimidating and time consuming. We made a
<a href="https://docs.golioth.io/connectivity/credentials/pki#establishing-pki">short guide showing how they can sign their own certificates</a>, but that’s still not a production-grade solution. Storing your own private key for a root CA is much less secure than a Public Key Infrastructure provider. But, again, this is done to allows users to bypass the cumbersome PKI setup process during their initial setup. The difficulty again ramps up for users looking to just try out the capabilities of something like Golioth Connectivity: generating and loading self-signed certs is still harder than just copying the PSK onto the device. As a result, a lot of our users end up using PSKs during their development phase. When they’re ready to take their product to production, they often continue using PSKs (why rock the boat, right?). We’ve effectively failed them: The shortest path to shipping their product was with the less secure authentication method, so that’s what they chose.</p>
<h2 id="introducing-the-certificate-generator">Introducing The Certificate Generator</h2>
<p>What if we could maximize security
<em>and</em> convenience? To reduce the threshold for getting started with certificate based authentication, we have added a Certificate Generator to the Golioth Console.<img src="https://blog.golioth.io/wp-content/uploads/2025/10/certificate-generator-1030x579.png" alt=""> The Certificate Generator allows you to generate certificates and private keys for your devices directly in your browser, making it easy to use certificate based authentication throughout your development process. You don’t have to install any command line tools, and you can easily transition to an external Public Key Infrastructure provider later, without changing your firmware. You can find the Certificate Generator from the <a href="https://console.golioth.io/certificates">Certificates page in the console</a>, or at the end of the device creation workflow. Note that you do not have to create the device before generating the certificate. Through <a href="https://docs.golioth.io/connectivity/credentials/pki#zero-touch-provisioning">Zero Touch Provisioning</a>, you can just set a unique device name in the generator, and the device will get created in Golioth as soon as it authenticates. The Certificate Generator creates a temporary certificate authority that gets used to sign a device certificate you can download, alongside a matching private key. The certificate authority gets registered with Golioth automatically, and the certificate signing key is discarded as soon as it has signed the device certificate. Golioth never stores or even sees any of your generated private keys, so if you lose it, you’ll need to generate a new one. You can run the Certificate Generator as many times as you’d like, and each device certificate you generate will have its own dedicated certificate authority registered with Golioth. You can invalidate the device certificate by removing its certificate authority (sometimes referred to in our docs as a ‘root ca’).</p>
<h2 id="limitations">Limitations</h2>
<p>The goal of the Certificate Generator is to make it easier to pick the strongest authentication method for your product, but we have to be careful to avoid weakening the security in the process. To ensure that the certificate generator doesn’t get abused, we’ve added some restrictions:</p>
<ul>
<li>Generated certificates and their corresponding certificate authorities are only valid for four weeks. Once your certificate expires, you’ll have to generate a new one, or change to a proper Private Key Infrastructure provider.</li>
<li>As the certificate signing key gets discarded right away, it can only be used to sign a single device certificate.</li>
<li>Certificate authorities registered by the Certificate Generator get a <code>DEMO</code> tag attached to them. This is visible in the console and management APIs, but it does not affect the certificate authority’s behavior as long as it’s valid. We’re working on some pretty big improvements to the certificate based authentication workflow, which we’ll be able to show you pretty soon. We hope the Certificate Generator makes it easier to start using certificate based authentication from the very first device you connect to Golioth. Let us know what you think on our forums or reach out directly!</li>
</ul>
<!-- notionvc: 35e5fd81-0f33-4084-aae4-a9f7c2bd74bb -->
]]></content:encoded></item><item><title>Golioth Joins the Google Cloud Partner Program</title><link>https://blog.golioth.io/golioth-joins-the-google-cloud-partner-program/</link><guid>https://blog.golioth.io/golioth-joins-the-google-cloud-partner-program/</guid><pubDate>Tue, 21 Oct 2025 14:34:37 +0000</pubDate><dc:creator>Jonathan Beri</dc:creator><category>Integrations</category><category>News</category><description>We’re proud to share that Golioth is now an official Google Cloud Partner, listed in the Google Cloud Partner Directory. For years, Golioth has made it easy for IoT developers to connect their devices securely and move data into Google Cloud. With Golioth Pipelines, developers can stream data to Cloud Pub/Sub, upload files to Cloud Storage, and connect to services like BigQuery and Vertex AI — all with just a few lines of YAML. Pipelines also enables developers to integrate Google APIs, such as Google Maps Platform, to add context and positioning data to their fleets. When Google Cloud IoT Core was retired in 2023, Golioth became a natural option for developers who wanted a straightforward, secure, and scalable way to keep their devices connected while continuing to build on Google Cloud. By partnering officially with Google Cloud, we’re reinforcing our shared goal; to let developers focus on creating and capturing IoT data for their business, not on building the complex foundation required to manage devices at scale. Golioth takes care of the hard parts of operating an IoT fleet — end-to-end security, fleet management, device observability, and data routing — so teams can focus on unlocking value from their data using Google Cloud’s analytics and AI tools. Looking ahead, we’re working with Google Cloud on deeper integrations that will roll out later this year and into 2026, including simplified migrations from other IoT platforms and more flexible procurement options through the Google Cloud Marketplace. Golioth is built to handle the infrastructure, so you can focus on building your business — now, officially alongside Google Cloud.</description><content:encoded><![CDATA[<p>We’re proud to share that Golioth is now an official Google Cloud Partner, listed in the <a href="https://cloud.google.com/find-a-partner/partner/golioth">Google Cloud Partner Directory</a>. For years, Golioth has made it easy for IoT developers to connect their devices securely and move data into Google Cloud. With <a href="https://golioth.io/pipelines">Golioth Pipelines</a>, developers can stream data to <a href="https://docs.golioth.io/data-routing/destinations/gcp-pubsub/">Cloud Pub/Sub</a>, upload files to <a href="https://docs.golioth.io/data-routing/destinations/gcp-cloud-storage/">Cloud Storage</a>, and connect to services like BigQuery and Vertex AI — all with just a few lines of YAML. Pipelines also enables developers to integrate Google APIs, such as <a href="https://docs.golioth.io/data-routing/examples/google-geolocation">Google Maps Platform</a>, to add context and positioning data to their fleets. When Google Cloud IoT Core was retired in 2023, Golioth became a natural option for developers who wanted a straightforward, secure, and scalable way to keep their devices connected while continuing to build on Google Cloud. By partnering officially with Google Cloud, we’re reinforcing our shared goal; to let developers focus on creating and capturing IoT data for their business, not on building the complex foundation required to manage devices at scale. Golioth takes care of the hard parts of operating an IoT fleet — end-to-end security, fleet management, device observability, and data routing — so teams can focus on unlocking value from their data using Google Cloud’s analytics and AI tools. Looking ahead, we’re working with Google Cloud on deeper integrations that will roll out later this year and into 2026, including simplified migrations from other IoT platforms and more flexible procurement options through the Google Cloud Marketplace. Golioth is built to handle the infrastructure, so you can focus on building your business — now, officially alongside Google Cloud.</p>
]]></content:encoded></item><item><title>'Zephyr: How to Monitor Heap and Locate Memory Leaks'</title><link>https://blog.golioth.io/zephyr-how-to-monitor-heap-and-locate-memory-leaks/</link><guid>https://blog.golioth.io/zephyr-how-to-monitor-heap-and-locate-memory-leaks/</guid><pubDate>Thu, 16 Oct 2025 14:17:38 +0000</pubDate><dc:creator>Mike Szczys</dc:creator><category>How To</category><category>Zephyr</category><description>Dynamic memory allocation in C is a powerful tool but also a potential source of hard to replicate bugs. Edge cases that leak memory slowly over time may go unnoticed until your project scales, which can mean your devices are already in the field. But Zephyr includes heap monitoring features that you can add to your testing/debugging process to help you detect leaks and resolve them before they end up in production. I use heap monitoring in pretty much every Zephyr project where I use dynamic memory allocation. It’s easy to set up and use but it’s also absent from the Zephyr documentation, existing esoterically in the API reference. I’m writing this blog post to help inform you, but also so I can use this as my cheat sheet the next time I need it!</description><content:encoded><![CDATA[<p>Dynamic memory allocation in C is a powerful tool but also a potential source of hard to replicate bugs. Edge cases that leak memory slowly over time may go unnoticed until your project scales, which can mean your devices are already in the field. But Zephyr includes heap monitoring features that you can add to your testing/debugging process to help you detect leaks and resolve them before they end up in production. I use heap monitoring in pretty much every Zephyr project where I use dynamic memory allocation. It’s easy to set up and use but it’s also absent from the Zephyr documentation, existing esoterically in the API reference. I’m writing this blog post to help inform you, but also so I can use this as my cheat sheet the next time I need it!</p>
<h2 id="zephyr-system-heap-runtime-stats">Zephyr System Heap Runtime Stats</h2>
<p><a href="https://blog.golioth.io/wp-content/uploads/2025/10/zephyr-kernel-shell.png"><img src="https://blog.golioth.io/wp-content/uploads/2025/10/zephyr-kernel-shell.png" alt="Zephyr"></a>The good news is that if you are using the Zephyr Shell, you likely already have access to the heap stats by typing <code>kernel heap</code>. This is a useful tool for spot checking your heap usage. If the command is not working for you, make sure you have <code>CONFIG_KERNEL_SHELL</code> selected. Spot checks like this will catch egregious leaks where you keep allocating memory on a regular basis without freeing it. However, I’ve found that programmatically tracking heap usage and reporting on anomalous growth is the most reliable way to catch a subtle leak. If you look deep inside <a href="https://docs.zephyrproject.org/latest/doxygen/html/group__low__level__heap__allocator.html">the API reference for Zephyr’s low level heap allocator</a> you’ll be delighted to find <code>sys_heap_runtime_stats_get()</code>. <a href="https://blog.golioth.io/wp-content/uploads/2025/10/zephyr_sys_heap_runtime_stats_get_function.png"><img src="https://blog.golioth.io/wp-content/uploads/2025/10/zephyr_sys_heap_runtime_stats_get_function.png" alt="Zephyr API reference showing information about the sys_heap_runtime_stats_get() fuction."></a>However, it’s not entirely clear how to get the pointer to the system heap that this function requires as a parameter. Luckily, we already know the kernel shell is getting these stats from somewhere so it is just a matter of <a href="https://github.com/zephyrproject-rtos/zephyr/blob/545d40e3141ea73ac871b81d943e4b2cad5fe3b2/subsys/shell/modules/kernel_service/heap.c#L11-L13">grepping to find how it’s done</a>. Let’s put together a test app to illustrate.</p>
<h2 id="memory-leak-test-app">Memory Leak Test App</h2>
<p>Here’s a barebones application that includes a memory leak that is tracked by printing out heap stats during every other loop.</p>
<pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-group="1" data-enlighter-title="prj.conf">CONFIG_LOG=y
CONFIG_SHELL=y

# Heap size may default to 0 in which case, no heap is allocated
CONFIG_HEAP_MEM_POOL_SIZE=4096

# Use runtime stats
CONFIG_SYS_HEAP_RUNTIME_STATS=y</pre>
<pre class="EnlighterJSRAW" data-enlighter-language="c" data-enlighter-group="2" data-enlighter-title="src/main.c">#include <zephyr/logging/log.h>
LOG_MODULE_REGISTER(memory_leak, LOG_LEVEL_DBG);

#include <stdint.h>
#include <zephyr/sys/sys_heap.h>
#include <zephyr/kernel.h>

struct sys_memory_stats stat;

extern struct sys_heap _system_heap;

int main(void)
{
    uint32_t counter = 0;

    while (true) {
        int *my_int = k_malloc(sizeof(int));

        counter++;
        if (0 == counter % 2)
        {
            continue;
        }

        LOG_INF("Hello %u", counter);
        sys_heap_runtime_stats_get(&_system_heap, &stat);
        LOG_WRN("Heap: Free: %u, Alloc: %u, MaxAlloc: %u", stat.free_bytes,
                stat.allocated_bytes, stat.max_allocated_bytes);

        k_free(my_int);

        k_msleep(2000);
    }
}</pre> The important parts of this code include: 
<ul>
<li><code>CONFIG_SYS_HEAP_RUNTIME_STATS=y</code> to include the library in the build</li>
<li><code>extern struct sys_heap _system_heap</code> to point to the system heap</li>
<li><code>sys_heap_runtime_stats_get(&amp;_system_heap, &amp;stat)</code> to poll the heap stat information When we run the program, an integer is dynamically allocated at the beginning of the loop and freed at the end. However, if the counter is even, we continue the loop, which jumps past the</li>
</ul>
<p><code>k_free()</code> command causing a memory leak. Here’s the output of this program.</p>
<pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-linenumbers="false">*** Booting Zephyr OS build v4.2.1 ***
[00:21:16.071,588] <inf> memory_leak: Hello 2
[00:21:16.071,600] <wrn> memory_leak: Heap: Free: 3992, Alloc: 32, MaxAlloc: 32
[00:21:18.071,775] <inf> memory_leak: Hello 4
[00:21:18.071,785] <wrn> memory_leak: Heap: Free: 3976, Alloc: 48, MaxAlloc: 48
[00:21:20.071,868] <inf> memory_leak: Hello 6
[00:21:20.071,876] <wrn> memory_leak: Heap: Free: 3960, Alloc: 64, MaxAlloc: 64
[00:21:22.071,966] <inf> memory_leak: Hello 8
[00:21:22.071,974] <wrn> memory_leak: Heap: Free: 3944, Alloc: 80, MaxAlloc: 80
[00:21:24.072,063] <inf> memory_leak: Hello 10
[00:21:24.072,069] <wrn> memory_leak: Heap: Free: 3928, Alloc: 96, MaxAlloc: 96
[00:21:26.072,163] <inf> memory_leak: Hello 12
</pre> Here we see the allocated memory growing and growing. By moving around where the reporting is happening you can narrow down the part of your code that is causing the leak. 
<h2 id="zephyr-heap-listener">Zephyr Heap Listener</h2>
<p>Memory stats give you running totals of what is happening with the heap. But it’s also useful to know where and when the allocations are happening. For this we can use
<a href="https://docs.zephyrproject.org/latest/doxygen/html/group__heap__listener__apis.html">Zephyr’s Heap Listener features</a>. Let’s update our application to use listeners instead of stats.</p>
<pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-group="3" data-enlighter-title="prj.conf">CONFIG_LOG=y
CONFIG_SHELL=y

# Heap size may default to 0 in which case, no heap is allocated
CONFIG_HEAP_MEM_POOL_SIZE=4096

# Use heap listener
CONFIG_SYS_HEAP_LISTENER=y</pre>
<pre class="EnlighterJSRAW" data-enlighter-language="c" data-enlighter-group="4" data-enlighter-title="src/main.c">#include <zephyr/logging/log.h>
LOG_MODULE_REGISTER(memory_leak, LOG_LEVEL_DBG);

#include <stdint.h>
#include <zephyr/sys/sys_heap.h>
#include <zephyr/sys/heap_listener.h>
#include <zephyr/kernel.h>

extern struct sys_heap _system_heap;

void on_heap_alloc(uintptr_t heap_id, void *mem, size_t bytes)
{
    LOG_WRN("Allocated: %zu", bytes);
}
HEAP_LISTENER_ALLOC_DEFINE(my_heap_listener_alloc, HEAP_ID_FROM_POINTER(&_system_heap),
               on_heap_alloc);

void on_heap_free(uintptr_t heap_id, void *mem, size_t bytes)
{
    LOG_WRN("Freed: %zu", bytes);
}
HEAP_LISTENER_FREE_DEFINE(my_heap_listener_free, HEAP_ID_FROM_POINTER(&_system_heap), on_heap_free);

int main(void)
{
    uint32_t counter = 0;

    heap_listener_register(&my_heap_listener_alloc);
    heap_listener_register(&my_heap_listener_free);

    while (true) {
        int *my_int = k_malloc(sizeof(int));

        counter++;
        if (0 == counter % 2)
        {
            continue;
        }

        LOG_INF("Hello %u", counter);

        k_free(my_int);

        k_msleep(2000);
    }
}</pre> This approach uses a familiar Zephyr pattern to define callbacks for the allocation and free events. These listeners are registered at run time so that the callbacks run each time 
<p><code>k_malloc()</code> and <code>k_free()</code> are called. <a href="https://blog.golioth.io/wp-content/uploads/2025/10/zephyr-heap-listener.png"><img src="https://blog.golioth.io/wp-content/uploads/2025/10/zephyr-heap-listener.png" alt="Zephyr shell output showing twice as many allocation events as free events"></a>When we look at this output it is pretty obvious that we’re allocating twice as often as we are freeing memory. For our simple application this visual pattern tells the whole story. But in your own test code, these callbacks can be used to keep track of the stats, or monitor for an imbalance between the two functions. There is also a callback available for heap resize events.</p>
<h2 id="demystifying-heap-operations">Demystifying Heap Operations</h2>
<p>It’s easy to be complacent and never give heap usage a second thought. But memory leaks are notoriously difficult to track down in large code bases, so it pays to keep an eye on the heap starting early in your development. Use these built-in Zephyr tools in your testing and set up alerts to catch an issue early, or better yet, to give you peace of mind when those tests turn up green. If you find that your devices in the field already have a leak that are affecting operations,
<a href="https://docs.golioth.io/device-management/ota/">Golioth’s Over the Air firmware update capabilities</a> will make it easy to push a fix and even monitor the resulting logs.</p>
]]></content:encoded></item><item><title>Advanced IoT Data Uploads Using the Golioth Blockwise Stream</title><link>https://blog.golioth.io/advanced-iot-data-uploads-using-the-golioth-blockwise-stream/</link><guid>https://blog.golioth.io/advanced-iot-data-uploads-using-the-golioth-blockwise-stream/</guid><pubDate>Tue, 14 Oct 2025 15:45:38 +0000</pubDate><dc:creator>Mike Szczys</dc:creator><category>Data Management</category><category>SDK</category><description>Golioth makes it easy to upload large data payloads from your IoT devices to the cloud. We do this using the blockwise Stream service, which includes a versatile set of transform and destination options so the data goes where you want, in the format that you need. Our last post on Blockwise Stream covered the basics of calculating the blocks and sending then one-by-one to the Golioth server. But in the name of simplicity that example used a synchronous API call that blocked program flow until all data had been sent. Today let’s jump in and tackle the topics of proper asynchronous blockwise Stream, including dynamic allocation and accessing data in different ways.</description><content:encoded><![CDATA[<p>Golioth makes it easy to upload large data payloads from your IoT devices to the cloud. We do this using the blockwise Stream service, which includes a versatile set of <a href="https://docs.golioth.io/data-routing">transform and destination options</a> so the data goes where you want, in the format that you need. <a href="https://blog.golioth.io/?p=10154">Our last post on Blockwise Stream</a> covered the basics of calculating the blocks and sending then one-by-one to the Golioth server. But in the name of simplicity that example used a synchronous API call that blocked program flow until all data had been sent. Today let’s jump in and tackle the topics of proper asynchronous blockwise Stream, including dynamic allocation and accessing data in different ways.</p>
<h2 id="prerequisites">Prerequisites</h2>
<p>If you want to give this a try yourself, here’s what you need to have solved before jumping in today:</p>
<ol>
<li>Have a Golioth project set up with a Pipeline to receive your Stream data</li>
<li>Understand the concepts behind sending data block-by-block using the <code>golioth_stream_set_blockwise_sync()</code> API call Both of these are covered in <a href="https://blog.golioth.io/?p=10154">How to Use Golioth Blockwise Stream to Upload Large IoT Data Payloads</a>.</li>
</ol>
<h2 id="using-a-dynamically-allocated-data-wrapper">Using a Dynamically Allocated Data Wrapper</h2>
<p>When using the async blockwise stream API it will be our responsibility for tracking the upload process through each block. To facilitate this, we can create a new struct in our program.</p>
<pre class="EnlighterJSRAW" data-enlighter-language="c">struct block_stream_container {
    struct blockwise_transfer *transfer_ctx;
    uint8_t *data;
    size_t data_len;
    uint32_t block_idx;
    size_t block_size;
    bool is_last;
};</pre> The blockwise_transfer member of this struct is required by the Golioth Firmware SDK when using the async blockwise stream system. We’ll hold onto it alongside a pointer to the data we want to send and the total length of that data. The rest of the struct tracks the progress of the transfer. 
<h2 id="a-helper-function-to-send-blocks">A Helper Function to Send Blocks</h2>
<p>We need to call the async API once for each block, so let’s set up a function just for this purpose.</p>
<pre class="EnlighterJSRAW" data-enlighter-language="c">enum golioth_status send_next_block(struct block_stream_container *container)
{
    if (NULL == container)
    {
        LOG_ERR("Container cannot be NULL");
        return GOLIOTH_ERR_NULL;
    }

    uint32_t offset = container->block_idx * container->block_size;

    /* Test to see if this is the final block of data we need to send */
    if (container->data_len <= offset + container->block_size)
    {
        /* Overwrite the block_size with number of bytes in last block */
        container->block_size = container->data_len - offset;
        /* Indicate to the Golioth Firmware SDK that this is the final block */
        container->is_last = true;
    }

    LOG_INF("Sending block %u of %zu bytes.%s",
            container->block_idx,
            container->block_size,
            container->is_last ? " Final block." : "");

    return golioth_stream_blockwise_set_block_async(container->transfer_ctx,
                                                    container->block_idx,
                                                    container->data + offset,
                                                    container->block_size,
                                                    container->is_last,
                                                    on_stream_block_sent,
                                                    container);
}
</pre> This function calculates information for sending the next block. We’re not going to go over this part again as it was 
<p><a href="https://blog.golioth.io/?p=10146#blockwise-async-callback-and-user-arguments">covered in detail</a> in the previous post. Just remember that we need to calculate the offset for the next block (using that value to read from the correct place in memory). We also need to handle the size and flag when sending the final block. This function submits each block to the Golioth Firmware SDK using the <code>golioth_stream_blockwise_set_block_async()</code> API. Note that everything we need for parameters comes from the container struct. Well, everything except the callback which you could store in the struct if you need different callbacks for some types of blockwise stream. The final argument is the pointer to our struct which will be passed to the callback function. This will be important later!</p>
<h2 id="beginning-the-asyncronous-blockwise-stream">Beginning the Asyncronous Blockwise Stream</h2>
<p>We haven’t yet discussed the async callback function, but let’s first look at initiating the blockwise stream as this is where our memory handling adventure begins.</p>
<pre class="EnlighterJSRAW" data-enlighter-language="c">static void send_blockwise_stream(void)
{
    struct block_stream_container *container = k_malloc(sizeof(struct block_stream_container));
    if (NULL == container)
    {
        LOG_ERR("Failed to allocate container; stream aborted");
        return;
    }

    container->transfer_ctx =
        golioth_stream_blockwise_start(client, "my-test-path", GOLIOTH_CONTENT_TYPE_JSON);
    if (NULL == container->transfer_ctx)
    {
        LOG_ERR("Failed to allocate transfer context; stream aborted.");
        k_free(container);
        return;
    }

    container->data = (uint8_t *)json_data;
    container->data_len = json_data_len;
    container->block_idx = 0;
    container->block_size = CONFIG_GOLIOTH_BLOCKWISE_UPLOAD_MAX_BLOCK_SIZE;
    container->is_last = 0;

    enum golioth_status status = send_next_block(container);
    if (GOLIOTH_OK != status)
    {
        LOG_ERR("Failed to start blockwise stream: %d", status);
        /* Clean up memory before returning */
        golioth_stream_blockwise_finish(container->transfer_ctx);
        k_free(container);
        return;
    }
}</pre> We begin by dynamically allocating memory for the container struct. This means the struct can outlive this function, remaining available to use as we send each block in the upload. Next we call 
<p><code>golioth_stream_blockwise_start()</code> which takes the Golioth client pointer, the path to which we’re sending data, and the data type. We will need to remember to call <code>golioth_stream_blockwise_finish()</code> and <code>k_free()</code> the context at the end of the stream operation, lest we create a memory leak. Note that we are calling these as part of the error handling in this function. But we will see them again in the next section of the post. This function sets up everything for the stream operation, starting with a data pointer and length. This assumes the data you wish to send is already in memory and can be directly accessed. But later in the post we’ll discuss other options for accessing block data to send. Worth noting is the use of <code>CONFIG_GOLIOTH_BLOCKWISE_UPLOAD_MAX_BLOCK_SIZE</code> as the starting block size. This symbol is provided by the Golioth Firmware SDK and is the upper limit for sending blocks. With everything configured, the send process begins by calling our <code>send_next_block()</code> function we discussed in the previous section. As long as that returns <code>GOLIOTH_OK</code>, program flow can continue and the asynchronous callbacks will handle the rest. Speaking of, we need to define our callback!</p>
<h2 id="async-callback">Async Callback</h2>
<p>The Golioth Firmware SDK will call a user callback at the end of sending each block. This callback receives the success/failure code from the block, and it’s a great way for us to know when the SDK is ready for the next block. This may look like a lot, but it’s mostly error handling.</p>
<pre class="EnlighterJSRAW" data-enlighter-language="c">static void on_stream_block_sent(struct golioth_client *client,
                                 enum golioth_status status,
                                 const struct golioth_coap_rsp_code *coap_rsp_code,
                                 const char *path,
                                 size_t block_size,
                                 void *arg)
{
    if (NULL == arg)
    {
        LOG_ERR("Expected block_stream_container pointer as arg but got NULL");
        return;
    }
    struct block_stream_container *container = arg;

    if (GOLIOTH_OK != status)
    {
        if (GOLIOTH_ERR_COAP_RESPONSE == status)
        {
            LOG_ERR("Blockwise stream failed with CoAP code %d.%02d",
                    coap_rsp_code->code_class,
                    coap_rsp_code->code_detail);
        }
        else
        {
            LOG_ERR("Blockwise stream failed: %d", status);
        }

        goto end_blockwise_stream;
    }

    if (true == container->is_last)
    {
        LOG_INF("Blockwise stream completed successfully");
        goto end_blockwise_stream;
    }

    /* Last block completed successfully but there are still more to send */

    /* Increment the block number */
    container->block_idx += 1;
    /* Ensure we use the block_size the server wants */
    container->block_size = block_size;

    enum golioth_status err = send_next_block(container);
    if (GOLIOTH_OK != err)
    {
        LOG_ERR("Failed to send next block of blockwise stream: %d", status);
        goto end_blockwise_stream;
    }

    return;

end_blockwise_stream:
    golioth_stream_blockwise_finish(container->transfer_ctx);
    k_free(container);
}</pre> Remember when we passed the pointer to our container struct as a user argument? We get that here in the callback function which can be first checked for null, then cast to our 
<p><code>block_stream_container</code> type. From there we check the status code that was returned by the Golioth Firmware SDK. This indicates that the block was successfully sent to the cloud (<code>GOLIOTH_OK</code>) or an error if unsuccessful. Some types of error will return a CoAP error, which explains the complicated logging seen above when an error is received. Next we check to see if the block we just sent is the last block of the transfer. If so, we can just return without further action. Otherwise now’s the time to increment the block index and call our <code>send_next_block()</code> function. Note that we always use the <code>block_size</code> received as a parameter of the callback. In practice this will almost always be the <code>CONFIG_GOLIOTH_BLOCKWISE_UPLOAD_MAX_BLOCK_SIZE</code> value we used when the blockwise stream was started, it is (very unlikely but) possible the server will request a smaller block size and this is how we handle that case. If at any point in this callback we encounter an error, or if we’ve already sent the final block, we need to clean up our memory here. The original calling function already went out of scope, so this will be the last time we have a pointer to that memory. We carefully clean up the <code>transfer_context</code> used by the SDK async blockwise stream APIs, then free the container itself.</p>
<h2 id="putting-it-all-together">Putting It All Together</h2>
<p>You can run the example code for yourself.
<a href="https://github.com/golioth/golioth-firmware-sdk/tree/main/examples/zephyr/stream">Start with the stream sample</a> from the Golioth Firmware SDK, replacing the <code>main.c</code> file with the one below. Build, flash, and provision the code following <a href="https://github.com/golioth/golioth-firmware-sdk/tree/main/examples/zephyr/stream#building-and-running">the README</a> in the SDK. The one caveat is that we need to make sure Zephyr includes <code>k_malloc()</code>. The default for this sample is 0 so add the following Kconfig symbol to your prj.conf file:</p>
<pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-linenumbers="false">CONFIG_HEAP_MEM_POOL_SIZE=128
</pre> This is quite a small heap but should make it a quick process to prove you do not have a memory leak. 
<pre class="EnlighterJSRAW" data-enlighter-language="c" data-enlighter-group="1" data-enlighter-title="examples/zephyr/stream/src/main.c">/*
 * Copyright (c) 2025 Golioth, Inc.
 *
 * SPDX-License-Identifier: Apache-2.0
 */

#include "golioth/golioth_status.h"
#include <zephyr/logging/log.h>
LOG_MODULE_REGISTER(golioth_stream, LOG_LEVEL_DBG);

#include <golioth/client.h>
#include <golioth/stream.h>
#include <samples/common/sample_credentials.h>
#include <samples/common/net_connect.h>
#include <stdlib.h>
#include <string.h>
#include <zephyr/kernel.h>

static const char json_data[] = "{\"my-quote\":\"1. The Imitation Game\\nI propose to consider the question, ‘Can machines think?’ This should begin with definitions of the meaning of the terms ‘machine’ and ‘think’. The definitions might be framed so as to reflect so far as possible the normal use of the words, but this attitude is dangerous. If the meaning of the words ‘machine’ and ‘think’ are to be found by examining how they are commonly used it is difficult to escape the conclusion that the meaning and the answer to the question, ‘Can machines think?’ is to be sought in a statistical survey such as a Gallup poll. But this is absurd. Instead of attempting such a definition I shall replace the question by another, which is closely related to it and is expressed in relatively unambiguous words.\\nThe new form of the problem can be described in terms of a game which we call the ‘imitation game’. It is played with three people, a man (A), a woman (B), and an interrogator (C) who may be of either sex. The interrogator stays in a room apart from the other two. The object of the game for the interrogator is to determine which of the other two is the man and which is the woman. He knows them by labels X and Y, and at the end of the game he says either ‘X is A and Y is B’ or ‘X is B and Y is A’. The interrogator is allowed to put questions to A and B thus\"}";

static size_t json_data_len = strlen(json_data);

struct golioth_client *client;
static K_SEM_DEFINE(connected, 0, 1);

static void on_client_event(struct golioth_client *client,
                            enum golioth_client_event event,
                            void *arg)
{
    bool is_connected = (event == GOLIOTH_CLIENT_EVENT_CONNECTED);
    if (is_connected)
    {
        k_sem_give(&connected);
    }
    LOG_INF("Golioth client %s", is_connected ? "connected" : "disconnected");
}

struct block_stream_container {
    struct blockwise_transfer *transfer_ctx;
    uint8_t *data;
    size_t data_len;
    uint32_t block_idx;
    size_t block_size;
    bool is_last;
};

/* forward declaration */
enum golioth_status send_next_block(struct block_stream_container *container);

static void on_stream_block_sent(struct golioth_client *client,
                                 enum golioth_status status,
                                 const struct golioth_coap_rsp_code *coap_rsp_code,
                                 const char *path,
                                 size_t block_size,
                                 void *arg)
{
    if (NULL == arg)
    {
        LOG_ERR("Expected block_stream_container pointer as arg but got NULL");
        return;
    }
    struct block_stream_container *container = arg;

    if (GOLIOTH_OK != status)
    {
        if (GOLIOTH_ERR_COAP_RESPONSE == status)
        {
            LOG_ERR("Blockwise stream failed with CoAP code %d.%02d",
                    coap_rsp_code->code_class,
                    coap_rsp_code->code_detail);
        }
        else
        {
            LOG_ERR("Blockwise stream failed: %d", status);
        }

        goto end_blockwise_stream;
    }

    if (true == container->is_last)
    {
        LOG_INF("Blockwise stream completed successfully");
        goto end_blockwise_stream;
    }

    /* Last block completed successfully but there are still more to send */

    /* Increment the block number */
    container->block_idx += 1;
    /* Ensure we use the block_size the server wants */
    container->block_size = block_size;

    enum golioth_status err = send_next_block(container);
    if (GOLIOTH_OK != err)
    {
        LOG_ERR("Failed to send next block of blockwise stream: %d", status);
        goto end_blockwise_stream;
    }

    return;

end_blockwise_stream:
    golioth_stream_blockwise_finish(container->transfer_ctx);
    k_free(container);
}

enum golioth_status send_next_block(struct block_stream_container *container)
{
    if (NULL == container)
    {
        LOG_ERR("Container cannot be NULL");
        return GOLIOTH_ERR_NULL;
    }

    uint32_t offset = container->block_idx * container->block_size;

    /* Test to see if this is the final block of data we need to send */
    if (container->data_len <= offset + container->block_size)
    {
        /* Overwrite the block_size with number of bytes in last block */
        container->block_size = container->data_len - offset;
        /* Indicate to the Golioth Firmware SDK that this is the final block */
        container->is_last = true;
    }

    LOG_INF("Sending block %u of %zu bytes.%s",
            container->block_idx,
            container->block_size,
            container->is_last ? " Final block." : "");

    return golioth_stream_blockwise_set_block_async(container->transfer_ctx,
                                                    container->block_idx,
                                                    container->data + offset,
                                                    container->block_size,
                                                    container->is_last,
                                                    on_stream_block_sent,
                                                    container);
}

static void send_blockwise_stream(void)
{
    struct block_stream_container *container = k_malloc(sizeof(struct block_stream_container));
    if (NULL == container)
    {
        LOG_ERR("Failed to allocate container; stream aborted");
        return;
    }

    container->transfer_ctx =
        golioth_stream_blockwise_start(client, "my-test-path", GOLIOTH_CONTENT_TYPE_JSON);
    if (NULL == container->transfer_ctx)
    {
        LOG_ERR("Failed to allocate transfer context; stream aborted.");
        k_free(container);
        return;
    }

    container->data = (uint8_t *)json_data;
    container->data_len = json_data_len;
    container->block_idx = 0;
    container->block_size = CONFIG_GOLIOTH_BLOCKWISE_UPLOAD_MAX_BLOCK_SIZE;
    container->is_last = 0;

    enum golioth_status status = send_next_block(container);
    if (GOLIOTH_OK != status)
    {
        LOG_ERR("Failed to start blockwise stream: %d", status);
        /* Clean up memory before returning */
        golioth_stream_blockwise_finish(container->transfer_ctx);
        k_free(container);
        return;
    }
}

int main(void)
{
    LOG_DBG("Start Golioth blockwise stream sample");

    net_connect();

    /* Note: In production, credentials should be saved in secure storage. For
     * simplicity, we provide a utility that stores credentials as plaintext
     * settings.
     */
    const struct golioth_client_config *client_config = golioth_sample_credentials_get();

    client = golioth_client_create(client_config);
    golioth_client_register_event_callback(client, on_client_event, NULL);

    k_sem_take(&connected, K_FOREVER);

    /* Give network connection logs a few seconds to clear */
    k_msleep(2000);

    int counter = 0;
    while(counter++ < 5)
    {
        send_blockwise_stream();
        k_msleep(5000);
    }

    return 0;
}
</pre> When monitoring the device via a serial terminal you will see logging messages that track the state of the blockwise upload: 
<pre class="EnlighterJSRAW" data-enlighter-language="generic">*** Booting Zephyr OS build v4.1.0 ***
*** Golioth Firmware SDK v0.20.0-6-gd6836844668c ***
[01:03:50.293,041] <inf> golioth_settings_autoload: Initializing settings subsystem
[01:03:50.294,606] <inf> fs_nvs: 8 Sectors of 4096 bytes
[01:03:50.294,624] <inf> fs_nvs: alloc wra: 0, f90
[01:03:50.294,631] <inf> fs_nvs: data wra: 0, d9
[01:03:50.294,668] <inf> golioth_settings_autoload: Loading settings
[01:03:50.294,873] <dbg> golioth_stream: main: Start Golioth blockwise stream sample
[01:03:50.294,913] <inf> golioth_samples: Starting DHCP to obtain IP address
[01:03:50.294,996] <inf> golioth_samples: Waiting to obtain IP address
[01:03:57.331,030] <inf> net_dhcpv4: Received: 192.168.1.238
[01:03:57.331,490] <inf> golioth_mbox: Mbox created, bufsize: 1320, num_items: 10, item_size: 120
[01:03:57.559,826] <inf> golioth_coap_client_zephyr: Golioth CoAP client connected
[01:03:57.559,937] <inf> golioth_stream: Golioth client connected
[01:03:57.560,077] <inf> golioth_coap_client_zephyr: Entering CoAP I/O loop
[01:03:59.560,104] <inf> golioth_stream: Sending block 0 of 1024 bytes.
[01:03:59.602,783] <inf> golioth_stream: Sending block 1 of 349 bytes. Final block.
[01:03:59.660,987] <inf> golioth_stream: Blockwise stream completed successfully
</pre> And the data can be verified in the LightDB Stream tab of your device on 
<p><a href="https://console.golioth.io">the Golioth web console</a>.</p>
<p><a href="https://blog.golioth.io/wp-content/uploads/2025/09/blockwise-stream-example.png"><img src="https://blog.golioth.io/wp-content/uploads/2025/09/blockwise-stream-example-1030x757.png" alt="Web console showing a JSON data structure that contains &ldquo;my-test-path&rdquo; and &ldquo;my-quote&rdquo; followed by a long quote from Alan Turing"></a></p>
<h2 id="what-if-your-data-isnt-in-memory">What if Your Data Isn’t In Memory?</h2>
<p>Your data may not be in memory that is directly accessible. This is the case when you have stored readings on a filesystem, or when you’re using another library that handles its own memory but provides chunked access to you. Don’t worry, just update the container struct to store a reference to how this data may be accessed, and add a block buffer when sending the block. (This buffer memory may go out of scope as soon as the Golioth</p>
<p><code>golioth_stream_blockwise_set_block_async()</code> command returns as the SDK will make a copy of it before the function returns.) While you need to implement this chunk-read function yourself, here’s how our code from above would differ with this approach.</p>
<pre class="EnlighterJSRAW" data-enlighter-language="c">uint8_t block_cache[CONFIG_GOLIOTH_BLOCKWISE_UPLOAD_MAX_BLOCK_SIZE];

enum golioth_status send_next_block(struct block_stream_container *container)
{
    /* unchanged code removed from beginning of function */

    read_chunk_from_memory(container->filesystem_uri, offset, container->block_size, block_cache);

    return golioth_stream_blockwise_set_block_async(container->transfer_ctx,
                                                    container->block_idx,
                                                    block_cache,
                                                    container->block_size,
                                                    container->is_last,
                                                    on_stream_block_sent,
                                                    container);
}
</pre> Allocating 1024 bytes for a buffer is a lot to put on the heap so here we’ve statically allocated the buffer. It is only accessed from this function. 
<h2 id="an-excellent-pipe-from-device-to-cloud">An Excellent Pipe from Device to Cloud</h2>
<p>Getting data from your device fleet to the cloud is something Golioth thinks about all the time. As you’ve seen here, we not only make it easy, but we provide flexibility on both the firmware side and on the cloud side with all the data management tools you will need.
<a href="https://console.golioth.io">Take Golioth for a test drive</a> to day and let us know how it goes by <a href="https://forum.golioth.io/">posting to the Golioth forum</a>!</p>
]]></content:encoded></item><item><title>Golioth Firmware SDK v0.21.0</title><link>https://blog.golioth.io/golioth-firmware-sdk-v0-21-0/</link><guid>https://blog.golioth.io/golioth-firmware-sdk-v0-21-0/</guid><pubDate>Thu, 09 Oct 2025 15:30:25 +0000</pubDate><dc:creator>Sam Friedman</dc:creator><category>Firmware</category><category>SDK</category><description>Yesterday, we released v0.21.0 of the Golioth Firmware SDK. This release improves the robustness of OTA firmware updates, adds more flexibility to WiFi networks in our samples, and updates our support for Zephyr and the nRF Connect SDK to the latest releases.</description><content:encoded><![CDATA[<p>Yesterday, we released <a href="https://github.com/golioth/golioth-firmware-sdk/releases/tag/v0.21.0">v0.21.0</a> of the Golioth Firmware SDK. This release improves the robustness of OTA firmware updates, adds more flexibility to WiFi networks in our samples, and updates our support for Zephyr and the nRF Connect SDK to the latest releases.</p>
<h2 id="improved-ota-robustness-the-golioth-firmware-sdk-makes-use-of">Improved OTA Robustness The Golioth Firmware SDK makes use of</h2>
<p><a href="https://www.rfc-editor.org/rfc/rfc7641.html">CoAP Observations</a> to be notified anytime a new OTA manifest is available. Observations allow the server to push updates to a resource directly to a client, so that the client can conserve bandwidth and power, while receiving updates with low latency. While generally reliable, these notifications, like all traffic on the Internet, are not 100% guaranteed. Packets can be dropped due to spotty Internet connections or congestion on the device itself. Polling can be more robust, as if any individual response is dropped, the server has another chance to send the response the next time the client requests an update. But most of the time there isn’t any new update, and achieving low latency requires many requests, resulting in high bandwidth and power usage. We’ve combined the best of both worlds into a polling-with-short-circuit mechanism that we’re calling <em>subscriptions</em>. Subscriptions still use CoAP observations to get asynchronously notified of new updates with low latency. But it layers polling with a long interval on top. This ensures that in the ‘happy path’, devices are notified of any changes immediately. In the worst case, they are still notified eventually. This results in a small increase in power usage, and the polling is configurable via a new KConfig symbol called <code>CONFIG_GOLIOTH_OTA_MANIFEST_SUBSCRITION_POLL_INTERVAL_S</code>. We’re rolling this out first for OTA manifests, but soon we’ll use this subscription mechanism in place of all observations in the SDK.</p>
<h2 id="more-flexible-wi-fi">More Flexible Wi-Fi</h2>
<p>When we first added support for Wi-Fi devices to our samples, there was no standard way to store and retrieve Wi-Fi credentials in Zephyr, so we built our own. It was relatively simple, only allowing open or WPA2-PSK networks, and storing credentials in plaintext in Zephyr’s settings subsystem. Since that time, Zephyr has implemented a robust <a href="https://docs.zephyrproject.org/latest/connectivity/networking/api/wifi_credentials.html">Wi-Fi Credentials library</a> that outclasses our own. Credentials can be added through the <code>wifi</code> shell for a wide variety of network types, and credentials can be stored in PSA secure storage in addition to the settings subsystem. We’ve moved our samples to use this library, which will result in a more flexible experience for users, while bringing our samples in line with the wider Zephyr ecosystem.</p>
<h2 id="updated-platform-support">Updated Platform Support</h2>
<p>We’ve updated our support for Zephyr and the nRF Connect SDK to the latest versions. That’s
<a href="https://github.com/zephyrproject-rtos/zephyr/releases/tag/v4.2.1">v4.2.1</a> for Zephyr and <a href="https://github.com/nrfconnect/sdk-nrf/releases/tag/v3.1.1">v3.1.1</a> for the nRF Connect SDK. While there’s always lots of great additions in each release, this time around we’re particularly excited for the new silicon and boards that are supported in these new releases. <!-- notionvc: 5112d6ee-afda-49f4-bb02-7a4da3ee7984 --></p>
]]></content:encoded></item><item><title>Visualizing Bluetooth OTA Firmware Updates</title><link>https://blog.golioth.io/visualizing-bluetooth-ota-firmware-updates/</link><guid>https://blog.golioth.io/visualizing-bluetooth-ota-firmware-updates/</guid><pubDate>Wed, 08 Oct 2025 13:30:20 +0000</pubDate><dc:creator>Chris Gammell</dc:creator><category>Bluetooth</category><category>Gateways</category><description>One of the best things about [Golioth Connectivity][1] is it makes Bluetooth devices act like all the other devices on the Golioth cloud. And one of my favorite things about Golioth is the ability to do Over-The-Air (OTA) firmware updates without any fuss. Today we’re not only going to show how the sample in our Pouch repository implements OTA, but we’ll visualize it on a development board that has a lot of LEDs and activate the firmware update using the accelerometer. Let’s dig in.</description><content:encoded><![CDATA[<p>One of the best things about [Golioth Connectivity][1] is it makes Bluetooth devices act like all the other devices on the Golioth cloud. And one of my favorite things about Golioth is the ability to do Over-The-Air (OTA) firmware updates without any fuss. Today we’re not only going to show how the sample in our Pouch repository implements OTA, but we’ll visualize it on a development board that has a lot of LEDs and activate the firmware update using the accelerometer. Let’s dig in. <div style="position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden;">
      <iframe allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share; fullscreen" loading="eager" referrerpolicy="strict-origin-when-cross-origin" src="https://www.youtube.com/embed/o9Whb78XaIo?autoplay=0&amp;controls=1&amp;end=0&amp;loop=0&amp;mute=0&amp;start=0" style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; border:0;" title="YouTube video"></iframe>
    </div>
</p>
<h2 id="how-ota-works-with-pouch">How OTA works with Pouch</h2>
<p>[Pouch][2] is the Golioth innovation that enables [Bluetooth devices to talk through standard gateways up to the Cloud][3] and send/receive data like any other device on the cloud. It is end-to-end encrypted and can access nearly all of the same services as cellular, Wi-Fi, or Ethernet devices running the Golioth Firmware SDK. Here’s the order of events, as it works in our reference implementation:</p>
<ol>
<li>Bluetooth device advertises that there is data to send</li>
<li>[Gateway][4] initiates connection with Bluetooth device</li>
<li>Bluetooth device sends data up to the cloud (uplink)</li>
<li>After uplink, available data (downlink) is sent down as a response, including services like OTA or Golioth settings</li>
<li>Bluetooth device disconnects and the entire cycle can start anew, depending on the device Note that it doesn’t need to work in that order, since many of the services in Pouch are independent of one another. Furthermore, it’s possible for you to create a completely bespoke version of the Gateway that handles Pouch traffic. But currently, our demos follow the order of events listed above. In my setup, I am using</li>
</ol>
<p>[the pre-compiled binary that is built for the NXP FRDM-RW612][5]. Once I program that in and add Golioth credentials to the Gateway, I basically don’t need to think about it again. The Gateway logs the connections that are happening, but cannot see any of the traffic transiting through, since only the Cloud has keys paired with the Bluetooth device. <figure id="attachment_10165" aria-describedby="caption-attachment-10165" style="width: 696px" class="wp-caption aligncenter"><img src="https://blog.golioth.io/wp-content/uploads/2025/10/Encrypted-Gateway-Traffic-1030x963.png" alt=""><figcaption id="caption-attachment-10165" class="wp-caption-text">Example log output from the FRDM-RW612 acting as a gateway</figcaption></figure> Looking at [the BLE GATT sample on the Pouch repository][6], we can see that OTA updates are already implemented using MCUboot. The device reports its firmware version using the VERSION file in the repository:<img src="https://blog.golioth.io/wp-content/uploads/2025/10/VERSION-File-1030x394.png" alt=""> This is used to report MCUboot firmware version at startup and to check for new available versions on the cloud. When a new version of firmware is deployed to a Cohort and the Bluetooth device checks in with the Cloud, that device will see the updated version of firmware is available as part of the downlink data. It sounds complex, but works really smoothly…let’s look at how.</p>
<h2 id="visualizing-ota-updates">Visualizing OTA updates</h2>
<p>We have been working on a demo using the Tikk board as an asset tracker, albeit a very LED-flashy one. Part of this work was developing a wake-on-movement behavior using the accelerometer (more about that in future posts). We pulled that work and the ability to control the LED matrix on the front of
[the open source Tikk board][7]. So we put that all together and what do we get? A Bluetooth device that prompts you to shake it to get an update to the OTA firmware!</p>
<h3 id="modifications">Modifications</h3>
<p>This took the standard files you see in BLE GATT demo and added some new functionality. First in the
<code>ota_manifest_receive</code> function, we add in some LED messaging to report the received firmware version and also clear out any previous indication of status, in case an OTA restarts.</p>
<pre class="EnlighterJSRAW" data-enlighter-language="generic">static void ota_manifest_receive(const struct golioth_ota_manifest_component *components,
                                 size_t num_components)
{

    char buf[32];
    for (int i = 0; i < num_components; i++)
    {
        fw_size = components[i].size;
        LOG_DBG("Target: %s@%s, %d bytes",
                components[i].name,
                components[i].target,
               fw_size);
        LOG_HEXDUMP_DBG(components[i].target_hash,
                        GOLIOTH_OTA_COMPONENT_HASH_BIN_LEN,
                        "Target hash:");
        if (0 != strcmp(components[i].current, components[i].target))
        {
            golioth_ota_mark_for_download(components[i].name);
        }
        snprintk(buf,sizeof(buf),"FW ver %s ready", components[i].target);
    }
    led_message(buf);
    led_message("Shake to start");

    clear_led_progress_bar();
}</pre> In 
<p><code>ota_main_receive</code> we see how the blocks of data are received from the cloud 1K at a time. Much like the blockwise_upload that sends data in the other direction, the total image size is split up into chunks and then tracked block by block. Since we know the current block and we know the total size, we can do some math on the progress and apply that to the 105 LEDs on the front of the Tikk board.</p>
<pre class="EnlighterJSRAW" data-enlighter-language="generic">static void ota_main_receive(const void *data, size_t offset, size_t len, bool is_last)
{
    LOG_DBG("Received %d bytes at offset %d", len, offset);

    int err = 0;
    if (count==0)
    {
        led_message("Starting FW update");
        count++;
    }
    if (0 == offset)
    {
        err = flash_img_init(&flash_context);
        if (err)
        {
            LOG_ERR("Failed to init flash write");
            return;
        }
        clear_led_progress_bar();
    }

    check_progress((uint32_t)offset);

    err = flash_img_buffered_write(&flash_context, data, len, is_last);
    if (err)
    {
        LOG_ERR("Failed to write to flash: %d", err);
        return;
    }

    if (is_last)
    {
        err = boot_request_upgrade(BOOT_UPGRADE_PERMANENT);
        if (err)
        {
            LOG_ERR("Failed to request upgrade");
            return;
        }

        LOG_INF("Rebooting to apply upgrade");

#if IS_ENABLED(CONFIG_LOG)
        while (log_process())
        {
        }
#endif
        led_message("Rebooting");
        k_sleep(K_SECONDS(3));

        sys_reboot(SYS_REBOOT_WARM);
    }
}
</pre> At the end of the OTA process, we accept the last block and then kick off a worker to reboot the processor. This also gives us time to send one last message to the user with the LED matrix. 
<h2 id="try-it-out-yourself">Try it out yourself</h2>
<p>While the Tikk board itself is not available, you can try out OTA on your own Bluetooth devices right now. Grab one of the supported Gateway dev boards, upload the binary, program one of the supported Bluetooth nodes with a sample, and kick off an OTA within your cohort. You’ll see the device pulling down blocks and rebooting, ready to face the world with a shiny new version of firmware.
[1]: <a href="https://golioth.io/connectivity">https://golioth.io/connectivity</a>
[2]: <a href="https://github.com/golioth/pouch">https://github.com/golioth/pouch</a>
[3]: <a href="https://blog.golioth.io/bluetooth-open-beta/">https://blog.golioth.io/bluetooth-open-beta/</a>
[4]: <a href="https://github.com/golioth/bluetooth-gateway">https://github.com/golioth/bluetooth-gateway</a>
[5]: <a href="https://github.com/golioth/bluetooth-gateway/releases/tag/v0.1.0">https://github.com/golioth/bluetooth-gateway/releases/tag/v0.1.0</a>
[6]: <a href="https://github.com/golioth/pouch/tree/main/examples/ble_gatt">https://github.com/golioth/pouch/tree/main/examples/ble_gatt</a>
[7]: <a href="https://blog.golioth.io/open-sourcing-the-tikk-demo-board/">https://blog.golioth.io/open-sourcing-the-tikk-demo-board/</a></p>
]]></content:encoded></item><item><title>How to Use Golioth Blockwise Stream to Upload Large IoT Data Payloads</title><link>https://blog.golioth.io/how-to-use-golioth-blockwise-stream-to-upload-large-iot-data-payloads/</link><guid>https://blog.golioth.io/how-to-use-golioth-blockwise-stream-to-upload-large-iot-data-payloads/</guid><pubDate>Thu, 02 Oct 2025 15:45:40 +0000</pubDate><dc:creator>Mike Szczys</dc:creator><category>Data Management</category><category>SDK</category><description>Pretty much everyone that uses Golioth needs to send data from their IoT devices to the cloud. Most often this is sensor readings. But it doesn’t matter to us what the data is or where you need it to go. Golioth makes it easy to collect data from your IoT fleet and route that data different places based on a path supplied by the device along with configuration you establish in the cloud (pipelines). For some of our customers, this data is quite large. Things like batch data consisting of many sensor readings along with timestamps, or just large payloads like images, audio, or video. Golioth handles large payloads in a way that is sensitive to constrained devices’ limitations. Those devices need to limit how much memory is used during the transmission. Today we’ll walk through how to use Golioth’s blockwise stream to send data from a device to the cloud. Today’s post is for your first adventures with blockwise stream and we’ll only be using a synchronous API. However, an upcoming post will cover the more advances asynchronous API that is also available.</description><content:encoded><![CDATA[<p>Pretty much everyone that uses Golioth needs to send data from their IoT devices to the cloud. Most often this is sensor readings. But it doesn’t matter to us what the data is or where you need it to go. Golioth makes it easy to collect data from your IoT fleet and route that data different places based on a path supplied by the device along with configuration you establish in the cloud (pipelines). For some of our customers, this data is quite large. Things like batch data consisting of many sensor readings along with timestamps, or just large payloads like images, audio, or video. Golioth handles large payloads in a way that is sensitive to constrained devices’ limitations. Those devices need to limit how much memory is used during the transmission. Today we’ll walk through how to use Golioth’s blockwise stream to send data from a device to the cloud. Today’s post is for your first adventures with blockwise stream and we’ll only be using a synchronous API. However, an upcoming post will cover the more advances asynchronous API that is also available.</p>
<h2 id="streams-and-pipelines-oh-my">Streams and Pipelines, Oh My</h2>
<p>When we refer to
<a href="https://docs.golioth.io/data-routing#streaming-data">Stream</a> we’re talking about a cloud service that receives data from devices in an IoT fleet. Sensor readings are a prime example: these are data packets sampled at different points in time that get pushed to the cloud. Golioth uses a concept called <a href="https://docs.golioth.io/data-routing">Pipelines</a> to transform and route this data. You must have a pipeline set up on <a href="https://console.golioth.io">the Golioth cloud</a> that indicates what you want to happen with your data. For today, we’re going to use a Pipeline that listens on all paths for JSON-formatted data and routes it to Golioth’s own LightDB stream destination. But you can just as easily send CBOR or raw binary data and route it to GCP, Azure, AWS, your own server using webhooks, or a number of other available <a href="https://docs.golioth.io/data-routing/destinations/">destinations</a>. To follow along with this guide, Make sure you have the <a href="https://docs.golioth.io/data-routing/examples/legacy-lightdb-stream">Legacy LightDB Stream JSON</a> pipeline example enabled on your Golioth project. If you don’t you can <a href="https://console.golioth.io/pipeline?name=Legacy%20LightDB%20Stream%20JSON&amp;pipeline=ZmlsdGVyOgogIHBhdGg6ICIqIgogIGNvbnRlbnRfdHlwZTogYXBwbGljYXRpb24vanNvbgpzdGVwczoKICAtIG5hbWU6IHN0ZXAtMAogICAgZGVzdGluYXRpb246CiAgICAgIHR5cGU6IGJhdGNoCiAgICAgIHZlcnNpb246IHYxCiAgLSBuYW1lOiBzdGVwLTEKICAgIHRyYW5zZm9ybWVyOgogICAgICB0eXBlOiBleHRyYWN0LXRpbWVzdGFtcAogICAgICB2ZXJzaW9uOiB2MQogIC0gbmFtZTogc3RlcC0yCiAgICB0cmFuc2Zvcm1lcjoKICAgICAgdHlwZTogaW5qZWN0LXBhdGgKICAgICAgdmVyc2lvbjogdjEKICAgIGRlc3RpbmF0aW9uOgogICAgICB0eXBlOiBsaWdodGRiLXN0cmVhbQogICAgICB2ZXJzaW9uOiB2MQ==">click this link to automatically add it</a>.</p>
<h2 id="firmware-concepts-for-blockwise-stream">Firmware Concepts for Blockwise Stream</h2>
<p>Microcontrollers have RAM limitations that make it difficult to hold the entire data set in memory in order to stream it over your network connection. This is where the “blockwise” part comes in. Golioth uses the CoAP protocol for securely transferring data to and from the cloud. The protocol has a payload limit of 1024 bytes per packet. The Golioth Firmware SDK implements an API that accepts 1024-byte blocks of data from your application, sending each to the cloud until the entire payload has been transferred. Your application needs to supply the data to the SDK using the following concepts:</p>
<ul>
<li><strong>block index:</strong> the number of the current block in the overall transfer, beginning with 0.</li>
<li><strong>block size:</strong> the maximum size of a block (configurable with a default of 1024).</li>
<li><strong>data offset:</strong> the offset from the beginning of your data set from which to read the next block of memory. Your callback needs to calculate this by multiply the block index by the block size.</li>
<li><strong>buffer length:</strong> the length of the data (in bytes) supplied by your callback. This will be the same as the block size, except when sending the last block which may be smaller.</li>
<li><strong>last block:</strong> a boolean value your callback sets to indicate all data has been sent Let’s apply these concepts in a practical firmware example.</li>
</ul>
<h2 id="blockwise-stream-firmware-example">Blockwise Stream Firmware Example</h2>
<p>We want an example that’s easy to set up and easy to see when the data has arrived on the cloud. For this reason we’ll use some hardcoded JSON data. It’s human-readable, and we’ll send it to Golioth’s internal LightDB destination because you can view received data directly on your project page.</p>
<pre class="EnlighterJSRAW" data-enlighter-language="c">/* Sample text source: Mind Volume LIX, Issue 236 https://academic.oup.com/mind/article-abstract/LIX/236/433/986238 */

static const char json_data[] = "{\"my-quote\":\"1. The Imitation Game\\nI propose to consider the question, ‘Can machines think?’ This should begin with definitions of the meaning of the terms ‘machine’ and ‘think’. The definitions might be framed so as to reflect so far as possible the normal use of the words, but this attitude is dangerous. If the meaning of the words ‘machine’ and ‘think’ are to be found by examining how they are commonly used it is difficult to escape the conclusion that the meaning and the answer to the question, ‘Can machines think?’ is to be sought in a statistical survey such as a Gallup poll. But this is absurd. Instead of attempting such a definition I shall replace the question by another, which is closely related to it and is expressed in relatively unambiguous words.\\nThe new form of the problem can be described in terms of a game which we call the ‘imitation game’. It is played with three people, a man (A), a woman (B), and an interrogator (C) who may be of either sex. The interrogator stays in a room apart from the other two. The object of the game for the interrogator is to determine which of the other two is the man and which is the woman. He knows them by labels X and Y, and at the end of the game he says either ‘X is A and Y is B’ or ‘X is B and Y is A’. The interrogator is allowed to put questions to A and B thus\"}";

static size_t json_data_len = strlen(json_data);</pre> I’m using some simple example data that consists of a key-value pair in JSON format. The important thing is that the JSON itself is valid (otherwise it will be dropped by the server) and that the length of the data is correct. 
<h2 id="blockwise-async-callback-and-user-arguments">Blockwise Async Callback and User Arguments</h2>
<p>We will call a
<strong>synchronous</strong> API but the firmware SDK uses <strong>an asynchronous callback</strong> to get each block of data from your application. This means that the API call will block your application program flow until all data has been sent. This makes it really easy to keep the data in scope, but we need to make both the data pointer and the data length available in the async block read callback. You can do this with global variables but instead let’s pass a pointer as the callback argument. When the callback runs, it can cast the <code>arg</code> pointer to the correct type and access the data within.</p>
<pre class="EnlighterJSRAW" data-enlighter-language="c">struct block_stream_container {
    uint8_t *data;
    size_t data_len;
};

enum golioth_status stream_read_block(uint32_t block_idx,
                                      uint8_t *block_buffer,
                                      size_t *block_size,
                                      bool *is_last,
                                      void *arg)
{
    if (NULL == arg)
    {
        LOG_ERR("Expected block_stream_container pointer as arg but got NULL");
        return GOLIOTH_ERR_NULL;
    }
    struct block_stream_container *container = arg;

    uint32_t offset = block_idx * *block_size;

    /* Test to see if this is the final block of data we need to send */
    if (container->data_len <= offset + *block_size)
    {
        /* Overwrite the block_size that was passed in with number of bytes in last block */
        *block_size = container->data_len - offset;
        /* Indicate to the Golioth Firmware SDK that this is the final block */
        *is_last = true;
    }

    /* Copy our data into the supplied buffer */
    memcpy(block_buffer, container->data + offset, *block_size);

    LOG_INF("Sending block %u of %zu bytes.%s",
            block_idx,
            *block_size,
            *is_last ? " Final block." : "");

    return GOLIOTH_OK;
}
</pre> After testing to ensure the 
<p><code>arg</code> is not <code>NULL</code> it is cast to the <code>block_stream_container</code> we defined as part of the application. Now we can begin calculating the information we need to access the data. The Golioth Firmware API supplied the parameters we need to target the next block of data. First, we can use <code>block_idx</code> and <code>block_size</code> to calculate the offset from the start of the data. This offset is where we begin reading after the start of the data set we are trying to upload. Next, we need to know if there is enough data remaining to send a full block. Since the SDK has told us it wants <code>block_size</code> bytes of data, we first check to see if there’s more than that amount remaining (if so, this is not the last block). When we get to the end of the transfer, we will have a smaller block size (or exactly the block size and no more). In this case we need to set the <code>is_last</code> flag, and set the <code>block_size</code> to the actual amount of data for this final block. With our calculations finished, we need to copy the data into the <code>block_buffer</code> supplied by the SDK. Return <code>GOLIOTH_OK</code> to show the data has been copied and is ready to upload. If there is a problem and you want to cancel the upload, return any other error code.</p>
<h2 id="initiating-a-blockwise-stream">Initiating a Blockwise Stream</h2>
<p>We’ve already done all the hard work in our callback. Initiating the transfer is rather simple.</p>
<pre class="EnlighterJSRAW" data-enlighter-language="c">static void send_blockwise_stream(void)
{
    struct block_stream_container container;

    container.data = (uint8_t *)json_data;
    container.data_len = json_data_len;

    enum golioth_status status = golioth_stream_set_blockwise_sync(client,
                                                                   "my-test-path",
                                                                   GOLIOTH_CONTENT_TYPE_JSON,
                                                                   stream_read_block,
                                                                   &container);
    if (GOLIOTH_OK != status)
    {
        LOG_ERR("Failed to send blockwise stream: %d", status);
        return;
    }

    LOG_INF("Blockwise data successfully sent.");
}</pre> First we create the container struct we’re using to pass information to the callback. This just gets a pointer to the data and a total length. The API call then supplies the path to which we are sending the data; this may be anything you want but remember that the Golioth pipelines you have set up in the web console will key on this path. We also set the type to JSON, pass the callback function, and the address of our container struct which will be available in the callback. This function will block until the send completes or there is an error (ensuring that our container struct remains in scope). Check the status code returned by this function to see if the operation was a success. Once complete, you may let the data go out of scope, the cloud already has it! 
<h2 id="putting-it-all-together">Putting it all together</h2>
<p>You can run the example code for yourself.
<a href="https://github.com/golioth/golioth-firmware-sdk/tree/main/examples/zephyr/stream">Start with the stream sample</a> from the Golioth Firmware SDK, replacing the <code>main.c</code> file with the one below. Build, flash, and provision the code following <a href="https://github.com/golioth/golioth-firmware-sdk/tree/main/examples/zephyr/stream#building-and-running">the README</a> in the SDK.</p>
<pre class="EnlighterJSRAW" data-enlighter-language="c" data-enlighter-group="1" data-enlighter-title="examples/zephyr/stream/src/main.c">/*
 * Copyright (c) 2025 Golioth, Inc.
 *
 * SPDX-License-Identifier: Apache-2.0
 */

#include <zephyr/logging/log.h>
LOG_MODULE_REGISTER(golioth_stream, LOG_LEVEL_DBG);

#include <golioth/client.h>
#include <golioth/stream.h>
#include <samples/common/sample_credentials.h>
#include <samples/common/net_connect.h>
#include <stdlib.h>
#include <string.h>
#include <zephyr/kernel.h>

static const char json_data[] = "{\"my-quote\":\"1. The Imitation Game\\nI propose to consider the question, ‘Can machines think?’ This should begin with definitions of the meaning of the terms ‘machine’ and ‘think’. The definitions might be framed so as to reflect so far as possible the normal use of the words, but this attitude is dangerous. If the meaning of the words ‘machine’ and ‘think’ are to be found by examining how they are commonly used it is difficult to escape the conclusion that the meaning and the answer to the question, ‘Can machines think?’ is to be sought in a statistical survey such as a Gallup poll. But this is absurd. Instead of attempting such a definition I shall replace the question by another, which is closely related to it and is expressed in relatively unambiguous words.\\nThe new form of the problem can be described in terms of a game which we call the ‘imitation game’. It is played with three people, a man (A), a woman (B), and an interrogator (C) who may be of either sex. The interrogator stays in a room apart from the other two. The object of the game for the interrogator is to determine which of the other two is the man and which is the woman. He knows them by labels X and Y, and at the end of the game he says either ‘X is A and Y is B’ or ‘X is B and Y is A’. The interrogator is allowed to put questions to A and B thus\"}";

static size_t json_data_len = strlen(json_data);

struct golioth_client *client;
static K_SEM_DEFINE(connected, 0, 1);

static void on_client_event(struct golioth_client *client,
                            enum golioth_client_event event,
                            void *arg)
{
    bool is_connected = (event == GOLIOTH_CLIENT_EVENT_CONNECTED);
    if (is_connected)
    {
        k_sem_give(&connected);
    }
    LOG_INF("Golioth client %s", is_connected ? "connected" : "disconnected");
}

struct block_stream_container {
    uint8_t *data;
    size_t data_len;
};

enum golioth_status stream_read_block(uint32_t block_idx,
                                      uint8_t *block_buffer,
                                      size_t *block_size,
                                      bool *is_last,
                                      void *arg)
{
    if (NULL == arg)
    {
        LOG_ERR("Expected block_stream_container pointer as arg but got NULL");
        return GOLIOTH_ERR_NULL;
    }
    struct block_stream_container *container = arg;

    uint32_t offset = block_idx * *block_size;

    /* Test to see if this is the final block of data we need to send */
    if (container->data_len <= offset + *block_size)
    {
        /* Overwrite the block_size that was passed in with number of bytes in last block */
        *block_size = container->data_len - offset;
        /* Indicate to the Golioth Firmware SDK that this is the final block */
        *is_last = true;
    }

    /* Copy our data into the supplied buffer */
    memcpy(block_buffer, container->data + offset, *block_size);

    LOG_INF("Sending block %u of %zu bytes.%s",
            block_idx,
            *block_size,
            *is_last ? " Final block." : "");

    return GOLIOTH_OK;
}

static void send_blockwise_stream(void)
{
    struct block_stream_container container;

    container.data = (uint8_t *)json_data;
    container.data_len = json_data_len;

    enum golioth_status status = golioth_stream_set_blockwise_sync(client,
                                                                   "my-test-path",
                                                                   GOLIOTH_CONTENT_TYPE_JSON,
                                                                   stream_read_block,
                                                                   &container);
    if (GOLIOTH_OK != status)
    {
        LOG_ERR("Failed to send blockwise stream: %d", status);
        return;
    }

    LOG_INF("Blockwise data successfully sent.");
}

int main(void)
{
    LOG_DBG("Start Golioth blockwise stream sample");

    net_connect();

    /* Note: In production, credentials should be saved in secure storage. For
     * simplicity, we provide a utility that stores credentials as plaintext
     * settings.
     */
    const struct golioth_client_config *client_config = golioth_sample_credentials_get();

    client = golioth_client_create(client_config);
    golioth_client_register_event_callback(client, on_client_event, NULL);

    k_sem_take(&connected, K_FOREVER);

    /* Give network connection logs a few seconds to clear */
    k_msleep(2000);

    send_blockwise_stream();

    return 0;
}</pre> When monitoring the device output via a serial connection, you will see the log messages we added for each of the blocks: 
<pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-linenumbers="false">*** Booting Zephyr OS build v4.1.0 ***
*** Golioth Firmware SDK v0.20.0 ***
[00:45:26.985,074] <inf> golioth_settings_autoload: Initializing settings subsystem
[00:45:26.986,658] <inf> fs_nvs: 8 Sectors of 4096 bytes
[00:45:26.986,675] <inf> fs_nvs: alloc wra: 0, fa0
[00:45:26.986,679] <inf> fs_nvs: data wra: 0, b6
[00:45:26.986,715] <inf> golioth_settings_autoload: Loading settings
[00:45:26.986,912] <dbg> golioth_stream: main: Start Golioth blockwise stream sample
[00:45:26.986,929] <inf> golioth_samples: Starting DHCP to obtain IP address
[00:45:26.987,001] <inf> golioth_samples: Waiting to obtain IP address
[00:45:35.004,339] <inf> net_dhcpv4: Received: 192.168.1.170
[00:45:35.004,816] <inf> golioth_mbox: Mbox created, bufsize: 1320, num_items: 10, item_size: 120
[00:45:35.269,926] <inf> golioth_coap_client_zephyr: Golioth CoAP client connected
[00:45:35.270,055] <inf> golioth_stream: Golioth client connected
[00:45:35.270,191] <inf> golioth_coap_client_zephyr: Entering CoAP I/O loop
[00:45:37.270,335] <inf> golioth_stream: Sending block 0 of 1024 bytes.
[00:45:37.322,012] <inf> golioth_stream: Sending block 1 of 349 bytes. Final block.
[00:45:37.407,104] <inf> golioth_stream: Blockwise data successfully sent.
</pre> Once your device connects to Golioth, you can view the received stream data in the LightDB Stream tab of the device page. 
<h2 id="web-console-showing-a-json-data-structure-that-contains-going-deeper-with-blockwise-stream-blockwise-stream-is-a-tool-you-should-be-using-because-it-unlocks-the-ability-to-cache-data-locally-into-larger-batches-for-upload-at-a-convenient-time-for-instance-devices-operating-on-battery-power-may-opt-to-only-upload-data-above-a-certain-charge-and-ambulatory-devices-may-only-have-a-network-connection-occasionally-golioths-data-routing-can-separate-readings"><a href="https://blog.golioth.io/wp-content/uploads/2025/09/blockwise-stream-example.png"><img src="https://blog.golioth.io/wp-content/uploads/2025/09/blockwise-stream-example-1030x757.png" alt="Web console showing a JSON data structure that contains &ldquo;my-test-path&rdquo; and &ldquo;my-quote&rdquo; followed by a long quote from Alan Turing"></a>Going Deeper with Blockwise Stream Blockwise stream is a tool you should be using because it unlocks the ability to cache data locally into larger batches for upload at a convenient time. For instance, devices operating on battery power may opt to only upload data above a certain charge, and ambulatory devices may only have a network connection occasionally. Golioth’s data routing can separate readings</h2>
<p><a href="https://docs.golioth.io/data-routing/destinations/batch">using the batch destination</a> and use <a href="https://docs.golioth.io/data-routing/transformers/extract-timestamp">the extract-timestamp transform</a> to parse timestamps embedded in the data for accurate accounting of each reading. While the example above is adequate for understanding how these block uploads work, in most cases you will not want to block program flow while uploading data. In an upcoming post we’ll cover using the asynchronous API, dynamic memory handling, and reading data from storage when using the blockwise stream feature.</p>
]]></content:encoded></item><item><title>Golioth Joins STMicroelectronics Partner Program</title><link>https://blog.golioth.io/golioth-joins-stmicroelectronics-partner-program/</link><guid>https://blog.golioth.io/golioth-joins-stmicroelectronics-partner-program/</guid><pubDate>Thu, 25 Sep 2025 14:57:07 +0000</pubDate><dc:creator>Jonathan Beri</dc:creator><category>News</category><category>Partners</category><description>We’re excited to announce that Golioth is now an official authorized partner of STMicroelectronics. ST is one of the leading developer platforms for professional embedded engineers. Their STM32 family powers everything from industrial automation to consumer electronics, and is a staple for teams building production-ready IoT devices. Partnering with ST means tighter collaboration and more tools in your toolbox to go from proof of concept to production smoothly.</description><content:encoded><![CDATA[<p>We’re excited to announce that Golioth is now an official authorized partner of <a href="https://www.st.com/en/partner-products-and-services/golioth-iot-infrastructure-platform.html">STMicroelectronics</a>. ST is one of the leading developer platforms for professional embedded engineers. Their STM32 family powers everything from industrial automation to consumer electronics, and is a staple for teams building production-ready IoT devices. Partnering with ST means tighter collaboration and more tools in your toolbox to go from proof of concept to production smoothly.</p>
<h2 id="what-this-means-today-bluetooth--connectivity">What this means today: Bluetooth + Connectivity</h2>
<p>One of the immediate wins from this partnership is the enhanced support for Bluetooth connectivity via ST hardware under our
<a href="https://golioth.io/connectivity">Golioth Connectivity</a> offering. Here’s how Golioth enhances Bluetooth devices:</p>
<ul>
<li><strong>One platform, every protocol.</strong> You use the same APIs, dashboards, monitoring, OTA, device settings, data streaming regardless of whether your device is Bluetooth, cellular, Wi-Fi, or anything else.</li>
<li><strong>Secure provisioning &amp; built-in encryption.</strong> Critical for low-power or battery-constrained devices using Bluetooth, we’ve baked in security from the start. Zero Touch Provisioning (ZTP) makes Golioth and easy decision on the production line.</li>
<li><strong>OTA over BLE.</strong> Firmware and software updates over Bluetooth LE, keeping devices up to date, complying with new regulations like the <a href="https://blog.golioth.io/an-introduction-to-the-cyber-resilience-act-cra-with-kate-stewart-of-the-zephyr-project/">Cyber Resilience Act (CRA)</a>, and reducing field maintenance.</li>
<li><strong>Gateway‑based architecture for Bluetooth.</strong> Constrained Bluetooth devices use a gateway to relay to the cloud to access the same services as directly connected devices. This can be a microcontroller-based gateway, a Linux based gateway, or a mobile phone app acting as a gateway.</li>
<li><strong>Unified dashboard.</strong> Manage all your devices from one place: Bluetooth, cellular, Wi-Fi, Thread, Ethernet. Same tooling. Same dashboard view. Less context switching. We already have demos showing ST’s hardware connecting</li>
</ul>
<p><a href="https://blog.golioth.io/connecting-bluetooth-iot-devices-to-the-internet-stm32wb5mm-dk/">Bluetooth IoT devices via the STM32WB5MM-DK</a>, using Bluetooth. This gives you a head start in building low‑power wireless devices on ST chips, with cloud connectivity handled via Golioth.</p>
<h2 id="ai-st-hardware--whats-coming">AI, ST Hardware &amp; What’s Coming</h2>
<p>On top of connectivity and Bluetooth, we’re pushing forward with
<a href="https://golioth.io/ai">Golioth AI Orchestration</a> — enabling you to deploy, manage, and update edge‑AI workloads across fleets. A great example: the STM32N6 series, featured in the <a href="https://www.kickstarter.com/projects/openmv/openmv-n6-and-ae3-low-power-python-programmable-ai-cameras">OpenMV N6 cameras</a> — python‑programmable, low‑power AI cameras. Together with Golioth, devices like these can stream ML inferences, receive model updates, and be managed securely from the cloud.</p>
<h2 id="what-to-expect-next">What to Expect Next</h2>
<p>From here, we’ll be focusing on:</p>
<ul>
<li>Deeper integration with ST’s development tools (e.g. SDKs, tools, references.)</li>
<li>Continued enhancements to Golioth Connectivity for Bluetooth—faster onboarding, more gateway options, tighter security.</li>
<li>Extending AI Orchestration support (models, pipelines) for ST’s edge‑AI capable chips. We’re really excited about this</li>
</ul>
<p><a href="https://golioth.io/integrations/st">partnership</a>. Together with ST, and with robust Bluetooth + AI orchestration support, we want to make building connected, smart, and reliable devices easier than ever. We can’t wait to see what you build.</p>
]]></content:encoded></item><item><title>'Bluetooth Gateways in the Field: The Globalscale GTI-RW612'</title><link>https://blog.golioth.io/bluetooth-gateways-in-the-field-the-globalscale-gti-rw612/</link><guid>https://blog.golioth.io/bluetooth-gateways-in-the-field-the-globalscale-gti-rw612/</guid><pubDate>Tue, 23 Sep 2025 16:17:22 +0000</pubDate><dc:creator>Chris Gammell</dc:creator><category>Bluetooth</category><category>Gateways</category><description>Golioth Connectivity enables developers to connect Bluetooth devices to the cloud using Gateways, including microcontroller based gateways. Today we’re discussing the Globalscale GTI-RW612 gateway. It’s an off-the-shelf piece of hardware normally marketed at the Matter ecosystem, but has been repurposed for Golioth Connectivity. We’ll show how we got it working sending Bluetooth device data to the Cloud and discuss the application spaces where this gateway might be a good fit.</description><content:encoded><![CDATA[<p>Golioth Connectivity enables developers to <a href="https://blog.golioth.io/bluetooth-open-beta/">connect Bluetooth devices to the cloud using Gateways</a>, including microcontroller based gateways. Today we’re discussing <a href="https://globalscaletechnologies.com/product/gti-rw612-matter-gateway/">the Globalscale GTI-RW612 gateway</a>. It’s an off-the-shelf piece of hardware normally marketed at the Matter ecosystem, but has been repurposed for Golioth Connectivity. We’ll show how we got it working sending Bluetooth device data to the Cloud and discuss the application spaces where this gateway might be a good fit. <div style="position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden;">
      <iframe allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share; fullscreen" loading="eager" referrerpolicy="strict-origin-when-cross-origin" src="https://www.youtube.com/embed/HoR7nbXQ8NU?autoplay=0&amp;controls=1&amp;end=0&amp;loop=0&amp;mute=0&amp;start=0" style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; border:0;" title="YouTube video"></iframe>
    </div>
</p>
<h2 id="about-the-gti-rw612-and-nxp-rw612-chipset">About the GTI-RW612 and NXP RW612 Chipset</h2>
<p>We are big fans of NXP parts,
<a href="https://www.nxp.com/company/about-nxp/smarter-world-blog/BL-NEW-RW61X">including the tri-band radio on the RW612</a>. We have <a href="https://github.com/golioth/bluetooth-gateway">precompiled Bluetooth gateway binaries for the FRDM-RW612</a>, which is an affordable dev board from NXP and a super easy way to get started with Golioth Connectivity. You can route Pouch-based Bluetooth packets back to the cloud using either Ethernet or Wi-Fi. This chip is targeted at the Thread / Matter ecosystem as a Border Gateway all-in-one chip, and it hits the mark. Each of the radios have a dedicated memory space and there is an additional Ethernet interface to enable wired backhaul. The main application processor is built with a core of a Cortex M33, but running at a whopping 260 MHz! There is also a PowerQuad coprocessor, but honestly we haven’t scratched the surface at utilizing all of the capabilities on this chip yet.<img src="https://www.nxp.com/assets/images/en/block-diagrams/RW612-MAIN-SUBSYSTEMS-BD.png" alt=""> Globalscale took this part and created their own in-house module, <a href="https://globalscaletechnologies.com/product/gti-rw612b-lga-module/">the GTI-RW612B</a>. Though the name is a bit confusing (similar to the name of the gateway that employs that module), the idea is clear: this is a module that enables precertified access to a capable chipset. <a href="https://globalscaletechnologies.com/product/gti-rw612-matter-gateway/">The GTI-RW612 is the gateway itself</a>, targeted at the Matter market as a border router. The unit comes with prebuilt firmware that is not source-available. It is based on MCUxpresso (another NXP specific firmware ecosystem), if you’re hoping to fire up the unit and start routing Matter traffic. Instead, we reprogrammed it using Zephyr and started digging into the peripherals to enable this as a Bluetooth gateway. Note that Golioth works great as a Thread enablement platform as well, so we could <em>also</em> use this as a Thread Border Router connected to Golioth…but that’s probably a blog post for another day.</p>
<h2 id="microcontroller-based-gateways">Microcontroller Based Gateways</h2>
<p>We have covered a different microcontroller based gateway in the past,
<a href="https://blog.golioth.io/bluetooth-gateways-in-the-field-the-ezurio-sentrius-mg100/">the Sentrius MG100 from Ezurio</a>. That has a Bluetooth main controller (nRF52840) and an attached cellular modem (Sierra Wireless HL7800). The enclosure is water resistant and works with Cat-M1 and NB-IOT, so it’s a good option for industrial use cases. The GTI Why use a microcontroller vs a larger Linux based gateway? One reason is that the gateway itself is a device on the Golioth platform, or <a href="https://blog.golioth.io/the-taxonomy-of-connected-device-networks/">as we call it, a “managed device”</a>. The primary benefit is that you can utilize our OTA firmware update service for the gateway, in addition to the nodes that are connecting over Bluetooth through that gateway. But it is also easy to add features to the gateway, such as time-series data and logging. We did that with <a href="https://blog.golioth.io/creating-an-off-grid-solar-powered-golioth-connectivity-bluetooth-gateway/">our internal platform called the Aludel Elixir, adding solar panel measurement</a>. To bring the point home and contrast it with Thread, a Border Router is often invisible to the Golioth platform, as it is routing packets like a Wi-Fi router might (a benefit in the days before Pouch).</p>
<h2 id="getting-the-gateway-working">Getting the gateway working</h2>
<p>The gateway showed up from the manufacturer effectively without firmware; or at least without firmware that we could use in our scenario. Instead, Mike had to bring up the board using
<a href="https://github.co">our <code>bluetooth-gateway</code> repository</a>, which is built on top of Zephyr. We do have the FRDM-RW612 supported in that repository, but there were a few key differences.</p>
<ul>
<li>The module had a different flash part than the FRDM-RW612, but it did match the in-tree board called <code>rd_rw612_bga/rw612/ethernet</code></li>
<li>The bootloader required adding a button and LED assignment</li>
<li>The LCD needed to be defined in the overlay file</li>
<li>LVGL needed to be added as a library in the manifest And most importantly, we needed to add a splash screen with the Golioth Logo:</li>
</ul>
<p><img src="https://blog.golioth.io/wp-content/uploads/2025/09/GTI-RW612-Angle-1030x773.jpg" alt=""> Since the device is not meant to be opened by the end user, Mike enabled MCUboot so that new firmware can be loaded over the USB port using smpmgr. The factory would need to program the initial bootloader but then future firmware updates could be done over USB or more likely via Golioth’s OTA firmware update service.</p>
<h2 id="application-spaces">Application Spaces</h2>
<p>This device is a great fit in an office or light industrial use case. Personally I love an Ethernet connection to not need to set up the WiFi connection, but both are equally capable. This could be a great fit for simple asset tracking using Bluetooth, which we will be highlighting on the blog soon. This works for locations like:</p>
<ul>
<li>Hospitals</li>
<li>Factory floors</li>
<li>Warehouse spaces</li>
<li>Offices We have other gateways we are looking at enabling on Golioth Connectivity, but we want to hear about what you need for your next Bluetooth application. Let us know on the forum or send an email to</li>
</ul>
<p><a href="mailto:devrel@golioth.io">devrel@golioth.io</a>. We can’t wait to see what you build!</p>
]]></content:encoded></item><item><title>Signed URLs for Embedded Devices</title><link>https://blog.golioth.io/signed-urls-for-embedded-devices/</link><guid>https://blog.golioth.io/signed-urls-for-embedded-devices/</guid><pubDate>Tue, 16 Sep 2025 14:26:46 +0000</pubDate><dc:creator>Dan Mangum</dc:creator><category>Data Management</category><category>Device Management</category><category>Platform</category><category>Security</category><description>Today we are launching support for device signed URLs, which is now available for Golioth projects in Teams or Enterprise tier organizations. The feature is off by default, but can be enabled on the settings page for a project. We are also releasing signy, an open source firmware library for generating signed URLs on embedded devices.</description><content:encoded><![CDATA[<p>Today we are launching support for device signed URLs, which is now available for Golioth projects in Teams or Enterprise tier organizations. The feature is off by default, but can be enabled on the <a href="https://console.golioth.io/project-settings">settings page</a> for a project. We are also releasing <code>signy</code>, an <a href="https://github.com/golioth/signy">open source firmware library</a> for generating signed URLs on embedded devices.</p>
<h2 id="signed-urls-background">Signed URLs Background</h2>
<p>Signed URLs are typically used on the web to grant time-limited access to private resources. The most common use is with Content Delivery Networks (CDNs), where a client may request a large private media asset from an origin server that is geographically far away, then be redirected to a closer CDN server with a signed URL to speed up the download of the asset. In order to avoid complex authentication and authorization logic across a large, geographically distributed CDN, the origin server can determine whether the client can access the asset, then generate a signed URL that the CDN server is able to verify as issued by the origin. When the client uses the signed URL, the CDN server verifies the signature in the URL, and ensures that the URL is not being used outside of any time restrictions included in URL parameters.</p>
<h2 id="bringing-signed-urls-to-embedded-devices">Bringing Signed URLs to Embedded Devices</h2>
<p>While device signed URLs use a similar technique to traditional signed URLs, the device, which would usually be thought of as a client, is acting more like an origin server. But if the device is the origin server, then who is the client? To be more precise, a component on a device is acting like an origin server, and the client may be another component on the same device, or an external system that is communicating with the device. One motivating use case for this functionality is embedded devices with multiple communication interfaces. For example, many devices that connect to the Golioth platform leverage both cellular and Wi-Fi, with a separate microcontroller (MCU) managing each. Most modern MCUs support the ability to leverage a secure processing environment (SPE) for private key storage and cryptographic operations. The network stack running on the MCU is typically able to interact with the SPE in order to securely connect to an external server using (D)TLS or some other secure transport. However, the device manufacturer is responsible for generating private keys in the SPE and issuing certificates from their PKI during initial provisioning. If there are multiple components that need credentials to communicate over the network, the provisioning process and ongoing credential management becomes more complex. Furthermore, it may be the case that one component lacks sophisticated security features that are present on the other.
<img src="https://blog.golioth.io/wp-content/uploads/2025/09/device_signed_urls_multiple_mcu_multiple_credentials.png" alt="Diagram of Multi-Protocol Device with Credentials on Each MCU">   With device signed URLs, a single component on a device can be provisioned with credentials, and can use its private key to issue time-limited signed URLs that may be distributed to other components over local communication interfaces (e.g. UART, SPI, etc.). Doing so grants the less secure component narrowly scoped access to a resource without transmitting any long-lived credentials.<img src="https://blog.golioth.io/wp-content/uploads/2025/09/device_signed_urls_multiple_mcu_single_credentials.png" alt="Diagram of Multi-Protocol Device with Multiple MCUs but a single set of credentials."></p>
<h2 id="making-device-signed-urls-easy-with-signy">Making Device Signed URLs Easy with <code>signy</code></h2>
<p>Generating signed URLs on embedded devices can be complex, which is why we have also built <code>signy</code>. <code>signy</code> takes care of crafting signed URLs in the format supported by Golioth, and makes use of the <a href="https://arm-software.github.io/psa-api/crypto/">Arm Platform Security Architecture (PSA) Crypto API</a> for all signing operations. This ensures that private keys can be stored and accessed securely, and that any cryptographic acceleration support on underlying hardware can be leveraged.</p>
<pre class="EnlighterJSRAW" data-enlighter-language="c">int err = signy_init(priv_key, device_crt_der, device_crt_der_len);
if (err != 0)
{
    LOG_ERR("Failed to initialize signy: %d", err);
    return -1;
}

err = signy_sign_url(CONFIG_BASE_URL,
                     strlen(CONFIG_BASE_URL),
                     signed_url,
                     sizeof(signed_url),
                     &signed_url_len);
if (err != 0)
{
    LOG_ERR("Failed to sign URL: %d", err);
    return -1;
}</pre> The 
<p><code>signy</code> repository is also a <a href="https://docs.zephyrproject.org/latest/develop/modules.html">Zephyr module</a>, meaning that it can easily be pulled into existing Zephyr applications with minimal configuration.</p>
<h2 id="looking-forward">Looking Forward</h2>
<p>We are excited about the new use cases that device signed URLs enable. Outside of the multi-MCU scenario described in this post, device signed URLs can also be leveraged to allow other devices without Golioth credentials, such as a smartphone connected to a
<a href="https://docs.golioth.io/connectivity/protocols/bluetooth">Bluetooth Low Energy peripheral using Golioth Managed Connectivity</a>, to download assets. Stay tuned for future posts, and let us know how you are using device signed URLs on the <a href="https://forum.golioth.io/">forum</a>!</p>
]]></content:encoded></item><item><title>'Zephyr for Hardware Engineers: GDB Debugging'</title><link>https://blog.golioth.io/zephyr-for-hardware-engineers-gdb-debugging/</link><guid>https://blog.golioth.io/zephyr-for-hardware-engineers-gdb-debugging/</guid><pubDate>Thu, 11 Sep 2025 19:00:51 +0000</pubDate><dc:creator>Chris Gammell</dc:creator><category>Zephyr</category><description>GDB–and step debugging generally–is something that often feels unnecessary for my Zephyr development, until it very much is needed. It’s not that it’s difficult, but it’s not part of my normal workflow and printf (or actually printk and LOG_DBG in Zephyr) debugging fits my needs for most applications I’m creating. As a hardware engineer, I’m often leaning on in-tree drivers and protocol stacks that are already proven and working. Well, sometimes things don’t work. Today I’m going to go through how to utilize tools like west attach to fire up gdb and start digging into code, one step at a time.</description><content:encoded><![CDATA[<p>GDB–and step debugging generally–is something that often feels unnecessary for my Zephyr development, until it <em>very much</em> is needed. It’s not that it’s difficult, but it’s not part of my normal workflow and printf (or actually printk and LOG_DBG in Zephyr) debugging fits my needs for most applications I’m creating. As a hardware engineer, I’m often leaning on in-tree drivers and protocol stacks that are already proven and working. Well, sometimes things don’t work. Today I’m going to go through how to utilize tools like <code>west attach</code> to fire up gdb and start digging into code, one step at a time.</p>
<h2 id="digging-into-issues-with-drivers">Digging into issues with drivers</h2>
<p>I don’t know about you, but as a hardware engineer, I always assume the problem with code is…
<em>me</em>. So my first instinct isn’t necessarily to start digging under the hood of Zephyr so much as “let’s figure out what I configured wrong”. This results in lots of tweaking of configurations like devicetree and kconfig symbols. Sometimes it happens that I need to check if those configurations are even taking effect though. This happened recently with configuring the behavior of an accelerometer we have on a lot of our development boards. I wanted to set up “wake on movement” and I have been struggling. I will get into the details of that in a future post once things are working better using many of the techniques I describe below. My co-worker Mike sat down with me and helped me do some of this debugging, and was the inspiration for this article. Mike has written about <a href="https://blog.golioth.io/how-to-debug-mcuboot-and-why-i-needed-to/">using GDB on the bootloader before</a> (a more difficult task) but I realized we didn’t have an article about GDB on the application side of Zephyr. That’s likely because GDB is “obvious” to many engineers, but as this series of articles shows, “Zephyr for Hardware Engineers” is meant to empower folks who don’t dig under the hood of code that often.</p>
<h2 id="gdb-over-other-options">GDB over other options?</h2>
<p>My expectations around debugging are built from my early experiences with Eclipse-based tools offered from vendors:</p>
<ul>
<li>Pre-configured from the vendor, often set up for Windows</li>
<li><em>Lots</em> of buttons to click, but one with a bug and a play button</li>
<li>Double click next to the line I want to break on, start the debugger, wait for the view to change to ‘debug view’, and hope for the best Transitioning from that to a more command-line driven development flow, it kind of makes sense that I would stick to printk debugging: where do I click? There are a growing number of vendor-specific options for building on different vendor ecosystems with Zephyr and getting pre-configured VS code setups to work with those boards. It’s still Zephyr and it’s a good option, but I wanted to use this learning opportunity to dig in.</li>
</ul>
<h2 id="getting-started-with-west-and-gdb">Getting started with west and GDB</h2>
<p>We’re unabashed fans of the west meta tool that Zephyr provides because it pulls together so many individual tools like the build system and Python scripts that manage tooling like autocompletion and SDK installation. The key command we’ll use here is
<code>west attach</code>. After you build and flash the board (as we show how to do in <a href="https://training.golioth.io">our training pages</a>), you are ready to connect the debugger. Why not use <code>west debug</code>? Well, that’s fine too, but requires more setup. And to be honest, when I see all my smartest firmware coworkers using <code>attach</code>…I take the hint. As an actual explanation: <code>debug</code> reflashes the ELF each time, which can overwrite flash areas you didn’t intend (e.g. bootloader), whereas west attach is non-destructive. Instead, you can run <code>attach</code> and hook up GDB whenever you’d like. If you want to run from the reset vector, you can use a command like <code>monitor reset</code> from within GDB (note the <code>(gdb)</code> callouts to see where you’re executing commands from within GDB vs on the normal command line. That command resets the remote GDB server–which thanks to west, we’re not really thinking about here, but is part of the equation. I should state my hardware setup, since variations could require different commands. I am building/testing for the Tikk board, which is plugged into an nRF52840 on a ProMicro-compatible form factor. I am connected to that board using a JLink PLUS Compact for programming and debugging, which should mirror other JLink setups. If you’re using a different debugger, look up Zephyr’s concepts of ‘runners’, which can/will be part of your build process and will allow you to customize for whichever board you might be building against. It’s possible (and preferable!) that these are already set up for many of the boards that are in-tree for Zephyr. So to recap:</p>
<pre class="EnlighterJSRAW" data-enlighter-language="generic">west build -p -b promicro_nrf52840 tikk_fleet/app
west flash
west attach
(gdb) monitor reset</pre> Now we have built, programmed, and started debugging an application in Zephyr. Here we go! 
<h2 id="commands-within-gdb">Commands within GDB</h2>
<p>This is where I almost always get confused about what to do next. So I am writing this article for future-Chris as well. I will encase any shortcut with
<code>[ ]</code> because main keywords are shortened for convenience/speed:</p>
<pre class="EnlighterJSRAW" data-enlighter-language="generic">[b]reak sensor_read   # Function name to break on (ie. "sensor_read")
[b]reak main.c:34     # Call out a line ("34") of a file ("main.c") to break on
[c]ontinue            # Continue the program until next breakpoint 
[n]ext                # step over
[s]tep                # step into
[f]inish              # run until current function returns
[p]rint <var>         # display latest variable, but also can print out complex data structures
[i]nfo [b]reakpoints  # list all breakpoints
[d]elete 1            # delete breakpoint #1 (shown in command above)
[b]ack[t]race         # see all calls that got you to where you currently are
[i]nfo threads        # see all threads (useful since this is an RTOS)
[w]atch <var>         # break when a variable's value changes
x/32bx 0x20020000     # prints out 32 bytes in hex format from that address
clear                 # remove the breakpoint you just stopped on</pre> For commands like 
<p><code>[n]ext</code> and <code>[s]tep</code>, it’s also possible to repeat the action by continuing to hit the enter key, especially useful as you’re stepping through a program. Oh man, there are so many more. This is a deep rabbit hole and there are literal books on the subject. The above will get you started. Almost any command has a help menu that’s accessible from within the <code>(gdb)</code> command line, so take advantage of that as well.</p>
<h2 id="getting-more-context-with-tui">Getting more context with TUI</h2>
<p>This one really unlocked more useful info for me. If I’m in a terminal window for my project and I type in
<code>west attach</code>, it’ll do that. But where am I in the code? What are some of the available symbols. It’s almost like entering a video game level without a map. Well if you want a map, hit <code>ctrl + x</code> and then <code>a</code>, you will be shown the current location in a program<img src="https://blog.golioth.io/wp-content/uploads/2025/09/TUI-gdb-1030x512.png" alt=""> From here you can scroll up and down in that file. If you don’t want to have focus on the code window, you can repeat the <code>ctrl + x, a</code> command to close the window or <code>ctrl + x, o</code> to change ‘focus’ back to the (gdb) terminal prompt. Other ways to enable TUI include:</p>
<pre class="EnlighterJSRAW" data-enlighter-language="generic">layout src           # show source window (same as ctrl + x, a)
layout asm           # show assembly window
layout split         # source + assembly split
layout regs          # registers window
focus src            # focus on the source window (same as ctrl + x, o)</pre>
<h2 id="other-navigation">Other navigation</h2>
<p><code>ctrl + c</code> interrupts the target, which is especially useful if your interface gets locked up. <code>ctrl + d</code> from the (gdb) command line will gracefully exit <code>help</code> from the (gdb) command line will introduce you to myriad other commands, as will other resources about GDB online. This is just the tip of the iceberg for fellow hardware engineers.</p>
<h2 id="so-how-did-you-solve-your-problem">So how did you solve your problem?</h2>
<p>The resolution to the problem with the accelerometer is ongoing from a code perspective. I still am not completely certain why configuration isn’t passing down over the i2c bus to the LIS2DH. However, I was able to trigger the expected behavior by directly modifying registers on the i2c device using the i2c shell, another favorite troubleshooting tool around these parts. I could directly manipulate the values I needed to turn on the interrupt mode that I wanted. With the shell interface to i2c and the ability to halt on code and dig into the problem, I’m certain I’ll be able to troubleshoot the root cause. As always with electronics, having the right tool for the job makes things
<em>much</em> easier. I’m grateful to have expert co-workers that also help guide me along the path, and hope this article does the same for you. If you get stuck please let us know over on <a href="https://forum.golioth.io">the Golioth forum</a>.</p>
]]></content:encoded></item><item><title>How to Flash a Pre-Loaded Filesystem During Production</title><link>https://blog.golioth.io/how-to-flash-a-pre-loaded-filesystem-during-production/</link><guid>https://blog.golioth.io/how-to-flash-a-pre-loaded-filesystem-during-production/</guid><pubDate>Tue, 09 Sep 2025 14:07:52 +0000</pubDate><dc:creator>Mike Szczys</dc:creator><category>How To</category><category>Zephyr</category><description>Zephyr has a filesystem API that makes it pretty easy to read and write to device flash. We’ve found this useful when testing out certificate authentication. The Golioth Firmware SDK shows how to write credentials onto the filesystem via SMP, useful since credentials are different for each device. But what if you want to write the same files to all devices during production? Whether it’s machine learning models or simple configuration data, you can preload a filesystem and flash it as part of your production process. Today we’ll cover generating, flashing, reading, and extracting LittleFS images using Zephyr. However, the process will be nearly identical for other RTOSes.</description><content:encoded><![CDATA[<p>Zephyr has a filesystem API that makes it pretty easy to read and write to device flash. We’ve found this useful when testing out certificate authentication. The Golioth Firmware SDK shows <a href="https://github.com/golioth/golioth-firmware-sdk/tree/main/examples/zephyr/certificate_provisioning">how to write credentials onto the filesystem via SMP</a>, useful since credentials are different for each device. But what if you want to write the same files to all devices during production? Whether it’s machine learning models or simple configuration data, you can preload a filesystem and flash it as part of your production process. Today we’ll cover generating, flashing, reading, and extracting LittleFS images using Zephyr. However, the process will be nearly identical for other RTOSes.</p>
<h2 id="why-not-just-make-it-part-of-the-firmware">Why not just make it part of the firmware?</h2>
<p>Why are we even doing this, can’t these files just be built into the firmware? The problem with hardcoding data like this is that you need to update the whole firmware image to change them. Take the case of machine learning models. These models are commonly trained on specialized hardware, and only once the model has been refined is it presented to the constrained device (your microcontroller-based product). If a more performant model is available later on, you want to be able to update just the model. Firmware updates consume bandwidth (which for cellular has a cost) and battery budget.
<a href="https://blog.golioth.io/golioth-for-ai/">Golioth specializes in OTA updates of not just firmware, but assets like ML models</a>. So you can send the new model as an OTA asset which gets written to the filesystem. Separating this data into a filesystem makes a lot of sense. You can update the firmware without sending the machine learning model with it. The device is also capable of updating what’s on the filesystem without touching the firmware its currently running. So let’s take care of the chicken and egg issue by flashing a populated filesystem as devices roll off the line.</p>
<h2 id="running-the-zephyr-lfs-sample">Running the Zephyr LFS Sample</h2>
<p>Zephy has a built-in support for
<a href="https://github.com/littlefs-project/littlefs">LFS</a>, the “little fail-safe filesystem designed for microcontrollers”. The RTOS reserves a partition for the filesystem, and at first run it will format the partition if unable to a read valid filesystem configuration. We can build <a href="https://github.com/zephyrproject-rtos/zephyr/tree/main/samples/subsys/fs/littlefs">the Zephyr LFS sample</a> to see the formatting happen, and the sample will also populate some files in the filesystem afterward. I’m building for the NXP <code>frdm_rw612</code> board. By default, this sample will build a filesystem that is ~57MB, so I’m going to use a devicetree overlay file to create a more manageable ~24k filesystem.</p>
<pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-group="1" data-enlighter-title="boards/frdm_rw612.overlay">/delete-node/ &storage_partition;

/ {
    fstab {
        compatible = "zephyr,fstab";
        lfs1: lfs1 {
            compatible = "zephyr,fstab,littlefs";
            mount-point = "/lfs1";
            partition = <&lfs1_part>;
            automount;
            read-size = <16>;
            prog-size = <16>;
            cache-size = <64>;
            lookahead-size = <32>;
            block-cycles = <512>;
        };
    };
};

&w25q512jvfiq {
    partitions {
        compatible = "fixed-partitions";
        #address-cells = <1>;
        #size-cells = <1>;

        lfs1_part: partition@623000 {
            label = "storage";
            reg = <0x623000 0x6000>;
        };
    };
};</pre> Now let’s build and flash the application: 
<pre class="EnlighterJSRAW" data-enlighter-language="generic">west build -p -b frdm_rw612
west flash</pre> You should see the application format the filesystem, mount it, then write a file to it. 
<pre class="EnlighterJSRAW" data-enlighter-language="generic">I: littlefs partition at /lfs1
I: LittleFS version 2.10, disk version 2.1
I: FS at w25q512jvfiq@0:0x623000 is 6 0x1000-byte blocks with 512 cycle
I: partition sizes: rd 16 ; pr 16 ; ca 64 ; la 32
E: WEST_TOPDIR/modules/fs/littlefs/lfs.c:1389: Corrupted dir pair at {0x0, 0x1}
W: can't mount (LFS -84); formatting
I: /lfs1 mounted
I: Automount /lfs1 succeeded
*** Booting Zephyr OS build v4.1.0 ***
Sample program to r/w files on littlefs
Area 0 at 0x623000 on w25q512jvfiq@0 for 24576 bytes
/lfs1 automounted
/lfs1: bsize = 16 ; frsize = 4096 ; blocks = 6 ; bfree = 4

Listing dir /lfs1 ...
/lfs1/boot_count read count:0 (bytes: 0)
/lfs1/boot_count write new boot count 1: [wr:1]
I: Test file: /lfs1/pattern.bin not found, create one!
------ FILE: /lfs1/pattern.bin ------
01 55 55 55 55 55 55 55 02 55 55 55 55 55 55 55
03 55 55 55 55 55 55 55 04 55 55 55 55 55 55 55

--- snip ---

43 55 55 55 55 55 55 55 44 55 55 55 55 55 55 55
45 55 aa
I: /lfs unmounted
/lfs unmount: 0</pre>
<h2 id="reading-and-analyzing-an-lfs-partition">Reading and Analyzing an LFS Partition</h2>
<p>Now that we have a filesystem on the device, let’s read it from flash and analyze the contents. I’m using a J-Link to read the memory. For this particular chip, flash is locate at an offset of 0x8000000. Combine this with the partition address (0x623000) and size (0x6000) from our devicetree overlay file and we can read the LFS partition to a file.</p>
<pre class="EnlighterJSRAW" data-enlighter-language="generic">JLinkExe -Device RW612 -if SWD -Speed 4000
connect
SaveBin lfs.bin 0x8623000 0x6000</pre> Now we have an lfs.bin file. There’s a handy tool called 
<p><a href="https://github.com/jrast/littlefs-python">littlefs-python</a> that we can use to view the files on the filesystem. Let’s install it and list the files. If you go back and look at the output of the sample program you’ll notice the filesystem is reported 6 blocks of size 0x1000 (4096 bytes) which we use when viewing the binary.</p>
<pre class="EnlighterJSRAW" data-enlighter-language="generic">pip install littlefs-python
littlefs-python list lfs.bin --block-size 4096

/boot_count
/pattern.bin</pre> Voila! We can see the pattern.bin file as well as a boot count file, both written by the sample application. If you want access to these files, you can extract them using the same tool. 
<pre class="EnlighterJSRAW" data-enlighter-language="generic">littlefs-python extract --block-size 4096 lfs.bin output/</pre>
<h2 id="creating-an-lfs-binary">Creating an LFS Binary</h2>
<p>Now for the main event. Let’s create our own filesystem binary and write it to the device. I’m going to start by setting up the desired filesystem on my computer. I’m going to do this with empty files but of course any files may be used here.</p>
<pre class="EnlighterJSRAW" data-enlighter-language="generic">mkdir my-new-fs
touch my-new-fs/hello-there.txt
mkdir my-new-fs/models
touch my-new-fs/models/yolo.bin
</pre> My filesystem now looks like this: 
<pre>my-new-fs/
├── hello-there.txt
└── models
    └── yolo.bin</pre> Now use the python tool to convert this to a binary. Make sure the block count matches the filesize you set up in Zephyr. 
<pre class="EnlighterJSRAW" data-enlighter-language="generic">littlefs-python create my-new-fs/ lfs-new.bin --block-size 4096 --block-count 6</pre> And then list the contents to make sure we have what we want: 
<pre class="EnlighterJSRAW" data-enlighter-language="generic">littlefs-python list lfs-new.bin --block-size 4096

/models
/hello-there.txt
/models/yolo.bin
</pre> Now writing it back onto the device is very similar to the read step earlier in this post. Once again, the start address (0x623000) comes from the partition in build/zephyr/zephyr.dts which was set by the overlay file that I used. And this chip begins at a memory offset of 0x8000000. 
<pre class="EnlighterJSRAW" data-enlighter-language="generic">JLinkExe -Device RW612 -if SWD -Speed 4000
connect
LoadBin lfs-new.bin 0x8623000
reset</pre> Our new filesystem is written to device flash and we rebooted the chip. However, it’s a bit difficult to tell so let’s use the filesystem shell so we can get a directory listing. 
<h2 id="using-zephyrs-filesystem-shell">Using Zephyr’s Filesystem Shell</h2>
<p>We can recompile and reflash our firmware to enable the shell. Remember, the filesystem is on its own partition, so this will not overwrite our data. One change is required in the sample code. At the end of the application, the filesystem is unmounted. We want to leave it mounted so we can list the files. Before rebuilding, comment out these two lines at the end of main.c:</p>
<pre class="EnlighterJSRAW" data-enlighter-language="generic">/*rc = fs_unmount(mountpoint);*/
/*LOG_PRINTK("%s unmount: %d\n", mountpoint->mnt_point, rc);*/
</pre> Now rebuild with additional Kconfig symbols to turn on the shell and the filesystem shell and flash the new firmware. These Kconfig can also go into your prj.conf if you want them to be permanently part of your project. 
<pre class="EnlighterJSRAW" data-enlighter-language="bash">west build -p -b frdm_rw612 -- -DCONFIG_SHELL=y -DCONFIG_FILE_SYSTEM_SHELL=y
west flash</pre> We can now use shell commands to list files on the filesystem, confirming our generated binary is present on the device. 
<pre class="EnlighterJSRAW" data-enlighter-language="bash">uart:~$ fs ls
lfs1/
uart:~$ fs ls lfs1
boot_count
hello-there.txt
models/
pattern.bin
uart:~$ fs ls lfs1/models
yolo.bin
</pre>
<h2 id="what-will-you-use-it-for">What will you use it for?</h2>
<p>These are some nice filesystem tricks that may come in handy during a manufacturing run. While we’ve used Zephyr’s SMP subsystem to read from and write to the filesystem, that requires the SMP to be built into your application. A programmer is already present on the production line, so writing a filesystem at the same time as the firmware makes sense. If it’s the same filesystem for every device, the binary may be bundled along with the firmware. But there are use cases where each device would have different data. Such is the case where certificate authentication is used. Your PKI should have the device itself generate a key, and the certificate signing request (CSR) may be written to the filesystem then read/signed and the resulting public cert written back onto the device. Whatever your use case, we’d love to hear about why you need to read/write the filesystem. Let us know by posting to
<a href="https://forum.golioth.io/">the Golioth forum</a>!</p>
]]></content:encoded></item><item><title>How to build a Bluetooth-connected digital signage fleet</title><link>https://blog.golioth.io/how-to-build-a-bluetooth-connected-digital-signage-fleet/</link><guid>https://blog.golioth.io/how-to-build-a-bluetooth-connected-digital-signage-fleet/</guid><pubDate>Thu, 04 Sep 2025 13:52:09 +0000</pubDate><dc:creator>Chris Gammell</dc:creator><category>Bluetooth</category><description>Today, we’re going to showcase a digital signage demo, using Golioth Connectivity (which enables Bluetooth support). This application also benefits from a small control surface, as in “not a lot of control data is needed to make bigger changes with a device”. This is a microcosm of the larger digital signage industry, which has migrated from simple LED signs up to insanely high resolution screens that shows ads. But the scale from top to bottom requires a lot of the same capabilities and Golioth can help throughout the stack. Areas that need a high density of low cost nodes are a great fit for Bluetooth demos. The nodes communicate back to the internet through a gateway and each node still gets the capabilities of the Golioth cloud (OTA, settings, data streaming). We’re going to be building with the open source Tikk board to showcase how this might work for a high density traffic area. Let’s look at what we can do:</description><content:encoded><![CDATA[<p>Today, we’re going to showcase a digital signage demo, using <a href="https://blog.golioth.io/bluetooth-open-beta/">Golioth Connectivity</a> (which enables Bluetooth support). This application also benefits from a small control surface, as in “not a lot of control data is needed to make bigger changes with a device”. This is a microcosm of the larger digital signage industry, which has migrated from simple LED signs up to insanely high resolution screens that shows ads. But the scale from top to bottom requires a lot of the same capabilities and Golioth can help throughout the stack. Areas that need a high density of low cost nodes are a great fit for Bluetooth demos. The nodes communicate back to the internet through a gateway and each node still gets the capabilities of the Golioth cloud (OTA, settings, data streaming). We’re going to be building with the open source Tikk board to showcase how this might work for a high density traffic area. Let’s look at what we can do: <div style="position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden;">
      <iframe allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share; fullscreen" loading="eager" referrerpolicy="strict-origin-when-cross-origin" src="https://www.youtube.com/embed/Pj1QK%5c_BSSF0?autoplay=0&amp;controls=1&amp;end=0&amp;loop=0&amp;mute=0&amp;start=0" style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; border:0;" title="YouTube video"></iframe>
    </div>
</p>
<h2 id="whats-in-the-code">What’s in the code</h2>
<p>The
<code>tikk-fleet</code> project that enables this digital signage demo is based on our <a href="https://github.com/golioth/pouch">Pouch BLE GATT example program</a>. It extends the capabilities by listening for different settings and then acts on those settings using a IS31FL3731 driver for Zephyr. This basically turns settings on the Golioth Cloud into scrolling text or arrows on the device. For the scrolling text, <a href="https://blog.golioth.io/a-settings-system-for-any-bluetooth-fleet/#adding-a-setting-to-send-text-to-the-bluetooth-fleet">we explained what that looks like on the last post, so check that out</a>. For the arrows being shown in this demo, there is a setting handler that looks for one of 4  integer values coming from the cloud. This is used to trigger the directions of the arrows on the IS31FL3731 driver, which ultimately is a set of frames that are stored and replayed to get different motion in the arrows.</p>
<pre class="EnlighterJSRAW" data-enlighter-language="c">static int led_arrow_cb(int new_value)
{
    if (_arrow_dir == new_value)
    {
        return 0;
    }

    _arrow_dir = new_value;

    switch (_arrow_dir)
    {
        case 0:
            scroll_arrows(led_dev, false, false);
            break;
        case 1:
            scroll_arrows(led_dev, true, false);
            break;
        case 2:
            scroll_arrows(led_dev, false, true);
            break;
        case 3:
            scroll_arrows(led_dev, true, true);
            break;

        default:
            LOG_WRN("Unsupported scroll settings: %i", new_value);
            break;
    }

    return 0;
}

GOLIOTH_SETTINGS_HANDLER(ARROW, led_arrow_cb);</pre>
<p><img src="https://blog.golioth.io/wp-content/uploads/2025/08/tikk-640x360-1.gif" alt=""></p>
<h2 id="commissioning-fleets-with-certificates">Commissioning fleets with certificates</h2>
<p>As you’ll see over the coming weeks and months on the Golioth blog, we’re thinking about producing devices for production grade fleets. You may have also noticed that one requirement that is different for Golioth Connectivity (and our new Bluetooth support) is that indirectly connected devices can only validate onto the Golioth platform using certificates. This is different than directly connected devices, which can still prototype using Pre-Shared Keys (PSKs) before going to production with certificates. For a device like the Tikk, we were able to take the various steps in
<a href="https://docs.golioth.io/connectivity/credentials/pki/">our Public Key Infrastructure documentation</a> and turn it into a Python script to quickly generate device keys from the Certificate Authority. This is still using a locally generated Root CA–and we highly recommend using a PKI provider to hold your keys–but it lessened the load on generating and pushing the certs onto the Tikk device. Here’s what the Python script looks like:</p>
<pre class="EnlighterJSRAW" data-enlighter-language="python">#!/usr/bin/env python3
import argparse
import csv
import subprocess
import sys
from pathlib import Path
import shutil
import datetime

def check_tool(name: str):
    if shutil.which(name) is None:
        sys.exit(f"ERROR: Required tool '{name}' not found on PATH.")

def run(cmd, cwd=None, dry_run=False):
    print(">>", " ".join(cmd))
    if dry_run:
        return 0
    try:
        res = subprocess.run(cmd, cwd=cwd, check=True, capture_output=True, text=True)
        if res.stdout.strip():
            print(res.stdout.strip())
        if res.stderr.strip():
            print(res.stderr.strip())
        return res.returncode
    except subprocess.CalledProcessError as e:
        print(e.stdout or "", end="")
        print(e.stderr or "", end="")
        sys.exit(f"Command failed with exit code {e.returncode}: {' '.join(cmd)}")

def make_subject(project_slug: str, cert_id: str) -> str:
    return f"/O={project_slug}/CN={cert_id}"

def prompt_yes_no(prompt: str, default_no=True) -> bool:
    ans = input(prompt).strip().lower()
    if ans in ("y", "yes"):
        return True
    if ans in ("n", "no"):
        return False
    return not default_no  # empty -> default to no

def prompt_int_with_default(prompt: str, default_val: int) -> int:
    while True:
        s = input(f"{prompt} [default {default_val}]: ").strip()
        if s == "":
            return default_val
        try:
            v = int(s)
            if v <= 0:
                print("Please enter a positive integer.")
                continue
            return v
        except ValueError:
            print("Please enter a valid integer.")

def ensure_ca_exists(dry_run: bool):
    ca_crt = Path("ca.crt.pem")
    ca_key = Path("ca.key.pem")
    if ca_crt.exists() and ca_key.exists():
        return

    print("No local CA found (ca.key.pem, ca.crt.pem).")
    if not prompt_yes_no("Would you like to generate a new Root CA now? [y/N]: "):
        sys.exit("ERROR: Root CA is required. Provide ca.key.pem and ca.crt.pem or allow generation.")

    days = prompt_int_with_default("Enter validity period in days for Root CA", 365)
    print(f"Generating Root CA with validity: {days} days")

    # Generate CA key
    run(["openssl", "ecparam", "-name", "prime256v1", "-genkey", "-noout", "-out", "ca.key.pem"], dry_run=dry_run)

    # Self-signed CA certificate
    run([
        "openssl", "req", "-x509", "-new", "-nodes",
        "-key", "ca.key.pem",
        "-sha256", "-subj", "/CN=Root CA",
        "-days", str(days), "-out", "ca.crt.pem"
    ], dry_run=dry_run)

    expiry_date = datetime.datetime.now() + datetime.timedelta(days=days)
    print(f"Root CA created with validity: {days} days")
    print(f"Root CA will expire on: {expiry_date.strftime('%Y-%m-%d')}")

def provision_one(project_slug: str, cert_id: str, serial_port: str, days: int, mtu: int, outdir: Path, dry_run: bool):
    device_dir = outdir / cert_id
    device_dir.mkdir(parents=True, exist_ok=True)

    # Compute expiry and filenames
    expiry_date = datetime.datetime.now() + datetime.timedelta(days=days)

    # Filenames (include cert_id and expiry date: CERTID_YYYY-MM-DD)
    base_name = f"{cert_id}_{expiry_date.strftime('%Y-%m-%d')}"
    key_pem = device_dir / f"{base_name}.key.pem"
    csr_pem = device_dir / f"{base_name}.csr.pem"
    crt_pem = device_dir / f"{base_name}.crt.pem"
    key_der = device_dir / f"{base_name}.key.der"
    crt_der = device_dir / f"{base_name}.crt.der"

    ca_crt = Path("ca.crt.pem")
    ca_key = Path("ca.key.pem")
    if not ca_crt.exists() or not ca_key.exists():
        sys.exit("ERROR: ca.crt.pem and/or ca.key.pem not found in current directory.")

    print(f"\n=== Provisioning {cert_id} on {serial_port} ===")
    print(f"Output dir: {device_dir}")
    print(f"Certificate validity: {days} days")
    print(f"Certificate will expire on: {expiry_date.strftime('%Y-%m-%d')}")

    # 1) EC private key
    if not key_pem.exists():
        run(["openssl", "ecparam", "-name", "prime256v1", "-genkey", "-noout", "-out", str(key_pem)], dry_run=dry_run)
    else:
        print(f"Skip key (exists): {key_pem}")

    # 2) CSR
    subj = make_subject(project_slug, cert_id)
    run(["openssl", "req", "-new", "-key", str(key_pem), "-subj", subj, "-out", str(csr_pem)], dry_run=dry_run)

    # 3) Sign certificate
    run([
        "openssl", "x509", "-req",
        "-in", str(csr_pem),
        "-CA", str(ca_crt),
        "-CAkey", str(ca_key),
        "-CAcreateserial",
        "-out", str(crt_pem),
        "-days", str(days),
        "-sha256"
    ], dry_run=dry_run)

    # 4) Export DER
    run(["openssl", "ec", "-in", str(key_pem), "-outform", "DER", "-out", str(key_der)], dry_run=dry_run)
    run(["openssl", "x509", "-in", str(crt_pem), "-outform", "DER", "-out", str(crt_der)], dry_run=dry_run)

    # 5) Upload via smpmgr
    run(["smpmgr", "--port", serial_port, "--mtu", str(mtu), "file", "upload", str(key_der), "/lfs1/credentials/key.der"], dry_run=dry_run)
    run(["smpmgr", "--port", serial_port, "--mtu", str(mtu), "file", "upload", str(crt_der), "/lfs1/credentials/crt.der"], dry_run=dry_run)

    print(f"Done: {cert_id} ✔")

def parse_csv(csv_path: Path):
    items = []
    with csv_path.open(newline="") as f:
        reader = csv.DictReader(f)
        required = {"cert_id", "serial_port"}
        if not required.issubset(reader.fieldnames or {}):
            sys.exit(f"ERROR: CSV must have headers: {', '.join(sorted(required))}")
        for row in reader:
            cert_id = row["cert_id"].strip()
            serial_port = row["serial_port"].strip()
            if not cert_id or not serial_port:
                print(f"WARNING: Skipping row with missing cert_id or serial_port: {row}")
                continue
            items.append((cert_id, serial_port))
    return items

def main():
    ap = argparse.ArgumentParser(
        description=(
            "Provision devices with OpenSSL + smpmgr uploads.\n\n"
            "Files are written under ./provisioned/<CERT_ID>/ using names like\n"
            "<CERT_ID>_YYYY-MM-DD.key.pem / .csr.pem / .crt.pem / .key.der / .crt.der\n\n"
            "If run with only --project-slug, the script will check/generate a Root CA and then exit with examples."
        )
    )
    ap.add_argument("--project-slug", required=True, help="Organization (O=) field for the certificate subject.")
    ap.add_argument("--cert-id", help="Device certificate ID (CN=) for single-device mode.")
    ap.add_argument("--serial-port", help="Serial port for single-device mode (e.g. /dev/ttyACM0, COM5).")
    ap.add_argument("--csv", type=Path, help="CSV with headers: cert_id,serial_port for batch mode.")
    ap.add_argument("--days", type=int, default=365, help="Certificate validity in days (default: 365).")
    ap.add_argument("--mtu", type=int, default=128, help="smpmgr MTU (default: 128).")
    ap.add_argument("--outdir", type=Path, default=Path("provisioned"), help="Base output directory (default: ./provisioned).")
    ap.add_argument("--dry-run", action="store_true", help="Print commands without executing.")
    args = ap.parse_args()

    check_tool("openssl")
    check_tool("smpmgr")

    # Ensure local Root CA exists or offer to generate one
    ensure_ca_exists(args.dry_run)

    # If no devices/CSV provided, cleanly exit after CA prep with next-step examples
    if not args.csv and (not args.cert_id or not args.serial_port):
        print("\nRoot CA is present and ready.")
        print("CA files: ca.key.pem, ca.crt.pem")
        print("\nNext, provision devices in one of two ways:\n")
        print("Single device:")
        print(f"  python3 provision_devices.py \\\n    --project-slug {args.project_slug} \\\n    --cert-id DEVICE123 \\\n    --serial-port /dev/ttyACM0")
        print("\nBatch (CSV with headers cert_id,serial_port):")
        print(f"  python3 provision_devices.py \\\n    --project-slug {args.project_slug} \\\n    --csv devices.csv")
        sys.exit(0)

    items = []
    if args.csv:
        items.extend(parse_csv(args.csv))
    if args.cert_id and args.serial_port:
        items.append((args.cert_id, args.serial_port))

    timestamp = datetime.datetime.now().strftime("%Y-%m-%d_%H-%M-%S")
    print(f"Starting provisioning at {timestamp}")
    print(f"Project slug: {args.project_slug}")
    print(f"Devices: {len(items)}")
    args.outdir.mkdir(parents=True, exist_ok=True)

    for cert_id, serial_port in items:
        provision_one(args.project_slug, cert_id, serial_port, args.days, args.mtu, args.outdir, args.dry_run)

    print("\nAll done. 🎉")

if __name__ == "__main__":
    main()
</pre>   
<h2 id="how-this-could-be-modified">How this could be modified</h2>
<p>While there aren’t a
<em>ton</em> of direct commercial use cases for a 25×15 mm LED sign, the idea is the same throughout the industry. Many devices could take our Settings service and apply it to portions of their fleet. As Golioth Connectivity continues to expand, we’ll be looking at other transport mechanisms to get the same encrypted data over other channels to devices (think CAN, LoRa, or even serial). One important distinction is that each device is directly communicating back through the cloud through a gateway. Effectively the Bluetooth device using Pouch <em>is</em> an internet connected device…just using an additional hop. This is different than the behavior that might be expected for updating a range of signs in a small area all at once. We have shown a BLE Mesh demo in the past that achieves that goal, but has many other challenges, including not actually being enabled by Golioth Connectivity (that demo far preceded our announcement). If we wanted similar behavior on this setup, we could instead implement a schedule, upon which all signs would change. But that requires additional features that deliver time to each device. If you’re interested in time-based updates, please reach out! We’ll continue to have demos using the Tikk board and Golioth Connectivity. If you have a use case you’d like us to take on, <a href="https://forum.golioth.io">please drop a note on our forum</a>!</p>
]]></content:encoded></item><item><title>A Settings System for any Bluetooth Fleet</title><link>https://blog.golioth.io/a-settings-system-for-any-bluetooth-fleet/</link><guid>https://blog.golioth.io/a-settings-system-for-any-bluetooth-fleet/</guid><pubDate>Tue, 02 Sep 2025 14:49:25 +0000</pubDate><dc:creator>Mike Szczys</dc:creator><category>Bluetooth</category><description>Last week we announced the open beta for Golioth Bluetooth support. It makes connecting your BLE IoT fleet to the Internet a snap, for bi-directional data and OTA firmware updates. Today I’ll show how easy it is to use this service to a control the fleet all at once, or to drill down to individual unit by using the built-in Settings service.</description><content:encoded><![CDATA[<p>Last week we announced the <a href="https://blog.golioth.io/bluetooth-open-beta/">open beta for Golioth Bluetooth support</a>. It makes connecting your BLE IoT fleet to the Internet a snap, for bi-directional data and OTA firmware updates. Today I’ll show how easy it is to use this service to a control the fleet all at once, or to drill down to individual unit by using the built-in Settings service.</p>
<h2 id="apply-settings-to-all-or-one">Apply settings to all or one</h2>
<p>The
<a href="https://docs.golioth.io/device-management/settings/">Golioth Settings service</a> delivers fleet control from the cloud down to devices. For example, let’s consider Bluetooth (BLE) devices that sleep most of the time but occasionally wakes up to upload sensor data via <a href="https://docs.golioth.io/connectivity/networks/gateways">a gateway</a>. The amount of time spent in the sleep state can be added as a setting that applies to the entire fleet, but the same setting may also be tweaked on a device-by-device basis. If you know your entire fleet is going to be collecting more sensor data than usual, you can make one change and all devices will update their sleep pattern the next time they check in. In the same way, the setting for a single device may be adjusted separately from the fleet-wide setting. Since this is built into the Golioth platform, its really easy to add it to your Bluetooth firmware. Let’s see what is involved in adding a new setting.</p>
<h2 id="prerequisite-run-the-sample-application">Prerequisite: Run the sample application</h2>
<p>If you don’t already have a Bluetooth fleet on Golioth, follow the
<a href="https://github.com/golioth/bluetooth-gateway/">Getting Started guide</a> in the Bluetooth Gateway repository and add a leaf-node by following <a href="https://github.com/golioth/pouch/tree/main/examples/ble_gatt">the example app</a> in the Pouch repository. This will get you up and streaming simulated sensor data to the cloud in no time. <a href="https://blog.golioth.io/device-management-should-be-free/">Golioth is free for individuals</a> making it frictionless to take it for a test-drive. You will need to get hardware that matches the recommended boards for the lowest-friction experience. The sample Pouch application already supports the Settings service to switch an LED on or off remotely. From the cloud, this is just a few clicks to add a boolean setting called <code>LED</code>. <a href="https://blog.golioth.io/wp-content/uploads/2025/08/golioth-setting-led-boolean.png"><img src="https://blog.golioth.io/wp-content/uploads/2025/08/golioth-setting-led-boolean-1030x668.png" alt="Adding a boolean setting to the Golioth Settings service using the web-based console "></a>The <a href="https://github.com/golioth/pouch/blob/dee858d8146f398da7e89339925104f7a1a9ec31/examples/ble_gatt/src/main.c#L119C1-L131C47">device-side code that supports this setting</a> couldn’t be easier. It’s made up of a function that will run as a callback, and a macro to register that callback.</p>
<pre class="EnlighterJSRAW" data-enlighter-language="c">static int led_setting_cb(bool new_value)
{
    LOG_INF("Received LED setting: %d", (int) new_value);

    if (DT_HAS_ALIAS(led0))
    {
        gpio_pin_set_dt(&led, new_value ? 1 : 0);
    }

    return 0;
}

GOLIOTH_SETTINGS_HANDLER(LED, led_setting_cb);</pre> The important parts are the first and last lines of the code segment above. The first line is a function declaration that follows 
<p><a href="https://github.com/golioth/pouch/blob/dee858d8146f398da7e89339925104f7a1a9ec31/include/golioth/settings_callbacks.h#L14-L24">the callback function prototypes specified in the Pouch library</a>. In this case we know the callback is for a Setting of boolean type because that’s the type for the parameter passed into this function. The last line is important because it registers this callback. The parameters used in that registration are the settings name that will trigger the callback (<code>LED</code>) and the function to run when that setting is received. The code inside the actual function is up to you. In this example, the new setting is logged. Next the function checks to see if there is an LED enabled in the devicetree, toggling it when found. I suppose I said there are only two important parts, but the return code is also important. Whether successful or not, the return code will be reported back to the cloud, indicating that the device successfully (or unsuccessfully) received the settings update. Lets try adding our own setting to the fleet.</p>
<h2 id="adding-a-setting-to-send-text-to-the-bluetooth-fleet">Adding a setting to send text to the Bluetooth fleet</h2>
<p>If your Bluetooth IoT devices have a user interface, like a screen, you may want to send messages to them. Lets add a setting that sends a string to the fleet. First, add the setting in
<a href="https://console.golioth.io">the Golioth web console</a>. <a href="https://blog.golioth.io/wp-content/uploads/2025/08/golioth-setting-led-string.png"><img src="https://blog.golioth.io/wp-content/uploads/2025/08/golioth-setting-led-string-1030x654.png" alt="Golioth web console used to add a string setting to an IoT fleet."></a>The screenshot above shows a setting called MESSAGE added as a String type. The value is the string that should initially be sent to the fleet. The value may be changed at any time (although the key and data type are immutable because they need to match what is built into the firmware). Adding this setting to the firmware is nearly the same as we saw in the previous section, requiring a callback function that is then registered for this setting.</p>
<pre class="EnlighterJSRAW" data-enlighter-language="c">#include <golioth/settings_callbacks.h>

#define MESSAGE_MAX_LEN 64
char _stored_msg[MESSAGE_MAX_LEN + 1];

static int settings_message_cb(const char *new_value, size_t len)
{
    if (MESSAGE_MAX_LEN < len)
    {
        LOG_ERR("Received message is too long: %zu > %u", len, MESSAGE_MAX_LEN);
        return -1;
    }

    if (0 == memcmp(_stored_msg, new_value, len))
    {
        return 0;
    }

    LOG_DBG("New MESSAGE setting: %.*s", len, new_value);
    snprintk(_stored_msg, MESSAGE_MAX_LEN, "%.*s", len, new_value);
    scroll_message(led_dev, _stored_msg, 50);

    return 0;
}

GOLIOTH_SETTINGS_HANDLER(MESSAGE, settings_message_cb);</pre> For completeness I’ve included all the code needed by this callback. We see the header file that contains the typedefs for the callback functions. Next there is a character array declared globally to store the most recently received message. I chose to illustrate this callback because a string setting is slightly different from the other types (bool, int, float) because it passes both a pointer to the string and a length. The callback function checks to see if the string is too long, before copying it to the global array and logging the new message. This happens to also push the message to an LED matrix on the demo device. Despite a few differences, this continues to be extremely easy to implement. Once the device is in the field, you can drill down to control a single member of the fleet from the settings tab of its device page. <figure id="attachment_10038" aria-describedby="caption-attachment-10038" style="width: 696px" class="wp-caption aligncenter">
<p><a href="https://blog.golioth.io/wp-content/uploads/2025/08/golioth-setting-device-override.png"><img src="https://blog.golioth.io/wp-content/uploads/2025/08/golioth-setting-device-override-1030x642.png" alt="Golioth web console showing the setting override for a single device"></a><figcaption id="caption-attachment-10038" class="wp-caption-text">Change the setting for a single device from the Settings tab of the device page.</figcaption></figure></p>
<h2 id="data-up-settings-and-ota-update-down">Data Up, Settings and OTA Update Down</h2>
<p>Golioth Connectivity enables a new generation of Bluetooth devices that can send any data they like to the cloud. At the same time, you can control your fleet using the Settings service, and update the leaf nodes using the OTA service. We’re happy to welcome the world of Bluetooth devices properly to the Internet, and
<a href="https://blog.golioth.io/bluetooth-open-beta/">its ready for you to start using right now</a>!</p>
]]></content:encoded></item><item><title>Get started with Bluetooth and Golioth Connectivity</title><link>https://blog.golioth.io/get-started-with-bluetooth-and-golioth-connectivity/</link><guid>https://blog.golioth.io/get-started-with-bluetooth-and-golioth-connectivity/</guid><pubDate>Thu, 28 Aug 2025 15:55:54 +0000</pubDate><dc:creator>Chris Gammell</dc:creator><category>Bluetooth</category><description>When we started talking about building Bluetooth devices at Golioth, I got so excited. While I’ve used chipsets with Bluetooth onboard in the past, I’ve never had the ability to do a full end-to-end application because of all the elements involved. I figured I would need 2 to 3 other people to help me out. Hardware, firmware, software, cloud, app development…there really is a lot to do! Golioth Connectivity makes it easier with a couple of predefined elements that will get you started, and you will be able to make Bluetooth devices do amazing things…all on your own. We’re going to walk through each piece required to do a fully encrypted end-to-end demo. Tomorrow, we’re going to do a livestream where we walk through each step.</description><content:encoded><![CDATA[<p>When we started talking about building Bluetooth devices at Golioth, I got so excited. While I’ve used chipsets with Bluetooth onboard in the past, I’ve never had the ability to do a full end-to-end application because of all the elements involved. I figured I would need 2 to 3 other people to help me out. Hardware, firmware, software, cloud, app development…there really is a lot to do! Golioth Connectivity makes it easier with a couple of predefined elements that will get you started, and you will be able to make Bluetooth devices do amazing things…all on your own. We’re going to walk through each piece required to do a fully encrypted end-to-end demo. Tomorrow, we’re going to do a livestream where we walk through each step.</p>
<p style="text-align: center;">
  <a href="https://streamyard.com/watch/d4dEMbwM2wWM"><strong>Join the livestream at 2 pm ET / 11 am PT on August 29th</strong></a>
</p>
<h2 id="step-1--cloud-control">Step 1 – Cloud control</h2>
<p>This step is exceedingly easy, and you’ve
<em>surely</em> already completed it: <a href="https://console.golioth.io/">get yourself a Golioth account</a>. This takes care of the cloud side of the equation: no setting up databases or creating IAM profiles and hoping you don’t give the wrong people the right access.</p>
<h2 id="step-2--app--gateway-control">Step 2 – App / Gateway control</h2>
<p>Another easy step! This one replaces the need to learn how to design an app. Instead, you can download a binary that goes onto one of these 3 development boards:</p>
<ul>
<li>NXP FRDM-RW612</li>
<li>Nordic Thingy: 91X</li>
<li>Nordic nRF9160-DK Follow the instructions on the repository to</li>
</ul>
<p><a href="https://github.com/golioth/bluetooth-gateway?tab=readme-ov-file#release-binaries">download the pre-compiled binary</a>, create a device on your <a href="https://console.golioth.io">Golioth console</a>, and assign credentials to the gateway. Bingo, you have a bluetooth-to-cloud gateway.</p>
<h2 id="step-3--bluetooth-node-control">Step 3 – Bluetooth node control</h2>
<p>This is the bulk of the activity we’ll be focusing on during
<a href="https://streamyard.com/watch/d4dEMbwM2wWM">the livestream tomorrow</a>, since the Bluetooth nodes in your fleet contain the smarts (firmware) and the credentials to talk through the gateway up to the cloud. First, you need to look at our <a href="https://github.com/golioth/pouch">Pouch repository</a>, which has <a href="https://github.com/golioth/pouch/tree/main/examples/ble_gatt">a <code>ble_gatt</code> example</a>. This is the basis for sending up a packet that has been encrypted with the cloud but is still allowed to traverse the gateway.<img src="https://blog.golioth.io/wp-content/uploads/2025/03/nt_3-1030x579.jpg" alt="NT Managed Gateway Managed Device"> Unstated in the directions is how to create a Zephyr workspace. If you are working from a fresh environment, it might look like this: <strong>Install the Python virtual environment (recommended)</strong></p>
<pre class="EnlighterJSRAW" data-enlighter-language="bash">cd ~
mkdir pouch-test
cd pouch-test
python -m venv .venv
source .venv/bin/activate
pip install wheel west ecdsa</pre>
<p><strong>Use west to initialize and Install</strong></p>
<pre class="EnlighterJSRAW" data-enlighter-language="generic">west init -m git@github.com:golioth/pouch.git .
west update --narrow -o=--depth=1
west zephyr-export
pip install -r zephyr/scripts/requirements.txt
pip install -r pouch/requirements.txt</pre> From here, 
<p><a href="https://github.com/golioth/pouch/tree/main/examples/ble_gatt#building">you would build against the board you’re looking to try out</a>. The pouch repository was extensively tested against <code>nrf52840dk/nrf52840</code>:</p>
<pre class="EnlighterJSRAW" data-enlighter-language="generic">west build -p -b nrf52840dk/nrf52840 pouch/examples/ble_gatt
west flash</pre> We also have been testing a range of other vendor hardware that is supported in Zephyr. 
<p><a href="https://forum.golioth.io/c/golioth-guides/bluetooth/33">Check out the new Bluetooth section on our forum</a> to try chips from different vendors and see what you might need to do differently (and ask questions about each, as needed).</p>
<h3 id="step-3a--using-certificates">Step 3a – Using certificates</h3>
<p>One of the biggest differences everyday Golioth users might notice is that Bluetooth nodes must use certificates in order to connect to the cloud (through a gateway). This is explained in
<a href="https://www.youtube.com/watch?v=whcN2DsVi8w">our introduction video for Golioth Connectivity</a> and we’ll continue to publish about the role of security in our new paradigm of indirectly connected devices. The example application we just built has a “Little Filesystem” (<code>littlefs</code> or <code>lfs</code>) included with it. That allows us to <a href="https://github.com/golioth/pouch/tree/main/examples/ble_gatt#authentication">push certificates onto the device using some standard SMP tooling</a>. You will first need to generate your own certificates using openssl; follow the <a href="https://docs.golioth.io/connectivity/credentials/pki/">Public Key Infrastructure (PKI) docs page</a> to do so.</p>
<h2 id="step-4--put-it-all-together">Step 4 – Put it all together</h2>
<p>Once your gateway is booted up and your Bluetooth node connects through the gateway to the cloud, you will see both devices on your console. If you create a node with a brand new Certificate ID, that node will be created on your project account the first time it connects (allowing zero-touch provisioning). Add a new device setting
<code>LED</code> as a boolean, and that will be passed down over the gateway to the node and will toggle the LED. The node will also publish a simulated temperature every 20 seconds (the pre-defined advertising interval). We’ll show all of this in action in the Livestream on the 29th.</p>
<h2 id="see-it-in-action-right-now">See it in action right now</h2>
<p>Yesterday we posted a video of a Bluetooth device talking using Pouch through a standard gateway up to the Cloud to complete an Over-The-Air firmware update. This is a preview of Golioth Connectivity in action. Want to learn how to do it yourself?
<a href="https://streamyard.com/watch/d4dEMbwM2wWM">Sign up for the livestream on August 29th at 2 pm ET / 11 am PT</a>…or watch it on demand!</p>
]]></content:encoded></item><item><title>'Bluetooth Support is here: Golioth Connectivity enables a new class of devices'</title><link>https://blog.golioth.io/bluetooth-open-beta/</link><guid>https://blog.golioth.io/bluetooth-open-beta/</guid><pubDate>Tue, 26 Aug 2025 12:55:16 +0000</pubDate><dc:creator>Dylan Swartz</dc:creator><category>Bluetooth</category><category>Featured</category><description> We’re excited to announce that Bluetooth support is now available in Open Beta for all Golioth users. With this release, Golioth now brings full device management and data routing to Bluetooth-based devices, even those without direct internet access. Developers can now stream telemetry, perform OTA firmware updates, manage settings, and log events using the same Golioth services they already use for devices with a direct internet access. Previously available in private access, Bluetooth support is now open to all users as a part of a broader initiative we call Golioth Connectivity. This enables a flexible and unified approach to connecting devices to the cloud, whether directly via Wi-Fi, Ethernet, or Cellular, or indirectly through a local gateway. As part of this release, Bluetooth Low Energy (BLE) devices can now connect to the Golioth platform securely through a gateway. These BLE devices, which often lack native internet connectivity, are now able to stream data, receive firmware updates, and be fully managed through the Golioth Console. To power this experience, developers use Pouch, an open source SDK for building firmware that communicates over protocols like BLE without a direct internet connection. Pouch provides an efficient, encrypted, and authenticated communication layer between BLE devices and the cloud, enabling seamless integration with Golioth services. While this open beta focuses on BLE, Pouch is designed to be extensible and will soon support additional protocols such as LoRa, CAN &amp;amp;amp; Modbus. This paves the way for broader support of gateway-based communication strategies across diverse IoT deployments. With these capabilities, Golioth extends full cloud control to BLE devices, streamlining deployment and operations for devices that typically lack direct cloud integration. Developers can now manage, update, and monitor BLE-based device fleets with the same ease and infrastructure used for other connected hardware. This also extends all of Golioth’s AI capabilities to unlock new Edge and Physical AI use cases.</description><content:encoded><![CDATA[<div style="position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden;">
      <iframe allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share; fullscreen" loading="eager" referrerpolicy="strict-origin-when-cross-origin" src="https://www.youtube.com/embed/whcN2DsVi8w?autoplay=0&amp;controls=1&amp;end=0&amp;loop=0&amp;mute=0&amp;start=0" style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; border:0;" title="YouTube video"></iframe>
    </div>

<p>We’re excited to announce that <strong>Bluetooth support is now available in Open Beta</strong> for all Golioth users. With this release, Golioth now brings full device management and data routing to Bluetooth-based devices, even those without direct internet access. Developers can now stream telemetry, perform OTA firmware updates, manage settings, and log events using the same Golioth services they already use for devices with a direct internet access. Previously <a href="https://blog.golioth.io/bluetooth-to-cloud/">available in private access</a>, Bluetooth support is now open to all users as a part of a broader initiative we call <a href="https://golioth.io/connectivity">Golioth Connectivity</a>. This enables a flexible and unified approach to connecting devices to the cloud, whether directly via Wi-Fi, Ethernet, or Cellular, or indirectly through a local gateway. As part of this release, Bluetooth Low Energy (BLE) devices can now connect to the Golioth platform securely through a <a href="http://docs.golioth.io/connectivity/networks/gateways/">gateway</a>. These BLE devices, which often lack native internet connectivity, are now able to stream data, receive firmware updates, and be fully managed through the Golioth Console. To power this experience, developers use <a href="https://github.com/golioth/pouch">Pouch</a>, an open source SDK for building firmware that communicates over protocols like BLE without a direct internet connection. Pouch provides an efficient, encrypted, and authenticated communication layer between BLE devices and the cloud, enabling seamless integration with Golioth services. While this open beta focuses on BLE, Pouch is designed to be extensible and will soon support additional protocols such as LoRa, CAN &amp; Modbus. This paves the way for broader support of gateway-based communication strategies across diverse IoT deployments. With these capabilities, Golioth extends full cloud control to BLE devices, streamlining deployment and operations for devices that typically lack direct cloud integration. Developers can now manage, update, and monitor BLE-based device fleets with the same ease and infrastructure used for other connected hardware. This also extends all of Golioth’s <a href="https://golioth.io/ai">AI</a> capabilities to unlock new Edge and Physical AI use cases.<img src="https://blog.golioth.io/wp-content/uploads/2025/08/networks-1030x519.gif" alt=""></p>
<h3 id="gateways">Gateways</h3>
<p>To help developers get started quickly, Golioth provides reference implementations that use popular development boards configured as BLE gateways. These reference gateways include both pre-compiled binaries and fully open source firmware, available on
<a href="https://github.com/golioth/bluetooth-gateway">GitHub</a>.</p>
<ul>
<li>NXP FRDM-RW612</li>
<li>Nordic Semiconductor Thingy:91 X</li>
<li>Nordic Semiconductor nRF9160DK These boards offer a fast path for evaluating BLE gateway functionality and provide a foundation for building custom gateway implementations. In addition to reference hardware, Golioth is partnering with leading providers to offer off-the-shelf BLE gateways that are pre-validated to work with Golioth Connectivity, including</li>
</ul>
<p><a href="https://ezurio.com/">Ezurio</a>, <a href="https://www.arduino.cc/">Arduino</a>, <a href="https://globalscaletechnologies.com/">Globalscale Technologies</a>, <a href="https://www.minew.com/">Minew</a> &amp; <a href="https://www.device-solutions.com/">Device Solutions</a>.<img src="https://blog.golioth.io/wp-content/uploads/2025/08/gateway-partners-open-beta.png" alt=""> These ready-made options give teams the flexibility to align their gateway choice with their product architecture and deployment needs. At the same time, they allow developers to focus on building their Bluetooth-based product rather than reinventing the wheel by creating gateway infrastructure from scratch. Our users can build upon the Reference Implementation in the <code>bluetooth_gateway</code> repository and create their own production-grade gateway. Golioth’s in-house prototyping platform <a href="https://blog.golioth.io/creating-an-off-grid-solar-powered-bluetooth-to-cloud-gateway/">the Aludel Elixir will be a showcase of how to do that on custom hardware</a>.</p>
<h2 id="building-a-bluetooth-device">Building a Bluetooth device</h2>
<p>We have shown over the past few month a range of different development kits working as  nodes with our Bluetooth support (then called Bluetooth-to-Cloud). This includes hardware from
<a href="https://forum.golioth.io/t/enabling-espressif-parts-with-golioth-s-bluetooth-support/1650">Espressif</a>, <a href="https://www.nordicsemi.com/">Nordic Semiconductor</a>, <a href="https://forum.golioth.io/t/enabling-nxp-parts-with-golioth-s-bluetooth-support/1649">NXP</a>, <a href="https://forum.golioth.io/t/enabling-renesas-dialog-parts-with-golioths-bluetooth-support/1651">Renesas</a>, <a href="https://forum.golioth.io/t/enabling-silabs-parts-with-golioths-bluetooth-support/1648">Silicon Labs</a> &amp; <a href="https://forum.golioth.io/t/enabling-st-parts-with-golioths-bluetooth-support/1642">STMicroelectronics</a>. Each of those links are forum posts that will track any required changes to get various development boards working, and offer a place to discuss issues getting hardware booted up on Golioth.<img src="https://blog.golioth.io/wp-content/uploads/2025/08/bluetooth-devkits-open-beta.png" alt=""> We have ongoing reference hardware and implementations that will grace the pages of this blog, such as the recently announced <a href="https://blog.golioth.io/open-sourcing-the-tikk-demo-board/">Tikk add-on board</a>.</p>
<h2 id="pricing">Pricing</h2>
<p>During the open beta, users can add unlimited BLE devices at no extra cost. This is a great time to test BLE deployments and explore Golioth Connectivity without device-based fees. Standard platform and service usage fees still apply. See the
<a href="https://golioth.io/pricing">pricing page</a> for details. Once Golioth Connectivity reaches General Availability, pricing will apply to BLE devices that use an advanced feature of Bluetooth Connectivity that allows devices to seamlessly utilize multiple gateways for better coverage. We call this feature “roaming.” After GA, the rate for devices that roam is $0.50 per device, with volume discounts available for larger deployments.</p>
<h2 id="get-started-today">Get started today</h2>
<p>If you’re ready to explore Bluetooth support with Golioth Connectivity, there are several ways to dive in:</p>
<ul>
<li>Review the <a href="https://docs.golioth.io/connectivity">Golioth Connectivity documentation</a> for detailed technical guides and integration patterns</li>
<li>Try out our <a href="https://github.com/golioth/bluetooth-gateway">reference gateway firmware</a> to quickly stand up a BLE gateway integration</li>
<li>Choose from a supported board to begin prototyping</li>
<li>Browse the <a href="https://docs.golioth.io/connectivity">docs</a></li>
<li>Explore one of the <a href="https://forum.golioth.io/c/golioth-guides/bluetooth/33">sample projects</a> As you get started with managing BLE devices during our open beta, we’d love to hear from you. Your feedback is critical as we refine the experience and work toward general availability. Join the conversation and share your questions, comments, or suggestions in the</li>
</ul>
<p><a href="https://forum.golioth.io/">Golioth Forum</a>. Your input directly helps shape the future of Golioth Connectivity.</p>
]]></content:encoded></item><item><title>Open sourcing the Tikk demo board</title><link>https://blog.golioth.io/open-sourcing-the-tikk-demo-board/</link><guid>https://blog.golioth.io/open-sourcing-the-tikk-demo-board/</guid><pubDate>Thu, 21 Aug 2025 17:40:53 +0000</pubDate><dc:creator>Chris Gammell</dc:creator><category>Bluetooth</category><category>Hardware</category><category>Solutions</category><description>In my many years of making demonstrations, I have found that blinky almost always wins. LEDs naturally draw the eye. With the upcoming release of Golioth’s Bluetooth-to-Cloud, I knew we would also need to create a large amount of node devices to showcase many devices connecting through a gateway. I wanted a reasonable per-node cost, and interesting input and output capabilities. So I did what I normally do, despite it not _always _being the best idea: I fired up KiCad and started creating a new design. As luck would have it, I found a design that has a potential to make a versatile, compact node for demonstrating Bluetooth control and communication to the cloud. As we’ve done with our other hardware at Golioth, today we’re open sourcing this design as well. Check out the Tikk hardware design on GitHub or view the files directly on KiCanvas</description><content:encoded><![CDATA[<p>In my many years of making demonstrations, I have found that blinky almost always wins. LEDs naturally draw the eye. With the upcoming release of <a href="https://blog.golioth.io/bluetooth-to-cloud/">Golioth’s Bluetooth-to-Cloud</a>, I knew we would also need to create a large amount of node devices to showcase many devices connecting through a gateway. I wanted a reasonable per-node cost, and interesting input and output capabilities. So I did what I normally do, despite it not _always _being the best idea: I fired up KiCad and started creating a new design. As luck would have it, I found a design that has a potential to make a versatile, compact node for demonstrating Bluetooth control and communication to the cloud. As we’ve done with our other hardware at Golioth, today we’re open sourcing this design as well. Check out <a href="https://github.com/golioth/tikk-hw">the Tikk hardware design on GitHub</a> or <a href="https://kicanvas.org/?github=https%3A%2F%2Fgithub.com%2Fgolioth%2Ftikk-hw">view the files directly on KiCanvas</a><img src="https://blog.golioth.io/wp-content/uploads/2025/08/tikk-640x360-1.gif" alt=""></p>
<h2 id="start-from-low-cost-ecosystems">Start from low cost ecosystems</h2>
<p>There have been standardized form factors since before Arduino popped on the scene with the Uno header in 2007. In that time, electronics costs have dropped through the floor. We continue to relish in these ad hoc standards, adopting things like the MikroBus Click header standard on
<a href="https://blog.golioth.io/all-golioth-hardware-is-now-open-source/">our Aludel Elixir board</a> (also open source) and utilizing Uno-to-Click converters to make those same designs even more accessible with our “Follow Along Hardware”, which uses a range of development kits and Zephyr’s flexible pin assignments to adopt hardware to a range of vendor hardware. For this design, I wanted to go smaller. Not quite as small as on <a href="https://blog.golioth.io/designing-and-building-an-airtag-clone-a-new-series-from-golioth/">the AirTag clone (Orleon)</a>, but smaller than an Uno or Click header would allow. Instead, I was clued into <a href="https://www.aliexpress.us/item/3256807958420169.html">a ProMicro clone that includes the nRF52840 chip</a>, one of the many that works with Zephyr and Golioth’s Bluetooth-to-Cloud. Surprisingly, the board can be shipped to a house in the US from a factory in China with tariffs included (somehow) for $3. OK, you need to buy two, but that’s fine with me, I bought a lot more than that. Why this low price point? They’re targeting the keyboard builders of the world, many of whom adopted the ProMicro form factor (rumor has it that some keyboard enthusiasts roam the virtual halls of Golioth, as well!). This board has USB, the chip and RF front end/passives, battery charging, power regulation/control, status LEDs, and 26 breakout pins in a 2×13 pattern. That’s enough for just about anything I might want to do with tiny boards, though the board maker did leave a lot of pins unused on the nRF52840 (I hate to see unused pins, there are just so many on that part!) What this board didn’t have? Much of anything interesting to alert a user that <em>things were happening</em> inside the Bluetooth part. So let’s build a demo board to attach to it.</p>
<h2 id="how-many-leds">How many LEDs?</h2>
<p>Sure, blinking an LED from the cloud is cool and impressive when transiting the various systems we transit. But what if we blinked a
_lot _of LEDs? And put them all in order. Perusing the available chips from LCSC (more on that later), I found a familiar chipset: the <a href="https://lumissil.com/assets/pdf/core/IS31FL3731_DS.pdf">IS31FL3731</a>. It’s a 9×16 LED matrix display with an optional audio input that can match the pulse of input audio. It’s targeted at EQ displays for stereos, but also makes a fun way to display text, graphics, and whatever else you can dream up…but maintaining that “Times Square in the 80s” kind of look. This board has 105 LEDs in a 7×15 matrix, basically the most I could fit onboard with 0402 LEDs in the available space. The 7 pixels high is a crucial number to fit a minimalist 7×5 pixel font size. The I2C driver allows control of individual LEDs and has framebuffers to allow animations without regular interaction with the driver chip; you set up the frames and away you go. Easier said than done, but Mike Szczys made short work of the driver in Zephyr and plans to upstream it at some point as well.</p>
<h2 id="an-exercise-in-lower-cost">An exercise in low(er) cost</h2>
<p>In the face of US tariffs, getting any custom electronics made right now can be tricky. The supply chain is inexorably tied to other countries, especially China, even if we decided to do domestic production. I couldn’t find what I needed on US marketplaces at the price point I wanted, so instead I decided to lean into lower cost parts (LCSC) and assembly (JLC) and then pay the tariffs. As such, the parts on this board were largely informed by what I could get on that marketplace and what fit onto the somewhat limited size of the board. Here are the features/part names:</p>
<ul dir="auto">
  <li>
    105 LEDs in 0402 format, individually addressable in a 7×15 matrix with the IS31FL3731
  </li>
  <li>
    Two H-Bridge chips (L9110S) for driving a single stepper or two dc motors
  </li>
  <li>
    3 axis accelerometer (LIS2DH12)
  </li>
  <li>
    Temperature sensor (TMP102)
  </li>
  <li>
    QWIIC / Stemma header to expand breakout boards*
  </li>
  <li>
    User button
  </li>
  <li>
    Reset button
  </li>
  <li>
    A voltage divider that monitors battery voltage
  </li>
</ul> Additional capabilities added by the Tenstar ProMicro and nRF52840 
<ul dir="auto">
  <li>
    Additional GPIOs I can solder to (5 digital, one analog)
  </li>
  <li>
    LiPo battery charger on board
  </li>
  <li>
    Load switch for all peripherals except motor driver
  </li>
  <li>
    USB Input to the nRF52840 (get serial terminal using USB-CDC)
  </li>
  <li>
    NFC tag emulation (via the nRF52840 and onboard flex connector)
  </li>
</ul>
<h2 id="shield-your-eyes-demos-are-on-the-way">Shield your eyes, demos are on the way</h2>
<p>This board will highlight the various Golioth Bluetooth-to-Cloud capabilities by making it blindingly obvious when a configuration change has been sent down to a device. The less outwardly obvious demonstrations like OTA and Cloud side data will follow our normal display format, but are just as intriguing when done over a Bluetooth link without any custom code on the intermediary device. Stay tuned for more demos here or reach out on the forum to let us know what we should build!
<img src="https://blog.golioth.io/wp-content/uploads/2025/08/tikk-640x360-1.gif" alt=""> <em>* As happens with some Rev A boards, the QWIIC header got miswired, so this is currently backwards (but fixable with a cable mod) and <a href="https://github.com/golioth/tikk-hw/issues/1">will be fixed in Rev B</a></em></p>
]]></content:encoded></item><item><title>Adding Espressif ESP32 to the Golioth Bluetooth-to-Cloud Service</title><link>https://blog.golioth.io/adding-espressif-esp32-to-the-golioth-bluetooth-to-cloud-service/</link><guid>https://blog.golioth.io/adding-espressif-esp32-to-the-golioth-bluetooth-to-cloud-service/</guid><pubDate>Tue, 19 Aug 2025 15:01:04 +0000</pubDate><dc:creator>Mike Szczys</dc:creator><category>Bluetooth</category><description> 💡 Golioth Bluetooth-to-Cloud is now known as Golioth Connectivity, which is now out of private access. For ongoing support of ESP32 components, we are tracking changes in a thread on the Golioth forum specific to Espressif parts. Note that this project also uses the previous manner of validating devices onto the network, whereas new devices require X.509 certificates. Oh my, we almost forgot about Espressif! I’m so often using the ESP32 family for WiFi-enabled projects that I haven’t yet added it to the Golioth Bluetooth IoT fleet. That’s the goal today, connecting an ESP32-S3 up to the Internet via Bluetooth (instead of WiFi). Connecting any Bluetooth device to an IoT fleet is made easy by the Bluetooth-to-Cloud capabilities of Golioth. For this approach, we use a Gateway to bridge between the resource-constrained BLE devices to the cloud. The Bluetooth devices themselves just advertise a characteristic that indicates they are using the Pouch protocol. This delivers bi-directional access to a huge number of devices that are usually considered to be connectionless.</description><content:encoded><![CDATA[<div class="golioth-callout-banner">
  💡 Golioth Bluetooth-to-Cloud is now known as <a href="https://blog.golioth.io/bluetooth-open-beta/">Golioth Connectivity</a>, which is now out of private access. For ongoing support of ESP32 components, we are tracking changes in <a href="https://forum.golioth.io/t/enabling-espressif-parts-with-golioth-s-bluetooth-support/1650">a thread on the Golioth forum specific to Espressif parts</a>. Note that this project also uses the previous manner of validating devices onto the network, whereas new devices require X.509 certificates.
</div> Oh my, we almost forgot about Espressif! I’m so often using the ESP32 family for WiFi-enabled projects that I haven’t yet added it to the Golioth Bluetooth IoT fleet. That’s the goal today, connecting an ESP32-S3 up to the Internet via Bluetooth (instead of WiFi). Connecting any Bluetooth device to an IoT fleet is made easy by 
<p><a href="https://blog.golioth.io/bluetooth-to-cloud/">the Bluetooth-to-Cloud capabilities of Golioth</a>. For this approach, we use a Gateway to bridge between the resource-constrained BLE devices to the cloud. The Bluetooth devices themselves just advertise a characteristic that indicates they are using <a href="https://github.com/golioth/pouch">the Pouch protocol</a>.  This delivers bi-directional access to a huge number of devices that are usually considered to be connectionless.</p>
<h2 id="using-the-esp32-with-zephyr">Using the ESP32 with Zephyr</h2>
<p>Today we’re using Zephyr because that’s what’s currently supported in the Golioth Bluetooth fleet examples. However, Pouch is designed to work cross-platform so with a bit of implementation work this is also possible for ESP-IDF.
<a href="https://blog.golioth.io/wp-content/uploads/2025/08/espressif-esp32s3-with-LED.jpg"><img src="https://blog.golioth.io/wp-content/uploads/2025/08/espressif-esp32s3-with-LED-1030x701.jpg" alt="Black development board on a wooden desktop with a blue LED illuminated."></a>Espressif maintains support for the ESP32 family in Zephyr, so building for this ESP32-S3 is trivial when you already have Zephyr installed on your development computer. I found that all I needed to do was add <code>hal_espressif</code> to my allow-list and run a <code>west</code> command to fetch the binary blobs.</p>
<h2 id="building-golioths-bluetooth-to-cloud-example-to-add-the-esp32-s3-to-my-iot-fleet-i-followed-the-instructions-from"><a id="building-golioths-bluetooth-to-cloud-example" class="aal_anchor" href="https://blog.golioth.io/enabling-bluetooth-to-cloud-on-the-renesas-da14695/#building-golioths-bluetooth-to-cloud-example" aria-hidden="true"></a>Building Golioth’s Bluetooth-to-Cloud Example To add the ESP32-S3 to my IoT fleet I followed the instructions from</h2>
<p><a href="https://github.com/golioth/ble-private-access">the Golioth Bluetooth-to-Cloud private access repository</a>. The example application uses a west manifest with an allow-list to limit the number of libraries that are cloned during initialization. I updated this file to include the Espressif HAL library.</p>
<pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-highlight="17" data-enlighter-group="1" data-enlighter-title="pouch/west.yml"># Copyright (c) 2025 Golioth, Inc.
# SPDX-License-Identifier: Apache-2.0

manifest:
  version: 0.8

  projects:
    - name: zephyr
      revision: v4.1.0
      url: https://github.com/zephyrproject-rtos/zephyr
      west-commands: scripts/west-commands.yml
      import:
        path-prefix: deps
        name-allowlist:
          - zephyr
          - cmsis
          - hal_expressif
          - hal_nordic
          - mbedtls
          - mcuboot
          - segger
          - tfm-mcuboot
          - trusted-firmware-m
          - zcbor</pre> Don’t forget to update after making changes to your manifest. Now is also a great time to ensure you have the binary blobs on hand. 
<pre class="EnlighterJSRAW" data-enlighter-language="generic">west update
west blobs fetch hal_espressif</pre> By default, the sample application allows you to change the state of an LED attached to the board by issuing settings commands from the cloud. However, the ESP32-S3 doesn’t have an on-board LED. Without one, the sample code will fail to build. So I connected an LED (and current limiting resistor) between GPIO14 and Ground. Add an overlay file to map this additional hardware in the devicetree. 
<pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-group="2" data-enlighter-title="pouch/examples/ble_gatt/boards/esp32s3_devkitc_esp32s3_procpu.overlay">/ {
    leds {
        compatible = "gpio-leds";

        led0: led_0 {
            gpios = <&gpio0 14 GPIO_ACTIVE_HIGH>;
        };
  };

    aliases {
        led0 = &led0;
    };
};</pre> Now we can build and flash the application. Note that I’ve replaced the 
<p><code>REPLACEME</code> text with the Device ID from the <a href="https://console.golioth.io/">Golioth console</a>.</p>
<pre class="EnlighterJSRAW" data-enlighter-language="generic">west build -p -b esp32s3_devkitc/esp32s3/procpu pouch/examples/ble_gatt -- -DCONFIG_EXAMPLE_DEVICE_ID=\"REPLACEME\"
west flash</pre> As I mentioned, I already have a gateway device running. If you have not yet done so, 
<p><a href="https://github.com/golioth/ble-private-access#getting-started">follow steps 1 and 2 from the getting started guide</a>. After a few seconds, the device will start advertising and sending data up to the cloud!</p>
<h2 id="topology-map-showing-an-esp32s3_explore-both-uplink-and-downlink-the-example-code-sends-simulated-sensor-data-from-the-device-to-the-cloud-from-the-golioth-web-console-we-can-use-the-lightdb-stream-tab-to-view-the-sensor-readings-received-on-the-cloud"><a href="https://blog.golioth.io/wp-content/uploads/2025/08/golioth_bluetooth_topology_esp32s3.jpg"><img src="https://blog.golioth.io/wp-content/uploads/2025/08/golioth_bluetooth_topology_esp32s3.jpg" alt="Topology map showing an esp32s3_devkitc connecting to a Golioth BLE gateway."></a>Explore both Uplink and Downlink The example code sends simulated sensor data from the device to the cloud. From the Golioth web console, we can use the LightDB Stream tab to view the “sensor” readings received on the cloud.</h2>
<p><a href="https://blog.golioth.io/wp-content/uploads/2025/08/esp32s3-lightdb-stream-temperature-data.png"><img src="https://blog.golioth.io/wp-content/uploads/2025/08/esp32s3-lightdb-stream-temperature-data.png" alt=""></a>I didn’t actually connect a hardware sensor up to the device, so we’re only going to receive 22C for every reading as long as this device is powered on. However, as covered in the previous step, I did add an LED to this board.</p>
<div id='gallery-12' class='gallery galleryid-9937 gallery-columns-2 gallery-size-large'>
  <figure class='gallery-item'> 
  <div class='gallery-icon landscape'>
    <a href='https://blog.golioth.io/adding-espressif-esp32-to-the-golioth-bluetooth-to-cloud-service/led-added-to-esp32s3-dev-board/'>![An LED and resistor connected to an esp32s3 dev board.](https://blog.golioth.io/wp-content/uploads/2025/08/LED-added-to-esp32s3-dev-board-1030x773.jpg)</a>
  </div></figure><figure class='gallery-item'> 
  <div class='gallery-icon landscape'>
    <a href='https://blog.golioth.io/adding-espressif-esp32-to-the-golioth-bluetooth-to-cloud-service/led-illuminated-via-an-esp32s3-dev-board/'>![Blue LED illuminated](https://blog.golioth.io/wp-content/uploads/2025/08/LED-illuminated-via-an-esp32s3-dev-board-1030x773.jpg)</a>
  </div></figure>
</div> We can use the 
<p><a href="https://docs.golioth.io/device-management/settings/">Golioth Settings service</a> to change the state of this LED from the Cloud. Settings can changed at the Project or Device level. Here I’ve edited the device-specific setting and a few seconds later the LED lights up!</p>
<h2 id="golioth-web-console-showing-the-settings-page-with-an-led-truefalse-optionthe-internet-is-for-bluetooth-too-your-fleet-of-ble-devices-can-easily-gain-bi-directional-access-to-the-internet-using"><a href="https://blog.golioth.io/wp-content/uploads/2025/08/golioth-settings-page-led-state.png"><img src="https://blog.golioth.io/wp-content/uploads/2025/08/golioth-settings-page-led-state.png" alt="Golioth web console showing the Settings page with an LED true/false option."></a>The Internet is for Bluetooth Too! Your fleet of BLE devices can easily gain bi-directional access to the Internet using</h2>
<p><a href="https://blog.golioth.io/bluetooth-to-cloud/">Golioth’s Bluetooth-to-Cloud service</a>. Just add a gateway that can be used by all devices within range and you’ve immediately unlocked a world of functionality.</p>
]]></content:encoded></item><item><title>Creating an off-grid, solar powered Golioth Connectivity (Bluetooth) gateway</title><link>https://blog.golioth.io/creating-an-off-grid-solar-powered-golioth-connectivity-bluetooth-gateway/</link><guid>https://blog.golioth.io/creating-an-off-grid-solar-powered-golioth-connectivity-bluetooth-gateway/</guid><pubDate>Thu, 14 Aug 2025 16:00:19 +0000</pubDate><dc:creator>Chris Gammell</dc:creator><category>Bluetooth</category><description>What happens when you’re ready to utilize Bluetooth-to-Cloud, but are far from a power point and network connection and want to ensure your Bluetooth nodes’ data still shows up on the cloud? Utilize the power of the sun! I combined a range of different Golioth capabilities and examples to not only power a device, but also monitor the received solar energy. Let’s take a look at how it all fits together.</description><content:encoded><![CDATA[<p>What happens when you’re ready to utilize <a href="https://blog.golioth.io/bluetooth-to-cloud/">Bluetooth-to-Cloud</a>, but are far from a power point and network connection and want to ensure your Bluetooth nodes’ data still shows up on the cloud? Utilize the power of the sun!<img src="https://blog.golioth.io/wp-content/uploads/2025/08/power-of-the-sun.gif" alt=""> I combined a range of different Golioth capabilities and examples to not only power a device, but also monitor the received solar energy. Let’s take a look at how it all fits together.<img src="https://blog.golioth.io/wp-content/uploads/2025/08/Solar-Bluetooth-Cellular-Gateway-1030x650.jpg" alt=""></p>
<h2 id="solar--cellular--bluetooth-hardware">Solar + Cellular + Bluetooth Hardware</h2>
<p>This setup is built on top of
<a href="https://github.com/golioth/elixir-hw">the Aludel Elixir, our open source hardware cellular platform</a>. It takes advantage of the multiple types of connectivity onboard, including the Nordic Semiconductor nRF9160 cellular modem and the Espressif ESP32-C3. The former is the main processor running Zephyr (more on that below) and the latter is set up as an HCI interface for Bluetooth connections. There are also two Mikrobus Click slots onboard and we have a VCP Click plugged in there. That is an <a href="https://www.ti.com/lit/ds/symlink/ina260.pdf?ts=1755147989662">INA260 chip from Texas Instruments</a>, the same that we used on the DC Power Monitor Reference Design to measure incoming DC power. <figure id="attachment_9917" aria-describedby="caption-attachment-9917" style="width: 696px" class="wp-caption aligncenter"><img src="https://blog.golioth.io/wp-content/uploads/2025/08/Solar-Bluetooth-Gateway-Guts-1030x812.jpg" alt=""><figcaption id="caption-attachment-9917" class="wp-caption-text">Aludel Elixir with a VCP monitor click plugged into slot 1</figcaption></figure> I wired up a 6V solar panel through the VCP click, though I would do better with a 12V panel; the click can handle up to 36V relative to its ground and the high voltage regulator is rated for 5.5V to 40V on the Elixir. That means we only are really charging the system with this particular panel in full sun, and there’s no real notion of MPPT or other energy harvesting. The panel is plugged into the “supply” terminal block inputs of the click, and the “load” terminal block outputs are wired over to the high voltage input. That high voltage buck regulator powers the system and also charges the battery. The battery fuel gauge is a secondary method of determining usage by the system.</p>
<h2 id="firmware-required-on-the-bluetooth-gateway">Firmware required on the Bluetooth gateway</h2>
<p>A key enablement for this design is
<a href="https://blog.golioth.io/the-esp32-hci-makes-any-zephyr-board-a-bluetooth-gateway/">Mike’s recent work with the ESP32-C3 Bluetooth HCI</a>, effectively turning that segment of the hardware into a ‘bluetooth modem’. Other gateways that we create also use HCI, which is a bit of an equalizer in Zephyr. We can change out the chosen HCI and assign the pins to swap over to using this new HCI.</p>
<pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-group="overlay_1" data-enlighter-title="aludel_elixir_nrf9160_ns.overlay" data-enlighter-linenumbers="false">/ {
    chosen {
        zephyr,bt-hci = &bt_hci_uart;
    };
};

&uart1 {
    status = "okay";
    current-speed = <115200>;
    pinctrl-0 = <&esp_uart_default>;
    pinctrl-1 = <&esp_uart_sleep>;
    pinctrl-names = "default", "sleep";

    bt_hci_uart: bt_hci_uart {
        compatible = "zephyr,bt-hci-uart";
        status = "okay";
    };
};</pre> I did all of this in the 
<p><code>bluetooth-gateway</code> repository, which is a Zephyr based repository that is yet to be released for the general public. Doing so allowed me to add new compatibilities for the Aludel Elixir, much of which mirrors the currently supported nRF9160-DK. The current Bluetooth gateway firmware has an instance of “<a href="https://github.com/golioth/pouch">pouch</a>“–our new enabling capability for data to transit over transports like BLE GATT–and <a href="https://github.com/golioth/golioth-firmware-sdk/">our firmware SDK</a>–which allows the gateway to bounce those packetized pieces of data up to the cloud. The piece that is not in the standard firmware is sending up generic sensor data through from the gateway itself. I can imagine gateways that might have light or temperature sensors could be standard measurements any user would want to send up, but in our case, I want to send up voltage, current, and power coming from the solar panel. First, I need to process the sensor data coming from the INA260. Since that sensor isn’t in-tree in Zephyr, I instead pulled the driver code from our <a href="https://projects.golioth.io/reference-designs/dc-power-monitor/">DC Power Monitor reference design</a>. We implemented it differently back when that RD was created, so instead Mike helped me to transition it over to being a Zephyr module.<img src="https://blog.golioth.io/wp-content/uploads/2025/08/INA260-Zephyr-Module.png" alt=""> I pulled in <code>app_sensors.c</code>, a file that we normally have in our Reference Designs and one that sits completely separate from all of the Bluetooth stuff. In <code>main.c</code>, when we call the initialization function, I am passing the Golioth client object, so that we have access to all of the normal goodness that the Golioth Firmware SDK enables. In this case, I want to be able to send up asynchronous stream data that will then get routed through Golioth Pipelines to LightDB Stream, our internal timeseries database. So we’re initializing the Stream capabilities, calling the sensor driver to read from the INA260, formatting that data into something human readable, and then passing it to Golioth with the Stream functionalities. This lives in perfect harmony with the Bluetooth capabilities that are processing incoming Pouch packets and shuttling those up to a different destination on the Golioth cloud for processing and decryption.</p>
<h2 id="sunshine-sensor-data-on-the-cloud">Sunshine sensor data on the Cloud</h2>
<p>Once the solar panel’s readings are sent up to the cloud, I send those through a simple Pipeline to LightDB Stream
<img src="https://blog.golioth.io/wp-content/uploads/2025/08/Sensor-CBOR-Pipeline-1030x697.png" alt=""> The data looks like this<img src="https://blog.golioth.io/wp-content/uploads/2025/08/Solar-LightDB-Stream-Data-1030x662.png" alt=""> During editing of this article, my coworker Mike pointed out that I was actually sending up the raw readings, which have a conversion factor as explained in <a href="https://www.ti.com/lit/ds/symlink/ina260.pdf">the INA260 datasheet</a>. There was a different function that makes this correction, but I will do that correction using the images above and below: <a href="https://www.ti.com/lit/ds/symlink/ina260.pdf"><img src="https://blog.golioth.io/wp-content/uploads/2025/08/INA260_Conversion.png" alt=""></a> So the readings are actually 5275 * 1.25 mV = <strong>6.594V for the voltage</strong>, 128 * 1.25 mA = <strong>160 mA for the current</strong>, and 90 * 10 mW = <strong>900 mW for the power</strong>. The last one seems to have some non-linearity on the chip output because 6.594V * 0.160 A = 1.05W. Note that the current (“cur”) only goes above zero when the voltage (“vol”) is up above 5.5V (ie. the middle reading 4165 + 1.25 mV = 5.2V is less than the required 5.5V). That’s due to the input cutoff of the high power buck regulator, and why I said a 12V panel would generally operate better.</p>
<h2 id="what-else-can-you-put-on-your-gateway">What else can you put on your gateway?</h2>
<p>This demo is a great example of the power of an embedded processing gateway: not only is it easy to add on additional capabilities for sensors connected to the gateway, it also operates at low power envelopes. When the sun
<em>isn’t</em> shining, that means we can operate for longer or with smaller on-board batteries. This design isn’t super optimized for low power, but <a href="https://blog.golioth.io/upcoming-webinar-batteryless-cellular-iot-with-conexio/">we have done webinars recently about enabling “batteryless” sensors</a>…perhaps we’ll be able to do the same for gateways next? We are still in <a href="https://blog.golioth.io/bluetooth-to-cloud/">Private Access for our Bluetooth-to-Cloud capabilities</a>, but joining simply requires a short application. We can’t wait to see what you build!</p>
]]></content:encoded></item><item><title>Adding SiLabs EFR32BG27 to the Golioth Bluetooth-to-Cloud Service</title><link>https://blog.golioth.io/adding-silabs-efr32bg27-to-the-golioth-bluetooth-to-cloud-service/</link><guid>https://blog.golioth.io/adding-silabs-efr32bg27-to-the-golioth-bluetooth-to-cloud-service/</guid><pubDate>Tue, 12 Aug 2025 14:11:27 +0000</pubDate><dc:creator>Mike Szczys</dc:creator><category>Bluetooth</category><description> 💡 Golioth Bluetooth-to-Cloud is now known as Golioth Connectivity, which is now out of private access. For ongoing support of SiLabs components, we are tracking changes in a thread on the Golioth forum specific to SiLabs parts. If there’s one thing we love doing, it’s adding new chips to an IoT fleet. The Golioth Bluetooth-to-Cloud service is so hot right now because if your chip speaks BLE, we can connect it to the cloud. The Silicon Labs EFR32BG27 found on the xG27 development kit is the latest Bluetooth offering to be added to a Golioth fleet. Let’s jump in and discuss what it takes to enable SiLabs parts. Using the EFR32BG27 with Zephyr We grabbed the SiLabs xG27 dev kit for this experiment because it already has support in the Zephyr tree. Although support begins with v4.1.0 of Zephyr, our testing showed that for the device to work correctly with our gateways we needed to upgrade to v4.2.0. This board includes an embedded J-Link adapter so you can flash it using the USB-C port and Zephyr’s west flash command. To access the Bluetooth features of the chip, use west to grab the binary blob.</description><content:encoded><![CDATA[<div class="golioth-callout-banner">
  💡 Golioth Bluetooth-to-Cloud is now known as <a href="https://blog.golioth.io/bluetooth-open-beta/">Golioth Connectivity</a>, which is now out of private access. For ongoing support of SiLabs components, we are tracking changes in <a href="https://forum.golioth.io/t/enabling-silabs-parts-with-golioths-bluetooth-support/1648">a thread on the Golioth forum specific to SiLabs parts</a>.
</div> If there’s one thing we love doing, it’s adding new chips to an IoT fleet. The Golioth Bluetooth-to-Cloud service is so hot right now because if your chip speaks BLE, we can connect it to the cloud. The Silicon Labs EFR32BG27 found on the xG27 development kit is the latest Bluetooth offering to be added to a Golioth fleet. Let’s jump in and discuss what it takes to enable SiLabs parts. 
<h2 id="using-the-efr32bg27-with-zephyr">Using the EFR32BG27 with Zephyr</h2>
<p>We grabbed
<a href="https://docs.zephyrproject.org/latest/boards/silabs/dev_kits/xg27_dk2602a/doc/index.html">the SiLabs xG27 dev kit</a> for this experiment because it already has support in the Zephyr tree. Although support begins with v4.1.0 of Zephyr, our testing showed that for the device to work correctly with our gateways we needed to upgrade to v4.2.0. <a href="https://blog.golioth.io/wp-content/uploads/2025/08/silabs-xg27-dev-kit.jpg"><img src="https://blog.golioth.io/wp-content/uploads/2025/08/silabs-xg27-dev-kit-1030x690.jpg" alt="Black rectangular development board about the size of two postage stamps with a white Silicon Labs logo on it."></a>This board includes an embedded J-Link adapter so you can flash it using the USB-C port and Zephyr’s <code>west flash</code> command. To access the Bluetooth features of the chip, use <code>west</code> to grab the binary blob.</p>
<pre class="EnlighterJSRAW" data-enlighter-language="generic">west blobs fetch hal_silabs</pre> That takes care of the BLE prerequisites. I find that 
<p><a href="https://github.com/zephyrproject-rtos/zephyr/tree/main/samples/bluetooth/observer">the Zephyr Bluetooth Observer sample</a> is really handy when testing out new chips, so I built and ran that sample to confirm the radio was working as expected. Now it’s time to add this to our IoT fleet!</p>
<h2 id="building-golioths-bluetooth-to-cloud-example-to-get-the-silabs-efr32bg27-talking-to-the-cloud-i-followed-the-instructions-from-the"><a id="building-golioths-bluetooth-to-cloud-example" class="aal_anchor" href="https://blog.golioth.io/enabling-bluetooth-to-cloud-on-the-renesas-da14695/#building-golioths-bluetooth-to-cloud-example" aria-hidden="true"></a>Building Golioth’s Bluetooth-to-Cloud Example To get the SiLabs EFR32BG27 talking to the cloud I followed the instructions from the</h2>
<p><a href="https://github.com/golioth/ble-private-access">Golioth Bluetooth-to-Cloud connectivity</a> repository. The <a href="https://github.com/golioth/pouch/tree/6de6f17dca55890d91f671c68a5096a5b7785abf/examples/ble_gatt">example application</a> is found in the Pouch non-IP protocol repository that is included as a submodule. I just needed to make a few updates to the <code>pouch/west.yml</code> manifest file in that repo so it will work with SiLabs parts:</p>
<ol>
<li>Use Zephyr v4.2.0</li>
<li>Add the SiLabs HAL library to the allowlist</li>
<li>Add <code>cmsis_6</code> to the allowlist</li>
</ol>
<pre class="EnlighterJSRAW" data-enlighter-language="yaml" data-enlighter-highlight="9, 17, 19" data-enlighter-group="1" data-enlighter-title="pouch/west.yml"># Copyright (c) 2025 Golioth, Inc.
# SPDX-License-Identifier: Apache-2.0

manifest:
  version: 0.8

  projects:
    - name: zephyr
      revision: v4.2.0
      url: https://github.com/zephyrproject-rtos/zephyr
      west-commands: scripts/west-commands.yml
      import:
        path-prefix: deps
        name-allowlist:
          - zephyr
          - cmsis
          - cmsis_6
          - hal_nordic
          - hal_silabs
          - mbedtls
          - mcuboot
          - segger
          - tfm-mcuboot
          - trusted-firmware-m
          - zcbor</pre> Don’t forget to run 
<p><code>west update</code> after making these changes to pull in the new version and new library. Here’s what that looks like, including the step to download binary blobs.</p>
<pre class="EnlighterJSRAW" data-enlighter-language="generic">west update
west blobs fetch hal_silabs</pre> Next, build the example application. Don’t forget to replace the 
<p><code>REPLACEME</code> part with the Device ID from the <a href="https://console.golioth.io">Golioth console</a>.</p>
<pre class="EnlighterJSRAW" data-enlighter-language="generic">west build -p -b xg27_dk2602a pouch/examples/ble_gatt -- -DCONFIG_EXAMPLE_DEVICE_ID=\"REPLACEME\"
west flash</pre> You should have already set up your gateway by 
<p><a href="https://github.com/golioth/ble-private-access#getting-started">following steps 1 and 2 from the getting started guide</a>. If so, your newly flashed SiLabs board will soon be reporting into the cloud!</p>
<p><a href="https://blog.golioth.io/wp-content/uploads/2025/08/golioth-console-showing-silabs-board.png"><img src="https://blog.golioth.io/wp-content/uploads/2025/08/golioth-console-showing-silabs-board-1030x601.png" alt="Golioth web console showing the SiLabs xg27_dk2602a connecting to the Gateway."></a></p>
<h2 id="go-deeper-with-the-silabs-xg27">Go Deeper with the SiLabs xG27</h2>
<p>By default the Golioth example sends simulated temperature sensor data. But the xG27 dev board has a Si7210 sensor that includes a temperature reading. It’s far more fun to send actual sensor readings to the cloud so let’s update the sample to read that sensor! First, create an overlay file to enable the sensor node which is already part of the in-tree Zephyr definition.</p>
<pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-group="2" data-enlighter-title="pouch/examples/ble_gatt/boards/xg27_dk2602a.overlay">&i2c0 {
  si7210 {
    status = "okay";
  };
};</pre> I also added some Kconfig symbols to get this sensor working. The SHELL-related symbols are optional: 
<pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-group="3" data-enlighter-title="pouch/examples/ble_gatt/boards/xg27_dk2602a.conf">CONFIG_I2C=y
CONFIG_I2C_SHELL=y
CONFIG_SENSOR=y
CONFIG_SENSOR_SHELL=y</pre> From there it’s the simple matter of actually reading the sensor and sending it to Golioth. 
<pre class="EnlighterJSRAW" data-enlighter-language="c" data-enlighter-highlight="1-2. 8-19" data-enlighter-group="4" data-enlighter-title="pouch/examples/ble_gatt/src/main.c">const struct device *const temperature_dev = DEVICE_DT_GET_ANY(silabs_si7210);
#include <zephyr/drivers/sensor.h>

static void pouch_event_handler(enum pouch_event event, void *ctx)
{
    if (POUCH_EVENT_SESSION_START == event)
    {
        sensor_sample_fetch(temperature_dev);
        struct sensor_value temperature;
        sensor_channel_get(temperature_dev, SENSOR_CHAN_AMBIENT_TEMP, &temperature);

        char sbuf[64];
        snprintk(sbuf, sizeof(sbuf), "{\"temp\":%g}", sensor_value_to_double(&temperature));

        pouch_uplink_entry_write(".s/sensor",
                                 POUCH_CONTENT_TYPE_JSON,
                                 sbuf,
                                 strlen(sbuf),
                                 K_FOREVER);

        golioth_sync_to_cloud();
    }

    if (POUCH_EVENT_SESSION_END == event)
    {
        service_data.data.flags = 0x00;
        k_work_schedule(&sync_request_work, K_SECONDS(CONFIG_EXAMPLE_SYNC_PERIOD_S));
    }
}</pre> And we immediately notice actual sensor data streaming in instead of the hardcoded value of 22: 
<p><a href="https://blog.golioth.io/wp-content/uploads/2025/08/silabs-xg27-actual-sensor-data.png"><img src="https://blog.golioth.io/wp-content/uploads/2025/08/silabs-xg27-actual-sensor-data.png" alt="Golioth console showing a simulated temperature of 22 followed by an actual reading of 29.5275"></a></p>
<h2 id="bluetooth-to-cloud-has-never-been-this-easy">Bluetooth to Cloud has never been this easy</h2>
<p>The Golioth Bluetooth-to-Cloud service is incredibly easy to integrate with your low-power devices. <a href="https://blog.golioth.io/bluetooth-to-cloud/">Add a Gateway to your fleet</a> and you will be streaming your sensor data to the cloud right away.</p>
]]></content:encoded></item><item><title>Expanding Golioth Location with Gateway-Based Positioning</title><link>https://blog.golioth.io/expanding-golioth-location-with-gateway-based-positioning/</link><guid>https://blog.golioth.io/expanding-golioth-location-with-gateway-based-positioning/</guid><pubDate>Thu, 07 Aug 2025 13:21:28 +0000</pubDate><dc:creator>Dan Mangum</dc:creator><category>Bluetooth</category><category>LBS</category><category>Location</category><description>We recently announced the general availability of Golioth Location, a comprehensive device positioning service. Less than two weeks later, we have expanded the service to support gateway-based positioning, making it possible to obtain the location of devices that do not have GNSS, cellular, or Wi-Fi capabilities. This is accomplished by resolving the location of an indirectly connected device by the known location of the gateway that delivered data to Golioth on its behalf.</description><content:encoded><![CDATA[<p>We recently announced the <a href="https://blog.golioth.io/golioth-location-ga/">general availability of Golioth Location</a>, a comprehensive device positioning service. Less than two weeks later, we have expanded the service to support gateway-based positioning, making it possible to obtain the location of devices that do not have GNSS, cellular, or Wi-Fi capabilities. This is accomplished by resolving the location of an indirectly connected device by the known location of the gateway that delivered data to Golioth on its behalf.</p>
<h2 id="gateways-and-devices">Gateways and Devices</h2>
<p>We previously defined the distinction between devices and gateways in <a href="https://blog.golioth.io/the-taxonomy-of-connected-device-networks/">The Taxonomy of Connected Device Networks</a>. Gateways deliver data to Golioth, typically over cellular, Wi-Fi, or Ethernet, on behalf of other devices. This is enabled by <code>pouch</code>, a custom protocol we developed that enables devices that can’t communicate directly with cloud services to deliver data via an intermediary (i.e. gateway). One of the most common use cases is <a href="https://en.wikipedia.org/wiki/Bluetooth_Low_Energy">Bluetooth Low Energy (BLE)</a> devices, which can deliver encrypted “pouches” to a gateway, such as a mobile application on a user’s smartphone or an embedded Linux-based single-board computer (SBC), which can then be forwarded along to Golioth over Wi-Fi or cellular. Golioth customers in our <a href="https://blog.golioth.io/bluetooth-to-cloud/">Bluetooth-to-Cloud private access program</a> are using this functionality today. Gateways can leverage Golioth Location’s <a href="https://docs.golioth.io/application-services/location/network-based-positioning">network-based positioning</a> to resolve their location from scanned Wi-Fi access points or nearby cell towers, or, if they are equipped with GNSS capabilities, can obtain their own position <a href="https://docs.golioth.io/application-services/location/tracking">and deliver it to Golioth Location for tracking</a>. Because edge devices must be in relatively close proximity to a gateway in order to communicate through it, Golioth Location is able to approximate the location of the device over time based on the gateways that deliver data on its behalf, regardless of how the gateway obtained its own location.</p>
<h2 id="making-use-of-pipelines">Making Use of Pipelines</h2>
<p>The same <a href="https://docs.golioth.io/data-routing/transformers/location"><code>location</code> transformer</a> that is used for network-based positioning in <a href="https://docs.golioth.io/data-routing">Pipelines</a> can also be used for gateway-based positioning. If network information is provided in the message payload, it will first attempt to resolve using network-based positioning. If it is unsuccessful, or no network information is provided, the transformer will fallback to gateway-based positioning if the message was delivered via a gateway. If the position of the gateway is known, it will be returned as the position of the device. For example, in the following Pipeline, an empty message sent from a device on the <code>/.s/loc</code> path and delivered via a gateway could be routed to the <code>location</code> transformer. The information about the gateway will be automatically passed from the Pipeline context to the transformer. Because no network information is provided, the transformer will look up the location of the gateway, and if known, will return it such that it can be passed along to the <a href="https://docs.golioth.io/data-routing/destinations/location"><code>location</code></a> and <a href="https://docs.golioth.io/data-routing/destinations/lightdb-stream"><code>lightdb-stream</code></a> destinations.</p>
<pre class="EnlighterJSRAW" data-enlighter-language="yaml">filter:
  path: "/loc"
steps:
  - name: resolve-position
    transformer:
      type: location
      version: v1
  - name: store-location
    destination:
      type: location
      version: v1
  - name: stream-location
    destination:
      type: lightdb-stream
      version: v1</pre> Because multiple Pipelines can process the same data, location resolution can be triggered as a side-effect to streaming any data, such as sensor readings or other application data. The content of the message will be discarded by the 
<p><code>location</code> transformer if it does not contain network information. This effectively enables location tracking with zero-byte overhead, just by knowing what gateway a device communicated through.</p>
<h2 id="getting-started">Getting Started</h2>
<p>Gateway-based positioning is available to all users in Pipelines today, but in order to start using gateways you must <a href="https://hslp.golioth.io/bluetooth-cloud-early-access">enroll</a> in the Bluetooth-to-Cloud private access program. After doing so, you’ll be able to start tracking the location of your BLE devices without changing a single line of firmware. If you have any questions or feedback, feel free to reach out to us on the <a href="https://forum.golioth.io/">forum</a>!</p>
]]></content:encoded></item><item><title>How to use Zephyr zbus to Communicate Between Threads</title><link>https://blog.golioth.io/how-to-use-zephyr-zbus-to-communicate-between-threads/</link><guid>https://blog.golioth.io/how-to-use-zephyr-zbus-to-communicate-between-threads/</guid><pubDate>Tue, 05 Aug 2025 14:20:13 +0000</pubDate><dc:creator>Mike Szczys</dc:creator><category>How To</category><description>If you’re not using zbus, you probably should be. As the name indicates, this is the Zephyr bus, a built-in system for publishing, reading, subscribing to, and observing messages in a thread-safe way. It delivers peace of mind with minimal effort. Think of zbus as a collection of data producers and data consumers. One thread might be periodically taking sensor readings (producer). When that thread publishes to a zbus channel, all listeners (consumers) have a callback that runs to process the new message. You could make Golioth Stream service one of your consumers, pushing sensor data to the cloud each time a new zbus message is received. At the same time, another consumer could update your device’s screen with data from each new message. This example may seem trivial, but as your project grows in complexity you’ll be glad you used zbus.</description><content:encoded><![CDATA[<p>If you’re not using zbus, you probably should be. As the name indicates, this is <a href="https://docs.zephyrproject.org/latest/services/zbus/index.html">the Zephyr bus</a>, a built-in system for publishing, reading, subscribing to, and observing messages in a thread-safe way. It delivers peace of mind with minimal effort. Think of zbus as a collection of data producers and data consumers. One thread might be periodically taking sensor readings (producer). When that thread publishes to a zbus channel, all listeners (consumers) have a callback that runs to process the new message. You could make <a href="https://docs.golioth.io/data-routing#streaming-data">Golioth Stream</a> service one of your consumers, pushing sensor data to the cloud each time a new zbus message is received. At the same time, another consumer could update your device’s screen with data from each new message. This example may seem trivial, but as your project grows in complexity you’ll be glad you used zbus.</p>
<h2 id="zbus-overview">Zbus overview</h2>
<p>Your Zbus experience begins by defining a channel. Each channel has a set message type (a struct), a starting message value, and a list of observers. Optionally each channel may specify a function to validate each message before publishing, and a pointer to user data. In addition to the observers that were set up when the channel was defined, a zbus channel may be read from at any time, making the most recently published message data available. Zbus breaks down “observers” in a few different categories. Listeners receive a callback with data from each new message. Subscribers may choose to receive a notification of new message. The subscriber is responsible for choosing when to read the message after receiving the notification. There is also an option for subscribers to receive a copy of each message via a FIFO. There are several benefits to the zbus system. Chief among them is thread-safe data handling. Each channel stores its own message and arbitrates access to the data so that you never have a situation where data is changed while it is being read my another thread. Another notable benefit is the ability for many consumers to read data from a single producer. If you added more threads or functions that need to access a channel message, the new code can read messages from the zbus subsystem directly without needing to change existing code. The rest of this post will walk through how the
<a href="https://github.com/golioth/reference-design-coldchain">Golioth Coldchain Reference Design</a> to uses zbus to:</p>
<ol>
<li>Publish sensor readings on the zbus at 1Hz</li>
<li>When recording location data, read the latest sensor value from zbus</li>
</ol>
<h2 id="enable-the-zbus-subsystem">Enable the zbus subsystem</h2>
<p>As with all Zephyr libraries, use Kconfig to enable zbus by adding the following to your project’s
<code>prj.conf</code> file:</p>
<pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-linenumbers="false">CONFIG_ZBUS=y</pre> Then just add the appropriate header file whenever you need to call zbus functions: 
<pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-linenumbers="false">#include <zephyr/zbus/zbus.h>
</pre>
<h2 id="define-a-zbus-channel">Define a zbus channel</h2>
<p>Each zbus channel has a defined message type so this definition includes a struct to use as the message. The state of the message must be initialized so I fill my sensor readings with obviously invalid data that may be checked for by the consumers.</p>
<pre class="EnlighterJSRAW" data-enlighter-language="generic">struct weather_data {
    struct sensor_value tem;
    struct sensor_value pre;
    struct sensor_value hum;
};

#define ERROR_VAL1 999
#define ERROR_VAL2 999999

ZBUS_CHAN_DEFINE(weather_chan, struct weather_data, NULL, NULL, ZBUS_OBS_DECLARE(),
         ZBUS_MSG_INIT(.tem.val1 = ERROR_VAL1, .tem.val2 = ERROR_VAL2,
                   .pre.val1 = ERROR_VAL1, .pre.val2 = ERROR_VAL2,
                   .hum.val1 = ERROR_VAL1, .hum.val2 = ERROR_VAL2));
</pre> In the code snippet above I’ve named my new zbus channel 
<p><code>weather_chan</code>. The message type for this channel is <code>struct weather_data</code>, an agreed upon format that both the producers and consumers will have access to. I am not using a validator function or user data so both of those params have been set to <code>NULL</code>. In this example I don’t have any observers so I added an empty initializer. The final param is a macro that initializes the channel with my obviously invalid data that indicates a sensor error. This will be replaced as soon as the first message is published.</p>
<h2 id="publishing-a-zbus-message">Publishing a zbus message</h2>
<p>Publishing to zbus is a simple one-liner with an error check.</p>
<pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-linenumbers="false">err = zbus_chan_pub(&weather_chan, &reading, K_MSEC(100));
if (err != 0) {
    LOG_ERR("Failed to publish sensor data: %d", err);
    return;
}
</pre> The publish command begins with the channel name 
<p><code>&amp;weather_chan</code> which we defined in the previous section. The message itself (<code>&amp;reading</code>) is the address of a weather_data struct containing the sensor readings I want to publish. The zbus subsystem will copy this struct data so its fine to free that memory after calling the publish function. Since zbus arbitrates access to read/write a message, the final param is a timeout value in case another thread is currently accessing this zbus channel.</p>
<h2 id="reading-a-zbus-message">Reading a zbus message</h2>
<p>Reading from zbus is very similar to publishing to it.</p>
<pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-linenumbers="false">struct weather_data w_data;

err = zbus_chan_read(&weather_chan, &w_data, K_MSEC(50));
if (err) {
    LOG_ERR("Cannot access weather data: %d", err);
}
</pre> Just as with the publish operation, we begin a ready by passing the channel name 
<p><code>&amp;weather_chan</code>. The address of a struct matching the zbus channel type is given to receive the data from the zbus message. And finally we use a timeout value to account for any concurrent access issues.</p>
<h2 id="keep-going-with-observers">Keep going with observers</h2>
<p>Today’s post is just a taste of what zbus can deliver. To get a full picture I encourage you to try out the different observer options. You can register callbacks, receive notifications that a new message is available, or receive a copy of each new message in a FIFO buffer, depending on what type of observer you use. For more on this, check out the
<a href="https://docs.zephyrproject.org/latest/services/zbus/index.html#concepts">Zbus Concepts section of the Zephyr docs</a>.</p>
]]></content:encoded></item><item><title>'Bluetooth Gateways In The Field: The Ezurio Sentrius MG100'</title><link>https://blog.golioth.io/bluetooth-gateways-in-the-field-the-ezurio-sentrius-mg100/</link><guid>https://blog.golioth.io/bluetooth-gateways-in-the-field-the-ezurio-sentrius-mg100/</guid><pubDate>Thu, 31 Jul 2025 15:02:45 +0000</pubDate><dc:creator>Marko Puric</dc:creator><category>Bluetooth</category><category>Gateways</category><category>Hardware</category><description>Golioth’s Bluetooth-to-Cloud development continues to roll out new features for private access users, including offering a range of solutions for shuttling bluetooth data up to the cloud. As more customers build sensor-rich devices for agriculture, smart buildings, and asset tracking, Bluetooth Low Energy (BLE) is the protocol of choice for low-power, short-range communication. But there’s still a missing piece in many deployments: how do you reliably bridge BLE sensors to the cloud with off-the-shelf options? That’s where Bluetooth gateways come in.</description><content:encoded><![CDATA[<p>Golioth’s <a href="https://blog.golioth.io/bluetooth-to-cloud/">Bluetooth-to-Cloud development</a> continues to roll out new features for private access users, including offering a range of solutions for shuttling bluetooth data up to the cloud. As more customers build sensor-rich devices for agriculture, smart buildings, and asset tracking, Bluetooth Low Energy (BLE) is the protocol of choice for low-power, short-range communication. But there’s still a missing piece in many deployments: how do you reliably bridge BLE sensors to the cloud with off-the-shelf options? That’s where Bluetooth gateways come in.</p>
<p data-start="416" data-end="753">
  Over the past few months, we’ve been evaluating a variety of BLE gateways, some that pair with Wi-Fi or Ethernet, others that speak cellular, and running them through their paces with Golioth. Our goal was simple: find a reliable, flexible, and developer-friendly solution that we can confidently recommend to customers building BLE-enabled fleets.
</p>
<h2 data-start="416" data-end="753">
  Defining a Great BLE Gateway
</h2>
<p data-start="0" data-end="148">
  At Golioth, we think of networks in terms of devices (your sensors and actuators) and gateways (the last hop to the cloud). Gateways can be:
</p>
<ul data-start="150" data-end="355">
  <li data-start="150" data-end="251">
    <p data-start="152" data-end="251">
      <strong data-start="152" data-end="163">Managed</strong> — fully onboarded in Golioth with OTA updates, logging, settings, and policy control.
    </p>
  </li>
  <li data-start="252" data-end="355">
    <p data-start="254" data-end="355">
      <strong data-start="254" data-end="267">Unmanaged</strong> — authenticated to Golioth but only relaying data for endpoints that don’t run our SDK.
    </p>
  </li>
</ul>
<p data-start="357" data-end="902" data-is-last-node="" data-is-only-node="">
  A gateway is any device that connects directly to Golioth, typically over cellular, Wi-Fi, or Ethernet, and forwards traffic for local BLE nodes. For the newest Bluetooth-to-Cloud capabilities, that means the gateway needs to be able to forward data wrapped in Pouch, our new innovation in the space of non-IP communication. It’s also possible for Gateways to forward non-Pouch traffic, but we’ll be focusing on the most enhanced version of data transfer (ie. Pouch) for the near future.
</p>
<h2 id="the-ble-gateway-landscape">The BLE Gateway Landscape</h2>
<p data-start="1722" data-end="1974">
  You’ll find plenty of Zephyr-compatible dev boards, Nordic’s nRF9160 DK and the Thingy91:X among them that technically fit the bill. We love these boards for rapid prototyping and POCs: they pair the nRF52840’s BLE prowess with an nRF91 Series LTE-M/NB-IoT modem.
</p>
<p data-start="1976" data-end="2213">
  But most teams aren’t in the business of maintaining custom hardware stacks. They want a turnkey gateway that just works. That’s why, even though you <em data-start="2126" data-end="2131">can</em> cobble together a solution from dev boards, we don’t recommend it for production.
</p>
<h3 data-start="1976" data-end="2213">
  Our Top Pick: Ezurio Sentrius MG100
</h3>
<p><a href="https://blog.golioth.io/wp-content/uploads/2025/07/mg100.png"><img src="https://blog.golioth.io/wp-content/uploads/2025/07/mg100.png" alt=""></a></p>
<p data-start="2254" data-end="2366">
  After testing, we recommend the<a href="https://www.ezurio.com/iot-devices/bluetooth-iot-devices/sentrius-mg100-gateway-lte-mnb-iot-and-bluetooth-5"><strong> Sentrius MG100 from Ezurio</strong></a> for industrial BLE + cellular deployments. Here’s why:
</p>
<ul data-start="2368" data-end="2746">
  <li data-start="2368" data-end="2469">
    <p data-start="2370" data-end="2469">
      <strong>Zephyr-Ready:</strong> First-class support in Golioth’s Zephyr SDK, so you can hit the ground running.
    </p>
  </li>
  <li data-start="2470" data-end="2552">
    <p data-start="2472" data-end="2552">
      <strong>Proven Hardware:</strong> nRF52840 for BLE Central; Sierra HL7800 for LTE-M/NB-IoT.
    </p>
  </li>
  <li data-start="2553" data-end="2650">
    <p data-start="2555" data-end="2650">
      <strong>Off-the-Shelf Reliability:</strong> No soldering irons or custom enclosures, just unbox and deploy.
    </p>
  </li>
  <li data-start="2651" data-end="2746">
    <p data-start="2653" data-end="2746">
      <strong>Customizable:</strong> Optional branding and packaging, so it fits right into your product lineup.
    </p>
  </li>
</ul>
<p data-start="2748" data-end="2890">
  With the MG100 + Golioth, you get a hardened, field-ready gateway that delivers the “set-and-forget” experience you and your customers expect.
</p>
<h2 data-start="2897" data-end="2941">
  Next Up: BLE + Wi-Fi/Ethernet Gateways
</h2>
<p data-start="2943" data-end="3194">
  A microcontroller-based cellular device in an industrial enclosure doesn’t fit every application space. In our next post, we’ll explore the world of BLE + Wi-Fi/Ethernet gateways, as well as discussing more about the processing needs of particular gateways.
</p>
<p data-start="2943" data-end="3194">
  We’re continuing to enhance our BLE gateway tooling, improve reference firmware, and if you are building a connected product, sign up for <a href="https://glth.io/bluetooth">Golioth’s Bluetooth-to-Cloud Connectivity private access program</a>, or join us in the <a href="https://forum.golioth.io/">forum</a> to tell us more about your use case!
</p>
]]></content:encoded></item><item><title>Golioth Location General Availability</title><link>https://blog.golioth.io/golioth-location-ga/</link><guid>https://blog.golioth.io/golioth-location-ga/</guid><pubDate>Thu, 24 Jul 2025 12:00:41 +0000</pubDate><dc:creator>Dan Mangum</dc:creator><category>Featured</category><category>LBS</category><category>Location</category><category>News</category><description>Today we are excited to announce general availability of Golioth Location! Golioth Location is a comprehensive device positioning service, presenting a consistent interface for tracking device location via both traditional mechanisms, such as GNSS, and more sophisticated techniques, such as network-based positioning. The service has been in private access since its initial launch in December 2024.</description><content:encoded><![CDATA[<p>Today we are excited to announce general availability of <a href="https://docs.golioth.io/application-services/location/">Golioth Location</a>! Golioth Location is a comprehensive device positioning service, presenting a consistent interface for tracking device location via both traditional mechanisms, such as <a href="https://en.wikipedia.org/wiki/Satellite_navigation">GNSS</a>, and more sophisticated techniques, such as network-based positioning. The service has been in private access since its <a href="https://blog.golioth.io/golioth-location-private-access/">initial launch in December 2024</a>.</p>
<h2 id="what-makes-a-great-location-service">What Makes a Great Location Service?</h2>
<p>In our blog post launching Golioth Location,
<a href="https://blog.golioth.io/golioth-location-private-access/#why-build-a-location-service">we outlined</a> why we found it necessary to build a location service. The points presented primarily focused on why providing an integrated network-based positioning service, rather than forcing users to bring their own, dramatically simplified initial setup and ongoing management. We also detailed <a href="https://blog.golioth.io/golioth-location-private-access/#why-network-based-positioning">why network-based positioning is a good fit</a> for connected devices, especially those that are bandwidth and power constrained. These points hold true today: network-based positioning can extend battery life, alleviate <a href="https://en.wikipedia.org/wiki/Time_to_first_fix">time-to-first-fix (TTFF)</a> delays, and drive down the cost of hardware. However, we also received great feedback from customers in the private access program, ultimately driving us to rethink what an ideal location service could look like. As we have so many times since <a href="https://blog.golioth.io/announcing-pipelines-stream-transform-and-route-iot-data-anywhere/">introducing the product in May of 2024</a>, we found ourselves looking to <a href="https://docs.golioth.io/data-routing">Golioth Pipelines</a> to provide a flexible and robust foundation for Golioth Location. With the wide variety of use cases for location services, ranging from active asset tracking to infrequent device check-ins for product analytics, it is critical to enable users to configure location resolution and storage to fit their use case. For example, some use cases require that historical device location is persisted, whereas others only need the most recent device location. Some products need devices to be made aware of their location rather than just delivering it to a data store. The most significant difference in use cases is <a href="https://blog.golioth.io/you-might-not-need-gnss/">whether using GNSS is an option</a>, and whether it can be used together with network-based positioning. Doing so provides the right trade-off of accuracy and performance, all while offering a consistent interface.</p>
<h2 id="golioth-location-then-and-now">Golioth Location: Then and Now</h2>
<p>In the private access iteration of Golioth Location, devices:</p>
<ul>
<li>Submitted network information to a dedicated API endpoint</li>
<li>This information resolved a position (lat, long, accuracy)</li>
<li>It was returned in the response to the device making the request</li>
<li>The position would also be stored in Golioth Location, making it possible to visualize a fleet on a map in the <a href="https://console.golioth.io">Golioth console</a>. If it was necessary to store the device location in a timeseries data store, or route it to any external destination, the device would need to stream the position back to Golioth so that it could subsequently be routed to the desired destination via Pipelines. Besides the obvious unnecessary round-trip this incurred, we also found that many products did not require the device itself to be aware of its location. Returning the position to the device at all was extraneous. For use cases where it did seem necessary for the device know its location, the logic in the device firmware could be offloaded to the cloud. For example, in a</li>
</ul>
<p><a href="https://en.wikipedia.org/wiki/Geofence">geofence</a> application, the fence could be configured on the cloud, and the device would only need to be notified when its position moved outside of the fence. The other major takeaway from customer feedback was the desire to use network-based positioning as a supplemental solution alongside GNSS. In private access, combining location obtained via GNSS with network-based positioning was messy, and the former would not show up in the Golioth Location map visualization. This was especially frustrating when multiple fleets existed in the same project, with some devices using GNSS and others using network-based positioning. To address these concerns and more, we have removed the device-facing Golioth Location API endpoint, and instead introduced a Pipelines <a href="https://docs.golioth.io/data-routing/transformers/location"><code>location</code> transformer</a> and <a href="https://docs.golioth.io/data-routing/destinations/location"><code>location</code> destination</a>. The former is used to translate network information into a position, while the latter stores the most recent location for a device, regardless of how it was obtained. These can be combined with any other Pipeline transformers and destinations. For example, if you need to store the historical location for a device, you can add a step to send positions to <a href="https://docs.golioth.io/application-services/lightdb-stream/">LightDB Stream</a> via the <code>lightdb-stream</code> destination. If you need to return data to a device, you can add a step to set the current position in <a href="https://docs.golioth.io/application-services/lightdb-state/">LightDB State</a> via the <code>lightdb-state</code> destination, allowing the device to fetch its own location. You can even combine all of these into a single Pipeline, which addresses a wide range of use cases. <!-- notionvc: f7ec8c03-d37e-416b-bdf6-93c2c7fcdabb --></p>
<pre class="EnlighterJSRAW" data-enlighter-language="yaml">filter:
  path: "*"
  content_type: application/cbor
steps:
  - name: convert-to-json
    transformer:
      type: cbor-to-json
      version: v1
  - name: resolve-position
    transformer:
      type: location
      version: v1
  - name: store-location
    destination:
      type: location
      version: v1
  - name: stream-location
    destination:
      type: lightdb-stream
      version: v1
  - name: set-state
    destination:
      type: lightdb-state
      version: v1</pre> You can supplement the Pipeline above, with one that omits the 
<p><code>location</code> transformer and instead delivers data directly to the destinations after converting to JSON. This would allow for positions obtained via GNSS to appear in each destination in the same manner as those obtained via network-based positioning. <!-- notionvc: 72d0c1de-6e88-4890-86b5-1e19ee6aa8ac --></p>
<pre class="EnlighterJSRAW" data-enlighter-language="yaml">filter:
  path: "*"
  content_type: application/cbor
steps:
  - name: convert-to-json
    transformer:
      type: cbor-to-json
      version: v1
  - name: store-location
    destination:
      type: location
      version: v1
  - name: stream-location
    destination:
      type: lightdb-stream
      version: v1
  - name: set-state
    destination:
      type: lightdb-state
      version: v1</pre> Lastly, because the existing device-facing location API has been removed in favor of existing data streaming capabilities, customers do not need to update their 
<p><a href="https://github.com/golioth/golioth-firmware-sdk">Golioth Firmware SDK</a> versions to start utilizing these features. Nevertheless, we have introduced new <code>net_info</code> functionality in the most recent version of the SDK to make <a href="https://github.com/golioth/golioth-firmware-sdk/tree/main/examples/zephyr/location">using network-based positioning simple and straightforward</a>. <!-- notionvc: f1c6fc98-737f-49d4-8698-a6a8a2f922ad --></p>
<pre class="EnlighterJSRAW" data-enlighter-language="c">info = golioth_net_info_create();

struct golioth_wifi_scan_result golioth_result = {
    .mac = {result->mac[0],
            result->mac[1],
            result->mac[2],
            result->mac[3],
            result->mac[4],
            result->mac[5]},
    .rssi = result->rssi,
};

status = golioth_net_info_wifi_append(info, &golioth_result);
if (status != GOLIOTH_OK)
{
    LOG_ERR("Failed to append Wi-Fi access point");
    return -1;
}

status = golioth_net_info_finish(info);
if (status != GOLIOTH_OK)
{
    if (status == GOLIOTH_ERR_NULL)
    {
        LOG_WRN("No network information data provided");
        return 0;
    }

    LOG_ERR("Failed to encode network information data");
    return -1;
}

err = golioth_stream_set_sync(client,
                              "loc/net",
                              GOLIOTH_CONTENT_TYPE_CBOR,
                              golioth_net_info_get_buf(info),
                              golioth_net_info_get_buf_len(info),
                              SYNC_TIMEOUT_S);
if (err != GOLIOTH_OK)
{
    LOG_ERR("Failed to stream network data: %d", err);
    return -1;
}</pre>
<h2 id="getting-started">Getting Started</h2>
<p>Ready to get started? Go the the
<a href="https://console.golioth.io/location">Location section</a> in the console to activate Golioth Location, then check out the examples in the documentation to start tracking your devices. To learn more about network-based positioning pricing, take a look at the <a href="https://golioth.io/pricing">Golioth pricing page</a>. If you have any questions or feedback, reach out to us on the <a href="https://forum.golioth.io">forum</a>! <!-- notionvc: c31d25fb-9d55-4a78-84a3-43feb64504df --></p>
]]></content:encoded></item><item><title>An Introduction to the Cyber Resilience Act (CRA) with Kate Stewart of The Zephyr Project</title><link>https://blog.golioth.io/an-introduction-to-the-cyber-resilience-act-cra-with-kate-stewart-of-the-zephyr-project/</link><guid>https://blog.golioth.io/an-introduction-to-the-cyber-resilience-act-cra-with-kate-stewart-of-the-zephyr-project/</guid><pubDate>Tue, 22 Jul 2025 14:47:41 +0000</pubDate><dc:creator>Chris Gammell</dc:creator><category>Regulations</category><category>Zephyr</category><description>The Cyber Resilience Act (CRA) is a topic critical for anyone designing, developing, or manufacturing connected devices (AKA “Internet of Things” or “IoT” or “the stuff Golioth developers create”). If you haven’t heard about it yet, or are just starting to grasp its implications, you’re not alone. Companies and the engineers working at those companies are just now realizing the impact this new EU regulation will have on the industry. The EU isn’t simply suggesting better cybersecurity practices; they are mandating a rigorous, lifecycle-long commitment to the security of products with digital elements, including your IoT devices. This legal framework aims to set the foundational conditions for creating secure digital products, ensuring they come to market with fewer vulnerabilities, and that manufacturers treat security as a continuous priority throughout a product’s entire lifespan. It also seeks to improve user awareness and access to information, enabling them to make more informed choices about secure products. Personally, I had no idea about what is coming, so I did what I normally do: Ask an expert! I spoke with Kate Stewart from The Zephyr Project about how the open source ecosystem is planning to deal with these regulations and what engineers should do to prepare. Utilizing the open source Real Time Operating System (RTOS) and Ecosystem means manufacturers can take advantage of additional tools and reporting capabilities they might not otherwise have. That video is below, as well as additional information in this post that manufacturers might need.</description><content:encoded><![CDATA[<p>The <strong>Cyber Resilience Act (CRA)</strong> is a topic critical for anyone designing, developing, or manufacturing connected devices (AKA “Internet of Things” or “IoT” or “the stuff Golioth developers create”). If you haven’t heard about it yet, or are just starting to grasp its implications, you’re not alone. Companies and the engineers working at those companies are just now realizing the impact this new EU regulation will have on the industry. The EU isn’t simply suggesting better cybersecurity practices; they are mandating a rigorous, lifecycle-long commitment to the security of products with digital elements, including your IoT devices. This legal framework aims to set the foundational conditions for creating secure digital products, ensuring they come to market with fewer vulnerabilities, and that manufacturers treat security as a continuous priority throughout a product’s entire lifespan. It also seeks to improve user awareness and access to information, enabling them to make more informed choices about secure products. Personally, I had <em>no idea</em> about what is coming, so I did what I normally do: Ask an expert! I spoke with <a href="https://www.zephyrproject.org/staff/">Kate Stewart from The Zephyr Project</a> about how the open source ecosystem is planning to deal with these regulations and what engineers should do to prepare. Utilizing the open source Real Time Operating System (RTOS) and Ecosystem means manufacturers can take advantage of additional tools and reporting capabilities they might not otherwise have. That video is below, as well as additional information in this post that manufacturers might need. <div style="position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden;">
      <iframe allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share; fullscreen" loading="eager" referrerpolicy="strict-origin-when-cross-origin" src="https://www.youtube.com/embed/lQD3Dn5QDp0?autoplay=0&amp;controls=1&amp;end=0&amp;loop=0&amp;mute=0&amp;start=0" style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; border:0;" title="YouTube video"></iframe>
    </div>
</p>
<h2 id="you-need-to-pay-attention">You Need to Pay Attention.</h2>
<p><strong>The CRA Has Teeth!</strong></p>
<p><a href="https://eur-lex.europa.eu/legal-content/EN/TXT/HTML/?uri=OJ:L_202402847">The Cyber Resilience Act (Regulation (EU) 2024/2847)</a> is a uniform legal framework designed to strengthen the EU’s cybersecurity posture and enhance the internal market by ensuring that hardware and software products are placed on the market with a higher level of security. This legislation has significant enforcement provisions. <strong>Manufacturers are paying close attention because non-compliance can lead to administrative fines of up to €15,000,000 or 2.5% of the total worldwide annual turnover for the preceding financial year, whichever is higher</strong>. This substantial penalty is a primary driver for its rapid adoption across the industry, similar to how EU regulations historically influenced global markets, such as the widespread adoption of USB-C and GDPR. The CRA addresses critical issues like the low level of cybersecurity in digital products, the prevalence of vulnerabilities, and inconsistent provision of security updates. The regulation officially entered into force in December of last year, starting the clock for compliance. While governments need to establish certain procedures by June 2026, manufacturers must begin adhering to reporting obligations just three months later, by September 11, 2026. The entire regulation will be fully applicable by December 11, 2027. The term “manufacturer” under the CRA encompasses anyone with a commercial interest in a product with “digital” elements, which includes both software and hardware. This broad definition means it’s a catch-all for products not already covered by existing specific industry regulations. The CRA applies primarily to “economic operators” who make products with digital elements available on the market in the course of a commercial activity. Most notably, this includes manufacturers, defined as “any natural or legal person who develops, manufactures, or markets products with digital elements under their name or trademark, whether for payment, monetisation, or free of charge.”</p>
<h2 id="key-requirements-for-manufacturers-and-how-golioth-can-help">Key Requirements for Manufacturers and How Golioth Can Help</h2>
<p>The CRA mandates several crucial obligations for manufacturers to ensure cyber resilience throughout a product’s lifecycle:</p>
<ul>
<li><strong>Vulnerability Handling and Support Period</strong> – Manufacturers must ensure vulnerabilities are handled effectively for a “support period,” which must be at least five years, or the product’s expected use time if shorter.</li>
<li><strong>Reporting Obligations</strong> – This is where the rubber meets the road, as Kate mentioned (around 0:03:10).
<ul>
<li>Actively exploited vulnerabilities must be notified <em>without undue delay, within 24 hours</em> of awareness to the designated Computer Security Incident Response Team (CSIRT) and ENISA via a single reporting platform. A more detailed vulnerability notification is required within 72 hours, and a final report no later than 14 days after a corrective measure is available.</li>
<li>Severe incidents impacting product security also require a 24-hour early warning notification.</li>
<li>Manufacturers must also inform impacted users about the vulnerability or incident and any necessary risk mitigation measures.</li>
</ul>
</li>
<li><strong>Software Bill of Materials (SBOM)</strong> – Manufacturers are required to identify and document all components in their products, including by drawing up an SBOM in a commonly used, machine-readable format. This is vital for tracking known vulnerabilities and avoiding unnecessary updates. Kate highlighted how Zephyr’s capability to generate SBOMs with just a few lines of code puts them in a strong position for this requirement (around 0:08:00).</li>
<li><strong>Secure by Design and Updates</strong> – Products must be available with a secure by default configuration. Manufacturers should design products to receive automatic security updates (with an opt-out mechanism) and ensure updates are disseminated without delay and generally free of charge.</li>
<li><strong>CE Marking</strong> – Products with digital elements must bear the CE marking to indicate conformity with the CRA.</li>
</ul>
<h2 id="how-golioth-assists-your-compliance">How Golioth Assists Your Compliance</h2>
<p>At Golioth, we’re keenly aware of these evolving regulations. Our platform’s capabilities are designed to help manufacturers meet many of these demands for their embedded IoT devices:</p>
<ul>
<li><strong>Over-the-Air (OTA) Updates</strong> – The CRA emphasizes the need for secure and timely distribution of security updates. <a href="https://docs.golioth.io/device-management/ota/">Golioth’s OTA service</a> is a direct answer to this, enabling manufacturers to quickly deploy security patches and new functionality to their fleet of devices, even thousands of miles away. This is crucial for addressing identified vulnerabilities and ensuring ongoing compliance.</li>
<li><strong>Device Management and Remote Interaction</strong> – Our platform allows you to monitor and interact with your fleet. This helps manufacturers stay aware of their devices’ status and deliver necessary updates effectively.</li>
<li><strong>Remotely manage and update device configurations</strong> – The Golioth Settings service ensures adherence to secure defaults across the entire fleet and have stored backup configurations.</li>
<li><strong>Detect Anomalies</strong> – The CRA’s emphasis on “reporting on possible unauthorised access” [Annex I, Part I, point 2(d); 339, 671] is key here. By analyzing patterns in device logs (e.g., unusual data access, unexpected process starts) and streamed data, manufacturers can identify deviations from normal behavior that may indicate an intrusion attempt or an actively exploited vulnerability.</li>
<li><strong>Continuous Data Feed</strong> – Devices can be configured to stream telemetry data, events, and critical security logs directly to the Golioth cloud. This provides manufacturers with the real-time visibility needed to “know when someone was in the system”. An example of this is a light detection circuit on a board that reports back over the Golioth stream service, for when there is an unauthorized opening of a case.</li>
<li><strong>Facilitate Rapid Reporting</strong> – The CRA’s 24-hour reporting window for actively exploited vulnerabilities and severe incidents [Article 14(2)(a), 14(4)(a); 231, 233, 542, 544] is extremely tight. Centralized and accessible logs via Golioth enable manufacturers to quickly gather the necessary information for early warning notifications and subsequent detailed reports, ensuring compliance with these critical timelines. The EU’s Cyber Resilience Act is a significant step towards ensuring more secure digital products for consumers, moving away from a “throwaway economy” (around 6:50). It’s a call for manufacturers to prioritize cybersecurity throughout the product lifecycle. By leveraging robust platforms like Golioth, built on secure foundations like Zephyr, you can better navigate these regulations and deliver more resilient IoT products. Want to learn more about how Golioth’s Firmware SDK and platform can help your product meet these new cybersecurity challenges? Check out our documentation and examples, schedule a discovery call, or join the discussion on our forum!</li>
</ul>
]]></content:encoded></item><item><title>Enabling Bluetooth-to-Cloud on the Renesas DA14695</title><link>https://blog.golioth.io/enabling-bluetooth-to-cloud-on-the-renesas-da14695/</link><guid>https://blog.golioth.io/enabling-bluetooth-to-cloud-on-the-renesas-da14695/</guid><pubDate>Thu, 17 Jul 2025 15:15:58 +0000</pubDate><dc:creator>Mike Szczys</dc:creator><category>Bluetooth</category><category>Zephyr</category><description> 💡 Golioth Bluetooth-to-Cloud is now known as Golioth Connectivity, which is now out of private access. For ongoing support of Renesas (Dialog) components, we are tracking changes in a thread on the Golioth forum specific to Renesas parts. This week we enabled internet uplink over Bluetooth for the Renesas DA14695. This approach uses Golioth’s Bluetooth-to-Cloud service which leverages a standard gateway device to read data from any Bluetooth hardware capable of using the Generic Attribute Profile (GATT), which is basically all Bluetooth devices. With this setup, the gateway periodically scans for devices with a predetermined characteristic. When these devices connect to the gateway, they use Golioth’s Pouch protocol to package sensor (or any other data) and send it to the cloud. In addition to today’s Renesas device, we’ve already demonstrated this functionality with Nordic, STM, and NXP devices.</description><content:encoded><![CDATA[<div class="golioth-callout-banner">
  💡 Golioth Bluetooth-to-Cloud is now known as <a href="https://blog.golioth.io/bluetooth-open-beta/">Golioth Connectivity</a>, which is now out of private access. For ongoing support of Renesas (Dialog) components, we are tracking changes in <a href="https://forum.golioth.io/t/enabling-renesas-dialog-parts-with-golioths-bluetooth-support/1651">a thread on the Golioth forum specific to Renesas parts</a>.
</div> This week we enabled internet uplink over Bluetooth for the Renesas DA14695. This approach uses 
<p><a href="https://blog.golioth.io/bluetooth-to-cloud/">Golioth’s Bluetooth-to-Cloud service</a> which leverages a standard gateway device to read data from any Bluetooth hardware capable of using the Generic Attribute Profile (GATT), which is basically all Bluetooth devices. With this setup, the gateway periodically scans for devices with a predetermined characteristic. When these devices connect to the gateway, they use <a href="https://github.com/golioth/pouch/tree/main">Golioth’s Pouch protocol</a> to package sensor (or any other data) and send it to the cloud. In addition to today’s Renesas device, we’ve already demonstrated this functionality with <a href="https://blog.golioth.io/enabling-bluetooth-to-cloud-on-the-arduino-nicla-sense-me/">Nordic</a>, <a href="https://blog.golioth.io/connecting-bluetooth-iot-devices-to-the-internet-stm32wb5mm-dk/">STM</a>, and <a href="https://blog.golioth.io/enabling-the-mcxw71-nxp-with-golioths-bluetooth-to-cloud/">NXP</a> devices. <div style="position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden;">
      <iframe allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share; fullscreen" loading="eager" referrerpolicy="strict-origin-when-cross-origin" src="https://www.youtube.com/embed/4xpB-BOYd34?autoplay=0&amp;controls=1&amp;end=0&amp;loop=0&amp;mute=0&amp;start=0" style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; border:0;" title="YouTube video"></iframe>
    </div>
</p>
<h2 id="using-the-da14695-with-zephyr">Using the DA14695 with Zephyr</h2>
<p><a href="https://blog.golioth.io/wp-content/uploads/2025/07/renesas-da14695.jpg"><img src="https://blog.golioth.io/wp-content/uploads/2025/07/renesas-da14695-1030x773.jpg" alt="Renesas da14695 propped against a wireless earbud case."></a>While the Bluetooth-to-Cloud service doesn’t require that leaf nodes run Zephyr, <a href="https://github.com/golioth/ble-private-access">we have built the first example applications on Zephyr</a>. The first thing we need to do is get ready to build for the Renesas DA14695. <a href="https://blog.golioth.io/wp-content/uploads/2025/07/renesas-da14695-jumpers.jpg"><img src="https://blog.golioth.io/wp-content/uploads/2025/07/renesas-da14695-jumpers-1030x773.jpg" alt="Renesas da14695 jumper settings for 3.3V operation"></a>My device shipped with a jumper installed on J2 between pins 2-3. This runs the board in 5V mode, but I found that Zephyr applications like <a href="https://github.com/zephyrproject-rtos/zephyr/tree/main/samples/basic/blinky">basic/blinky</a> didn’t want to run (as if the clock was not running). This was resolved by moving the jumper to J3 between pins 1-2 which connects the chip to the 3.3V rail. While <a href="https://docs.zephyrproject.org/latest/boards/renesas/da14695_dk_usb/doc/index.html">the board includes Bluetooth support in Zephyr</a>, it requires that you fetch the binary blob and turn on the Bluetooth peripheral in the devicetree. I also found that Bluetooth functionality required Zephyr 4.1.0 or later. First, let’s use <code>west</code> to grab the binary blob.</p>
<pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-linenumbers="false">west blobs fetch hal_renesas</pre> With the binary blobs in place, add an overlay file for this board to your project. Note that we are both turning on the Bluetooth peripheral, and add it as a “chosen” in the overlay file. 
<pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-group="1" data-enlighter-title="boards/da14695_dk_usb.overlay" data-enlighter-linenumbers="false">/ {
    chosen {
    zephyr,bt-hci = &bt_hci_da1469x;
  };
};

&bt_hci_da1469x {
  status = "okay";
};
</pre> Finally, to flash the device we need to install the 
<p><a href="https://github.com/ezflash/ezFlashCLI/">eZFlashCLI</a>{.reference.external} which can be installed using pip.</p>
<pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-linenumbers="false">pip install ezFlashCLI</pre> If you want to test your Bluetooth functionality at this point, I recommending trying out 
<p><a href="https://github.com/zephyrproject-rtos/zephyr/tree/main/samples/bluetooth/observer">Zephyr’s observer example</a>. It simply scans for Bluetooth devices and reports their mac address in the serial console.</p>
<h2 id="building-golioths-bluetooth-to-cloud-example">Building Golioth’s Bluetooth-to-Cloud Example</h2>
<p>As shown in the video, there are a couple of tweaks we need to make to get the example code to build for this board:</p>
<ol>
<li>Use Zephyr 4.1.0</li>
<li>Add the Renesas HAL libraries Make sure you have already completed steps 1 and 2 for setting up a gateway in the</li>
</ol>
<p><a href="https://github.com/golioth/ble-private-access#getting-started">Getting Started section of the BLE Private Access application</a>. When you get to Step 3, follow the instructions to initialize and update the project. But pause at this point to edit the <code>pouch/west.yml</code> file as follows:</p>
<pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-highlight="9,18" data-enlighter-group="2" data-enlighter-title="pouch/west.yml"># Copyright (c) 2025 Golioth, Inc.
# SPDX-License-Identifier: Apache-2.0

manifest:
  version: 0.8

  projects:
    - name: zephyr
      revision: v4.1.0
      url: https://github.com/zephyrproject-rtos/zephyr
      west-commands: scripts/west-commands.yml
      import:
        path-prefix: deps
        name-allowlist:
          - zephyr
          - cmsis
          - hal_nordic
          - hal_renesas
          - mbedtls
          - mcuboot
          - segger
          - tfm-mcuboot
          - tinycrypt
          - trusted-firmware-m
          - zcbor
</pre> The lines highlighted above show the changes to this file. The Zephyr version was updated to 4.1.0 and the 
<p><code>hal_renesas</code> library was added to the <code>name-allowlist</code>. Let’s update for these changes to take effect and also ensure we fetch the binary blob and install the flashing tool as discussed in the previous section.</p>
<pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-linenumbers="false">west update
west blobs fetch hal_renesas
pip install ezFlashCLI</pre> Next, copy the overlay file shown in the previous section to 
<p><code>pouch/examples/ble_gatt/boards/da14695_dk_usb.overlay</code>. We’re now ready to build and flash the application.</p>
<pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-linenumbers="false">west build -p -b da14695_dk_usb pouch/examples/ble_gatt -- -DCONFIG_EXAMPLE_DEVICE_ID="REPLACEME"
west flash</pre> The device will reboot and begin running. It’s worth mentioning that the build command above includes a 
<p><code>REPLACEME</code> placeholder that needs to be replaced with the Golioth <code>DeviceID</code> for this hardware. Without this, the cloud will not recognize this as a valid device when receiving data via the gateway. <a href="https://blog.golioth.io/wp-content/uploads/2025/07/golioth_ble_gateway_5_devices_4_vendors.png"><img src="https://blog.golioth.io/wp-content/uploads/2025/07/golioth_ble_gateway_5_devices_4_vendors-1030x671.png" alt="Golioth Network Topology Map shows da14695_dk_usb connecting to the gateway"></a></p>
<h2 id="sending-actual-sensor-code">Sending Actual Sensor Code</h2>
<p>In the video we demonstrate going a step further by connecting a BME280 temperature, pressure, and humidity sensor up to the Renesas DA14695 so that we can send actual sensor data. It is actually quite simple to add data to the upload. Let’s look at
<a href="https://github.com/golioth/pouch/blob/ed81c7d88d48b40519dc4a32d978618c85e44180/examples/ble_gatt/src/main.c#L71-L88">the pouch_event_handler() callback</a>. You can see in this function we are packaging up some JSON code to send a (simulated) temperature value. This payload may be replaced with any valid JSON, just be sure to change both the JSON string and the length in the write API call.</p>
<pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-linenumbers="false">static void pouch_event_handler(enum pouch_event event, void *ctx)
{
    if (POUCH_EVENT_SESSION_START == event)
    {
        pouch_uplink_entry_write(".s/sensor",
                                 POUCH_CONTENT_TYPE_JSON,
                                 "{"temp":22}",
                                 sizeof("{"temp":22}") - 1,
                                 K_FOREVER);
        pouch_uplink_close(K_FOREVER);
    }

    if (POUCH_EVENT_SESSION_END == event)
    {
        service_data[ARRAY_SIZE(service_data) - 1] = 0x00;
        k_work_schedule(&sync_request_work, K_SECONDS(SYNC_PERIOD_S));
    }
}</pre>
<h2 id="sensor-data-received-from-da14695-over-bluetooth-gateway"><a href="https://blog.golioth.io/wp-content/uploads/2025/07/renesas-bluetooth-pouch-data.png"><img src="https://blog.golioth.io/wp-content/uploads/2025/07/renesas-bluetooth-pouch-data-1030x358.png" alt="Sensor data received from da14695 over bluetooth gateway"></a></h2>
<h2 id="your-bluetooth-devices-on-the-cloud-right-now">Your Bluetooth Devices On the Cloud, Right Now.</h2>
<p>Golioth has already solved the complexity of transferring data from your Bluetooth fleet to the cloud. By installing a gateway device, you can automatically scan and upload that data at a cadence of your choosing.
<a href="https://blog.golioth.io/bluetooth-to-cloud/">Take it for a spin today</a> and <a href="https://forum.golioth.io/">let us know how it goes</a>!</p>
]]></content:encoded></item><item><title>Upcoming Webinar – Surprise! It’s an IoT Medical Device</title><link>https://blog.golioth.io/surprise-youre-an-iot-device-webinar/</link><guid>https://blog.golioth.io/surprise-youre-an-iot-device-webinar/</guid><pubDate>Thu, 10 Jul 2025 18:49:58 +0000</pubDate><dc:creator>Chris Gammell</dc:creator><category>Partners</category><description>Golioth’s Bluetooth-to-Cloud capabilities are a good fit for many new types of products, such as medical devices. These typically utilize BLE over Wi-Fi, Cellular, Ethernet, or Thread.
Medical devices in the US and abroad have much more stringent regulatory requirements than consumer or industrial electronics. We will join Marcus Engineering–one of our newest Design Partners–and the MedTech Leading Voice group for an upcoming webinar about medical devices. Surprise! Almost all of them are actually IoT devices. You can sign up for the webinar here, happening 11 am ET (8 am PT) on July 16th</description><content:encoded><![CDATA[<p>Golioth’s <a href="https://blog.golioth.io/bluetooth-to-cloud/">Bluetooth-to-Cloud capabilities</a> are a good fit for many new types of products, such as medical devices. These typically utilize BLE over Wi-Fi, Cellular, Ethernet, or Thread.</p>
<p>Medical devices in the US and abroad have much more stringent regulatory requirements than consumer or industrial electronics. We will join <a href="https://www.marcusengineering.com/">Marcus Engineering</a>–one of our newest Design Partners–and the <a href="https://www.linkedin.com/company/medtech-leading-voice/">MedTech Leading Voice group</a> for an upcoming webinar about medical devices. Surprise! Almost all of them are actually IoT devices. <a href="https://www.linkedin.com/events/surprise-iot-medicaldeviceorbot7344408253226770433/comments/">You can sign up for the webinar here</a>, happening 11 am ET (8 am PT) on July 16th</p>
<h2 id="what-defines-an-iot-device-anyhow">What defines an IoT Device, anyhow?</h2>
<p>An “Internet of Things” (“IoT”) device is a squishy term, but in the eyes of regulators, if it runs software or firmware, these are increasingly considered IoT devices. The definitions include language like “[any device with a] direct or indirect connection to a device or network, where ‘Connection’ includes “using physical means, including through electrical, optical or mechanical interfaces, wires or radio waves”. So…all of them? But given the benefit of the doubt, these regulations are meant to ensure security and the ability to update and patch for vulnerabilities.</p>
<p>It’s up to each product designer and product team to understand whether they are subject to any particular requirements, such as FDA regulations in the US. Increasingly, there are additional requirements for connected devices that increase the burden on technical teams that might have previously been focused on building a hardware and firmware solution. These now need to include a cloud aspect as well, at least for the ability to update firmware and software. Here are some high level requirements that need to be met:</p>
<ul>
<li>Updatability/Patchability</li>
<li>Logging/Intrusion Detection</li>
<li>Authentication</li>
<li>Access Control</li>
<li>Integrity</li>
</ul>
<p>During this webinar we’ll discuss how each requirement impacts a medical device and how users can quickly start to meet those requirements with a solution like Golioth. Some of the newer regulations include the <a href="https://digital-strategy.ec.europa.eu/en/policies/cyber-resilience-act">Cyber Resilience Act</a> (CRA) in the EU, the <a href="https://www.fcc.gov/CyberTrustMark">Cyber Trust Mark</a> in the US, and the <a href="https://www.gov.uk/government/publications/the-uk-product-security-and-telecommunications-infrastructure-product-security-regime">Product Security and Telecommunications Infrastructure (PSTI)</a> in the UK. We’ll offer real world examples of past devices that went awry and how to make sure your product doesn’t meet the same fate.</p>
<p>So be <a href="https://www.linkedin.com/events/surprise-iot-medicaldeviceorbot7344408253226770433/comments/">sure to sign up for this webinar</a> on July 16th, regardless of the type of device you’re building. You’ll learn about regulatory environments and how you can harden your solution for a successful</p>
]]></content:encoded></item><item><title>Enabling the MCXW71 (NXP) with Golioth’s Bluetooth-to-Cloud</title><link>https://blog.golioth.io/enabling-the-mcxw71-nxp-with-golioths-bluetooth-to-cloud/</link><guid>https://blog.golioth.io/enabling-the-mcxw71-nxp-with-golioths-bluetooth-to-cloud/</guid><pubDate>Tue, 08 Jul 2025 14:44:30 +0000</pubDate><dc:creator>Chris Gammell</dc:creator><category>Bluetooth</category><category>Zephyr</category><description> 💡 Golioth Bluetooth-to-Cloud is now known as Golioth Connectivity, which is now out of private access. For ongoing support of NXP components, we are tracking changes in a thread on the Golioth forum specific to NXP parts. Golioth continues to enable new boards with Bluetooth-to-Cloud abilities, today it’s the NXP FRDM-MCXW71. At this point, it’s almost becoming routine! As a general rule, we look for the following in devices that can talk back to the cloud through our standard BLE-GATT based gateway:</description><content:encoded><![CDATA[<div class="golioth-callout-banner">
  💡 Golioth Bluetooth-to-Cloud is now known as <a href="https://blog.golioth.io/bluetooth-open-beta/">Golioth Connectivity</a>, which is now out of private access. For ongoing support of NXP components, we are tracking changes in <a href="https://forum.golioth.io/t/enabling-nxp-parts-with-golioth-s-bluetooth-support/1649">a thread on the Golioth forum specific to NXP parts</a>.
</div>
<p>Golioth continues to enable new boards with Bluetooth-to-Cloud abilities, today it’s <a href="https://www.nxp.com/design/design-center/development-boards-and-designs/FRDM-MCXW71">the NXP FRDM-MCXW71</a>. At this point, it’s almost becoming routine! As a general rule, we look for the following in devices that can talk back to the cloud through our standard BLE-GATT based gateway:</p>
<ul>
<li>A board that is running Zephyr, optimally with in-tree support (though custom hardware is also possible)</li>
<li>A device with a working BLE sample, including the HCI for communications</li>
</ul>
<p>So far we have enabled the ST Microelectronics <a href="https://blog.golioth.io/connecting-bluetooth-iot-devices-to-the-internet-stm32wb5mm-dk/">STM32WB5MM-DK</a>, the Arduino <a href="https://blog.golioth.io/enabling-bluetooth-to-cloud-on-the-arduino-nicla-sense-me/">Nicla Sense ME</a> (with a Nordic nRF52833), the nRF52840-DK, and the <a href="https://blog.golioth.io/using-the-nxp-frdm-rw612-with-golioth/">NXP RW612</a>. In all of these cases, we were able to send back generic packets (filled with our sensor data) to the cloud. Most of the time spent in our previous blog and video output centered around configuring Zephyr to be able to pull in external sensor readings. This made more realistic data flowing through <a href="https://blog.golioth.io/bluetooth-to-cloud/">the standard Golioth gateway up to the cloud</a>.<div style="position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden;">
      <iframe allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share; fullscreen" loading="eager" referrerpolicy="strict-origin-when-cross-origin" src="https://www.youtube.com/embed/x7QxfwwXbZw?autoplay=0&amp;controls=1&amp;end=0&amp;loop=0&amp;mute=0&amp;start=0" style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; border:0;" title="YouTube video"></iframe>
    </div>
</p>
<h2 id="a-second-nxp-board">A second NXP board</h2>
<p>We have already enabled the RW612, so why spend the time enabling a different part on their ecosystem? The nature of the parts matters. And in fact: it’s all about Matter!</p>
<p>The RW612 is meant as a Thread border router, which enables Matter smart home setups. It has more communication mechanisms (Bluetooth, 802.15.4, Ethernet, Wi-FI) and generally a lot more horsepower. As such, it has a much higher price tag. The MCXW71 is the ‘node’ chip in that system, meant to be a Thread (and by extension, Matter) sleepy device: low power, lower cost, reduced functionalities. This matches the abilities of other chips we have enabled so far. Small, low power microcontrollers meant to take readings and then go back to sleep are a perfect fit for what we’re often doing with our Bluetooth-to-Cloud. And while Thread actually runs using 802.15.4, it’s nearly universal that the pairing process for Thread devices is done over Bluetooth.</p>
<h2 id="challenges-with-this-board">Challenges with this board</h2>
<p>If you’re looking to run the FRDM-MCXW71 on your bench, there are two potential pitfalls to getting started (also highlighted in the video above):</p>
<ol>
<li><strong>Getting the <a href="https://www.nxp.com/design/design-center/software/development-software/mcuxpresso-software-and-tools-/linkserver-for-microcontrollers:LINKERSERVER">LinkServer</a> utility working</strong> – This is the debug interface for modern NXP boards. They list JLink as another way to debug this board, but we couldn’t get it working. The tricky part is that LinkServer wasn’t enabled until Zephyr 4.1, whereas our current Bluetooth builds were against 4.0. A bit of reworking in the west manifest and we were able to call out the correct version and also pull in the appropriate NXP HAL to make things build smoothly.</li>
<li>**The Bluetooth HCI / NBU ** – On this board, it’s required that you program the Bluetooth HCI image into a secondary Cortex-M3 processor. This is separate from the build process for Zephyr and requires a tool called <code>blhost</code>. Check out <a href="https://docs.zephyrproject.org/latest/boards/nxp/frdm_mcxw71/doc/index.html#nbu-flashing">the NBU section of the Zephyr</a> docs for more info.</li>
</ol>
<p>We’re excited to have another NXP board included in the list of boards that work with Golioth’s Bluetooth-to-Cloud capabilities. We will continue to add new vendors and chipset variations in the coming weeks. If you’d like to join the private access program, <a href="https://glth.io/bluetooth">sign up here</a>!</p>
]]></content:encoded></item></channel></rss>