Our mission at balena is to “reduce friction for fleet owners and unlock the power of physical computing”– it’s what guides every balenista’s work. However, there’s still complexity to be found in developing applications, and we’re on our way toward solving this part of the puzzle.
balenaBlocks are an exploration of what we’ve been working on to reduce the friction of building your next edge or IoT app (for instance, a balenaHub submission!). We’re creating intelligent, drop-in chunks of functionality built to handle the basics, allowing you to focus on solving the hard problems.
Our first example: the browser block
FlickTics is a (fictitious) startup creating self-serve kiosk devices for theatres. Their devices are touch screens placed in the lobby of theatres, which allow customers to select seats, purchase and print their own tickets. FlickTicks’ expertise lay in the code they have developed for taking photos of theatres and generating a user interface for the ticket system.
However, in order to make the whole device work, they will also need to put effort into getting a working web browser onto the touchscreen device, and making sure it’s configured correctly so that customers can only perform the functions they wish. They could spend their time reading about Chromium flags and build this display solution from scratch, or…
…they can use blocks!
Meet the browser block: an image providing a hardware accelerated web browser, optimized for balena device architectures. By adding this block to FlickTicks’ docker compose file, the company’s developers no longer need to work out how to run X11 and Chromium. Nor how to configure the browser to lock it down, and remove the menu bars. They don’t even need to tell
browser the URL of their ticketing system in order to display it – it will be automatically found as long as it runs on the device’s port 80.
A closer look at how it works
It’s not possible to rigidly define what is and what is not a block. IoT devices and applications are hugely diverse, and there are a wide range of different functions that developers need to create. Those functions could be unique to a particular application, and therefore don’t require the creation of a block to help others.
Alternatively, the function may be applicable to so many people, that it should become a feature in balenaCloud or balenaOS. However, somewhere in that middle ground lay functions which lots but not all developers need, and contain enough complexity that having to implement them from scratch adds significant friction to an application’s development.
On the inside, blocks all have a common high-level architecture to them:
At their heart, blocks deploy one or more executables, run them on top of a balena base image, and wrap around them to make them easier to configure and manage. The wrapper should use the functions of the balena environment to discover and automatically configure the executable wherever possible, to intelligently offer an opinion on how the executable is likely to be used.
Back to the browser block example
We can take a look at the browser block again here, and see how this high-level design looks in this example:
There are two executables here: the (chromium](https://www.chromium.org/) browser itself, and X11 which is used to display the browser on connected displays. These have been combined with the base image, so that they are optimised to work on balena devices.
The wrapping code in this block intelligently configures Chromium, by detecting and setting the resolution of the connected display, and finding a local service exposing HTTP content on port 80 and displaying it. However, we have another common saying in balena:
“Batteries included but swappable”
In this case, the browser block will configure chromium how we think the application will need it, but everything can be re-configured however the developer needs. Setting environment variables for a device or fleet of devices in balenaCloud, allows the developers and fleet owners to optimise the browser for their needs. Need to show an external URL? No problem: set the
LAUNCH_URL variable. Need hardware acceleration? Just set
1. You don’t want the cursor to show? Easy.
What are the APIs for?
Time to look at some other blocks, explain what they do and how they expose an API for advanced scenarios:
A closer look at the audio block
One of balenaLabs’ most popular projects is balenaSound; an application which turns devices like Raspberry Pis into a multi-room audio streaming system. Since version 3, balenaSound has been using balenaBlocks: namely the
audio block runs PulseAudio as its executable. PulseAudio is notoriously tricky to configure correctly, so simply providing this executable optimised to run on balenaOS devices is a benefit in itself. However we learned above that a balenaBlock aims to do more than just run an executable, and this is certainly true of the
audio block. Like
browser there are environment variables which developers can set to further configure the
audio block, but that doesn’t work optimally for all operations. For example, consider how an application may want to alter the volume of the audio block.
If this were configured via an environment variable, it would work but each time the application wanted to change the volume the
audio block container would restart to apply the new setting and the sound output would stop. That’s not ideal, and so the
audio block provides a
setVolume() method on its API surface which allows applications to dynamically alter the volume. Similarly there are methods to change the audio source and sink, and more. You can find more details here.
Finabler (aka making balenaFin features easier to use)
Another good example of a block with a useful API is
finabler. This block was born during our internal balena 2020 Hack Week with the aim of making some of the balenaFin superpowers, namely its coprocessor, easier to use. Here’s a more detailed version of this block’s design:
The executable here is actually based around the firmata firmware that runs on the coprocessor itself. The
finabler ensures that an appropriate version of this firmware is flashed to the balenaFin device, sets some device configuration variables, and then exposes some information in the form of device tags:
As you can see, my balenaFin is a version 1.1.0, is running v2.0.1 of Firmata and is currently awake. And here’s where the block’s API comes in: it makes it super simple to sleep the device and have it wake up again in the future! Application developers can call the
sleep endpoint and pass in a delay period (seconds) before the device sleeps and the length of time (seconds again) the device will sleep for before powering back up. The
wake-eta tag will show you when the device will next wake up.
Wrap this in a simple loop, and you easily have an application that sleeps between operations, to work in a low power mode. The use cases here are numerous: a time-lapse camera, a low-power sensor or a vehicle monitoring system are just a few.
Let’s meet some more blocks
We’ve already introduced the
finabler blocks, but there are more. Here’s a quick overview of the others you can use today:
dashboard block wraps the Grafana executable to auto-generate customisable dashboards based on the discovered schema of an InfluxDB instance running on the device. It finds InfluxDB running on the device, queries the schema of the data, and creates a default dashboard to visualise the data. This enables application developers to quickly see what their data looks like, and gives them a starting point to create a customised dashboard. Once they have a dashboard how they need it to look, it can be saved to a JSON file and passed to the
dashboard block to use on a fleet of devices.
connector block wraps Telegraf in code which discovers and intelligently connects data sources and sinks. For example, the
connector block will find an MQTT broker running on the device and subscribe to a
sensor topic. An application developer can simply send JSON-formatted data to this topic and it will be ingested by
connector. Similarly, the block will find an instance of InfluxDB running on the device, and send any ingested data to it.
The use of environment variables allows a developer or fleet owner to enable lots of other data sources and sinks, both on the device and externally hosted.
A framebuffer is a bitmap in memory driving a video display. fbcp mirrors the primary framebuffer (HDMI) onto the secondary to drive a connected TFT screen. Connect one of the supported displays to your raspberry pi, deploy this block, and get your video output displayed with ease!
Wifi-connect enables the dynamic configuration of WiFi connections by monitoring connectivity and exposing a captive portal.
A bluetooth agent optimised for balenaOS devices, to provide a simple way to add bluetooth pairing to IoT applications.
A block that allows you to display a photo slideshow on a webpage over a web server.
How blocks reduce friction of project building
So, does having these balenaBlocks available actually reduce the friction of IoT application development? Here’s a true account of an app I developed.
A few months ago I swapped internet providers to a local company supplying fibre connections. The price and speed looked great, but given I need my connection to work from home, I needed to be sure it was reliable. The company graciously said I could trial the service for a month and only sign the contract if I was happy the connection was solid. So, I needed to periodically run a speed test and keep a log of the results.
With a quick search I found that Ookla provided a CLI executable that could output speedtest results in JSON format: https://www.speedtest.net/apps/cli So all I needed to do was get those results into a database and draw some charts based on them. Without balenaBlocks not only would I need to write some code to wrap around the CLI to capture the JSON result, but I would also need to write database client code to store it. I would also need to install and configure something like Grafana to view the data and make a dashboard. None of that is particularly hard, but it takes time, and it wasn’t where I wanted to spend my time.
With balenaBlocks, what I was able to do was this:
My docker compose file brings in InfluxDB and the mosquitto MQTT broker from their docker images, and the
connector balenaBlocks. All I needed to do was wrap the Ookla CLI and send the result to MQTT. And since my Pi is connected to an HDMI screen, I add the browser block, so that the dashboard was found and displayed.
Here’s the result in
And after a few minutes customising the generated dashboard:
How to contribute
Now that you’ve made it this far, you know what balenaBlocks are, and which ones are available to use today. Maybe you’re thinking “wow, I have some ideas for blocks that would be useful for your projects!” I challenge you to contribute to balenaHub.
We would love to hear what blocks you would like to see, and welcome community members building their own blocks. Why not pop over to the balena forums and start a discussion on your ideas. I’ll see you there.
Until next time
balenaBlocks are pre-built container images which application developers can add to their multicontainer apps to provide useful functionality. Their aim is to reduce the friction of creating IoT applications which enables rapid prototyping and development.
Why don’t you see what you can cook up using the blocks we have today? If you have any ideas for blocks you’d like to see developed, or would like to contribute towards, head over to our forums and start a discussion. We’d love to know what people are making, and what would reduce their friction.