Tidbyt Application Development Fundamentals

I recently purchased a Tidbyt second-generation smart display. I was immediately intrigued by the idea of creating custom applications that could present fun messages or images to those who either have to walk by my office or those I interact with in a Zoom call. The first idea that came to mind was to create a clock that rotated through different Spectro Cloud graphics. Why Spectro Cloud? That’s where I work, so having the logo displayed on my Tidbyt would be a fun way to suprise my coworkers during Zoom calls.

Below is a preview of the final product. The clock rotates through different Spectro Cloud graphics every 60 seconds and supports a 24-hour format.

A view of a browser window displaying the intro application

I want to share the lessons I learned along the way. This post will help you create custom Tidbyt applications and share them with the world. There are so many possibilities with Tidbyt, and the official application store already has a wide variety of applications to choose from. Think of this article as a supplemental guide to the official documentation.

Don’t have a Tidbyt? No problem
You don’t need a physical Tidbyt to start building applications. You can use the Pixelet CLI to start a local development server and preview your applications in a web browser.

Where to Start?

The first step is to install Pixelet, the Tidbyt development tool. The Installing Pixlet guide provides detailed instructions on how to install Pixelet.

Once you have Pixelet installed, create a new project by issuing the following command:

1
mkdir my-starter-app && cd my-starter-app && pixlet create my-starter-app

