In a
previous blog post, we introduced balena Blocks, intelligent, drop-in chunks of app functionality built to handle common tasks. Then we showed off some examples of blocks we have already made (like
audio,
sensor, and pulse). This post is going to explore the anatomy of a block in more depth, and hopefully inspire more people to make and share their own blocks.
If you’ve been wondering, “how do I make my own balenaBlock?” and “how do I show it off to all of my friends and encourage others to use it?”, this is the article for you.
A reminder about balenaBlocks
A balenaBlock (from this point we’ll just use block) is a container image, which developers can add to their
multicontainer application to provide functionality they need. The aim is to reduce the friction of application development by providing fragments of functionality, much like a code package provides to software developers. Similar to how developers don't want to have to write math and string manipulation functions themselves, IoT developers don't want to have to create containers for every function their application may need to work.
Why spend hours getting a browser to run well in a container, when the
browser block already does it?
A quick, closer look inside of a block
On the inside, blocks have a common high-level architecture to them:
They 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 can use the functions of the balena environment to discover and automatically configure the executable wherever possible, and to intelligently offer an opinion on how the executable is likely to be used.
You can find all of the blocks we've built so far over on the
balenaHub.
What should become a block?
Just like with app stores and software package managers, more people contributing blocks improves the usefulness of the whole ecosystem. We at balena don't want to make all the blocks, and we don't want to be the gatekeepers of what is and is not a block, either. We want you, the community, to make pieces of functionality you have in your projects into something that others can benefit from.
We've found that a good rule of thumb for what is a good candidate for a block is "Would it do anything useful if it were deployed to a device on it's own?"
For instance, if you installed a database onto Intel NUC, it wouldn't be much use on it's own. So that sounds like a good candidate for a block, since other services could make use of it. Whereas a container categorising images and displaying the category on a screen: that sounds like an app.
How to make a block
OK, so let's assume I've done a good job of convincing you to get involved, and you're ready to make a block... how do we go about that? Let's lay out a 5 step plan:
Step 1: Identify a pattern
There's very little point in creating a block nobody needs, so start off by looking for patterns of repeated functionality in your and others projects. For example, it was pretty clear to my colleague (and possibly nicest man in the world)
Hardware Hacker Alan that lots of projects needed a feed from an attached camera. He had made some
AI projects, he'd seen the
kerberos (surveillance camera project) blog post and heard others discussing projects like a cat cam, a hydroponic web stream and a baby monitor. All of those things needed a camera feed.
Alan had identified a pattern where a block could prove useful.
Step 2: Find an implementation
The next step is to solve the need, in this example get a camera feed, by finding (or making if you're a hotshot developer!) something that provides the function. Alan took a look around and found that balena already had a project
balenaCam which was getting a camera feed and streaming it over WebRTC. The app was, at its heart, using
gstreamer to capture a video feed, in this case from any attached camera.
Step 3: Find the boundaries and interfaces
Now we have an implementation, we need to figure out the boundaries of the block. You don't want your block to be too specific, or it limits the scope of other use cases that can make use of its functionality.
Alan didn't want to take the whole of balenaCam as his block, since that would only prove useful for anyone building something on top of WebRTC. Instead, he wanted to just take gstreamer's output, and make it possible to send that somewhere else as an
RTSP (Real-Time Streaming Protocol) feed.
Once you know the boundary, you can then consider how another service (or block) can interface with your block. It may be that your block takes input from another service, like the
connector block takes data from an MQTT broker, or the
Audio block which takes a pulse audio source.
Alan decided his block would send an RTSP feed to somewhere else - so his interface is actually a push to another service.
Step 4 (optional): Wrap it in balena magic
The thing that can make the difference between just an executable in a container, and a balena block, is the way you can wrap it in some code or a bash script, to make use of some balenaCloud features. The
browser block is a good example to use here. Getting Chromium to run inside a container, although useful, is only some of what the block provides. It also wraps the browser in some code, so that it can be configured easily. The code does things for itself, like detect the resolution of the attached display, and size the output window accordingly. It reads in any
environment variables, so that a user can set things like whether the cursor shows or is hidden, and what URL to show on startup.
Another thing your code can do is implement an API so that other services can interact with it at runtime. The browser block does this to allow the URL to be changed and things like GPU acceleration and kiosk mode enabled or disabled. The
Fin block provides an API so that other services can perform actions on the balenaFin coprocessor, like put the device to sleep.
Alan's capture block doesn't need an API, but he has decided that users should be able to configure it with environment variables, so that they can decide where the RTSP feed should be sent, and which port to use.
Step 5: Dress it up for balenaHub
The way applications and blocks appear in balenaHub is driven by the code in your GitHub repository. So there are a few things to add, which drive this behavior, namely a logo image and a contract. The latter sounds like a big deal, but actually it's just a text file with a certain (YAML) format.
The easiest thing to do is just to take a copy of the contents of the
audio block's contract and paste it into a file named
balena.yml
at the root of your code repository. Now change the various bits as they relate to your block, such as the name, a description, and then types of device you have tested the block on.
NOTE: The logo is a PNG file, and the only constraints are that it should be 512x512 pixels square, and pointed to by the contract file above.
Here's an example:
name: capture-block
description: >-
Sends an RTSP video feed from a connect camera to a configurable endpoint
version: 0.0.1
type: sw.block
assets:
repository:
type: blob.asset
data:
url: 'https://github.com/balenablocks/capture
logo:
type: blob.asset
data:
url: 'https://raw.githubusercontent.com/balenablocks/capture/master/logo.png'
data:
defaultDeviceType: raspberrypi4-64
supportedDeviceTypes:
- raspberry-pi
- raspberry-pi2
- raspberrypi3
- raspberrypi3-64
- raspberrypi4-64
Check out our documentation on
balena.yml to ensure you’re on the right track.
How to contribute a block
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!”. Read on my friend!
You'll need a
balenaCloud account (first ten devices are free and fully-featured), so that you can add and manage your block. Then head over to the docs where we have guidance to help you
develop with blocks.\
We also have a handy
blog post to give more details about managing blocks and the roadmap for their onward development.
Until next time
balenaBlocks are pre-built container images which application developers can add to their multi-container 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.