You will be prompted for an application name, description, and summary. After you provide the information, Pixelet will create a new directory with the project files.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
Name (what do you want to call your app?): Sup World
Summary (what's the short and sweet of what this app does?): Demo app
Description (what's the long form of what this app does?): Demo app.
Author (your name or your Github handle): Karl Cardenas

App created at:
	/Users/karlcardenas/projects/tidbyt/my-starter-app/sup_world.star

To start the app, run:
	pixlet serve sup_world.star

For docs, head to:
	https://tidbyt.dev

Pixelet will generate two new files, sup_world.star and manifest.yaml. The sup_world.star file is where you will write your application code, and the manifest.yaml file is where you will define the application metadata.

Below is the content of the sup_world.star and manifest.yaml files.

1
2
3
4
5
6
---
id: sup-world
name: Sup World
summary: Demo app
desc: Demo App.
author: Karl Cardenas
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
"""
Applet: Sup World
Summary: Demo app
Description: Demo App.
Author: Karl Cardenas
"""

load("render.star", "render")
load("schema.star", "schema")

DEFAULT_WHO = "world"

def main(config):
    who = config.str("who", DEFAULT_WHO)
    message = "Hello, {}!".format(who)
    return render.Root(
        child = render.Text(message),
    )

def get_schema():
    return schema.Schema(
        version = "1",
        fields = [
            schema.Text(
                id = "who",
                name = "Who?",
                desc = "Who to say hello to.",
                icon = "user",
            ),
        ],
    )

The next step is to start the application by running the following command:

1
pixlet serve sup_world.star

Pixelet will start a local development server on http://localhost:8080. You can preview the application in a web browser by navigating to http://localhost:8080.

Press Ctrl + C to stop the development server. If you are on a Mac, you can press Cmd + C. You will start and stop the development server multiple times throughout the development process.

Core Concepts

Now that you have an application available and understand how to start the development server let’s explore the core concepts of Tidbyt applications.

Programming Language

Tidbyt applications are written in Starlark, which was designed to manage and maintain configuration for the Bazel build system. The syntax may look familiar if you have experience with Python.

Starlark Implementation

Tidbyt used the Go implementation of Starlark.

The best resource I found for understanding Starlark’s capabilities is the Starlark Language Specification document. This document provides a comprehensive overview of the language and its capabilities, such as built-in functions, data types, and control structures. Do keep this document handy as you build your Tidbyt applications. I found myself referencing it multiple times throughout the development process.

Libraries

Tidbyt provides a set of libraries, also called modules, that you can use to build your applications. You can find the list of available modules on the Tidbyt Modules page. I would encourage you to review the modules page and to keep it handy as you build your applications. For example, the load("render.star", "render") render module is the most important module you will use to build your applications. The rendering module provides a set of functions to create and render elements on the Tidbyt display.

Rendering Elements

You will use the render module to create and display elements on the Tidbyt display. All Tidbyt applications must return a Root element that contains the child elements you want to render. The Root element is the root of the element tree and is the parent of all other elements. Only one Root element is allowed per application.

When building my application, I struggled with understanding how to create and render elements. I couldn’t find the reference page for the render module. I later learned that the Tidbyt Wiget page acts as the reference page for the render module. The page provides information on using common elements such as Text, Box, Row, and Column, and many other elements that will help you paint your application on the Tidbyt display.

My best advice to you is to replace the return render.Root function and experiment with different elements and the code snippets provided on the Widget page to observe how the different elements render on the Tidbyt display. The more you experiment, the more you will understand how to make your vision a reality.

1
2
3
    return render.Root(
        child = render.Text(message),
    )

For example, the code below creates a Column element that contains three Box elements. The Box element is a simple element that renders a colored box on the Tidbyt display.

1
2
3
4
5
6
7
8
9
    return render.Root(
        child = render.Column(
            children=[
                render.Box(width=10, height=8, color="#a00"),
                render.Box(width=14, height=6, color="#0a0"),
                render.Box(width=16, height=4, color="#00a"),
            ],
        ),
)
Image of a web browser tab with three columns in different colors stacked on the far left side of the screen

If you do this for all the widgets, you will better understand how to create and render elements on the Tidbyt display.

Learn from others
Check out the Tidbyt Community repository on GitHub. You can learn a lot from the available applications in the apps directory.

Images

You can include images with your application. When images are pre-loaded into the application, they must be base64 encoded. You can convert an image to base64 format using an online tool such as Base64 Image Encoder. Once you have the base64 image, you can use the render.Image element to render the image on the Tidbyt display. The render.Image element has a src property that accepts the base64 image data. You need to import the encoding/base64.star module to decode the base64 image data. The encoding/base64.star module provides a set of functions to encode and decode base64 data. You can use the base64.decode function to decode the base64 image data.

Question

Can I import a file instead of hardcoding the base64 image data?

Unfortunately, you cannot import an image file directly into the Tidbyt application. You must convert the image to base64 format and hardcode the base64 image data in the application code. The alternative is to download the image from a URL and pass it to the render.Image element’s src attribute. Check out the Loveland Ski Area Webcams application for an example of how to download an image from a URL and pass it to the render.Image element.

The code example below is from the Crypto Tracke guide. The majority of the code is omitted for brevity. The code snippet shows how to decode the base64 image data and pass it to the render.Image element. Keep in mind that the image must be either a PNG, GIF, or JPEG image.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
load("encoding/base64.star", "base64")

BTC_ICON = base64.decode("""
iVBORw0KGgoAAAANSUhEUgAAABEAAAARCAYAAAA7bUf6AAAAlklEQVQ4T2NkwAH+H2T/jy7FaP+
TEZtyDEG4Zi0TTPXXzoDF0A1DMQRsADbN6MZdO4NiENwQbAbERh1lWLzMmgFGo5iFZBDYEFwuwG
sISCPUIKyGgDRjAyBXYXMNIz5XgDQga8TpLboYgux8DO/AwoUuLiEqTLBFMcmxQ7V0gssgklIsL
AYozjsoBoE45OZi5DRBSnkCAMLhlPBiQGHlAAAAAElFTkSuQmCC
""")

# Later in the code. Pass the image data to the render.Image element.
    return render.Root(
        child = render.Row( 
                children = [
                    render.Image(src=BTC_ICON),
                ],
        )
    )

Size

You need to be mindful of the size of the image you render on the Tidbyt display. The Tidbyt display can display 64x32 pixels. If you render an image larger than 64x32 pixels, the image will be cropped. It may take a few tries to get the image size right. I used the free image editing tools from Pinetools to re-size images down to the correct size for my application.

Pixelate

A common mistake when building a Tidbyt application is assuming you have to pixelate the image before rendering it on the display. The Tidbyt display is already pixelated, so you do not need to pixelate the image before rendering it on the display. If you pixelate the image before rendering it on the display, the results may differ from what you expect. Instead, focus your attention on the size of the image and ensure its quality.

Animations

Tidbyt applications support animations. You can animate elements using the render.Animation element. The animate element provides a set of methods that you can use to manipulate the element’s behavior. The list of available methods is on the Tidbyt Animation page.

Fonts

Tidbyt supports eight different fonts that you can use in your applications. When using the render.Text function, you can set the font property to one of the eight available fonts. Specify the font by its name. For example, to use the 5x8 font, set the font property to 5x8.

1
2
3
4
5
render.Text(
    "I am a text element",
    font = "5x8",
    color = "#6a5d9d",
),

Review the Fonts in Pixlet page for more information on the available fonts.

Architecture

Tidbyt has a small section in its documentation where Tidby Architecture is explained. This section is sparse, but it conveys important information such as the display size, caching behavior, secrets management, failure handling, and performance profiling. However, as you comb through the documentation, you will find that the information is scattered throughout the site. Below are some key points to keep in mind as you build your applications.

  • The default application cycle speed is 15 seconds. This means that Tidbyt will display a different application for most end users every 15 seconds. The user can change this value, but it is important to remember that the default value is 15 seconds. In other words, consider the 15-second cycle when designing your applications. If you have an animation that lasts longer than 15 seconds, the animation will be cut off when the application cycle changes, unless the user has changed the cycle speed, or you use the show_full_animation property to override the user’s configuration. Check out the App cycle speed page to learn more about the application cycle speed.

  • If you use the random module, keep in mind the random seed is updated every 15 seconds. This means that if you use the random module to generate random numbers, the numbers will change every 15 seconds.

  • If you use caching, remember that the cache is global and unique per application, not per installation. All Tidbyt applications share the same cache. This means that if you cache a value in your application, then all other instances of your application will use the same cached value. This is important to keep in mind when building applications that rely on caching.

  • Tidbyt pushes a WebP image file to the display. This image file is sent to the display from their servers. During local development, this is simulated through Pixelet. The image file is generated by your local server, which you can preview in a web browser. If you are creating applications where behavior changes, say a clock application that rotates images every 1 minute. You will not observe the change while the local server is active. The reason that Tidbyt and Pixlet send the image file once and only once to the display. In the real world, the application image file is sent from Tidbyt servers to the device right before the application cycle is about to complete for the current application. So, if you expect a new image to be displayed every 1 minute, you must start and stop the Pixelet server to observe the change. In other words, to get a new image to display in development, you must trigger the change with a server restart. There is extensive discussion about this behavior in the following GitHub issue with potential workarounds.

  • The screen size is 64x32 pixels.

Schema Configuration

Each Tidbyt application can accept configuration values from the user. The configuration values are set in the Tidbyt application settings and passed to the main function as a config object. The config object will contain any schema fields defined in the schema object.

Using the example code from earlier, a function named get_schema returns a schema object that defines the configuration fields that the user can set in the application settings. The schema object contains a list of fields that the user can set. Each field has an id, name, desc, and icon property. The id property is the unique identifier for the field. The name property is the field’s name displayed in the application settings. The desc property is the description of the field that is displayed in the application settings. The icon property is the icon that is displayed next to the field in the application settings.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
def get_schema():
    return schema.Schema(
        version = "1",
        fields = [
            schema.Text(
                id = "who",
                name = "Who?",
                desc = "Who to say hello to.",
                icon = "user",
            ),
        ],
    )

Each schema field can support different types of input. For example, the schema.Text field supports text input, the schema.Toggle field supports a toggle switch, and the schema.Option field supports a dropdown menu. The schema object also supports default values for each field. The default value is the value that is set when the user installs the application. The user can change the value in the application settings.

You can also create more advanced workflows through some of the built-in schema fields. For example, you can trigger an OAuth flow to authenticate the user, get the current geolocation, or use dynamic fields to autogenerate values such as an ID.

For a real-world example, check out the Spectro Cloud clock application schema. Users can choose between the following options:

  • Use a 12-hour format or 24-hour format.
  • Enable or disable the clock.

Below is the schema configuration for the Spectro Cloud clock application.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
def get_schema():
    return schema.Schema(
        fields = [
            schema.Toggle(
                id = "clock",
                name = "Display Clock",
                desc = "Display the clock",
                icon = "clock",
                default = True,
            ),
            schema.Toggle(
                id = "24hour",
                name = "24 Hour Time",
                icon = "clock",
                desc = "Choose whether to display 12-hour time (off) or 24-hour time (on). Requires Display Clock to be enabled.",
                default = False,
            ),
        ],
        version = "1",
    )

Inside the main function, you can inspect the config object to get the configuration values the user sets.The config object contains the configuration values set by the user and other important metadata, such as the timezone.

The config object has a set of methods that you can use to get the configuration values. For example, you can use the config.get function to get a configuration value by specifying its id. The config.get function returns the configuration value as a string. If you know the field type, you can use specific functions such as config.bool to get a boolean value, or config.str to get a string value.

1
2
3
4
5
6
def main(config):
    who = config.str("who", DEFAULT_WHO)
    message = "Hello, {}!".format(who)
    return render.Root(
        child = render.Text(message),
    )

Publish Application

If you aspire to share your application with the world, you can publish it in the Tidbyt application store. To publish an application, you must fork the Tidbyt Community repository. Once you have the repository forked and cloned to your local machine, you can create a new git branch and add your application to the apps directory.

Tidbyt has detailed instructions on how to publish an application in the Publishing Apps guide. Check out past Pull Requests (PR) from the community repository to get an idea of how to structure your PR and what information to include in the PR description. Feel free to check out the PR for the Spectro Cloud clock application.

Additional Help

If you need additional help, you can reach out to the Tidbyt community on the Tidbyt Discord channel. You can also use the Tidbyt Forum to ask questions and get help from the community.

Closing Thoughts

Building applications for Tidbyt can be a fun and rewarding experience. The possibilities are endless, and the community is constantly developing new and innovative applications. Use the knowledge provided in this article to start building your own applications. Remember to experiment with different elements, review the available modules, and check out the Tidbyt community repository for inspiration.

Spectro Cloud Clock App https://github.com/tidbyt/community/tree/main/apps/spectro_cloud_clock card image
0%