Skip to content

Authoring Apps

This guide provides best practices on how to build apps to integrate well with the Tronbyt ecosystem. If you haven't already, check out the tutorial for how to get started writing apps.

Architecture

Pixlet is heavily influenced by the way Tronbyt devices work. The Tronbyt displays tiny 64x32 animations and images. It keeps a local cache of the apps that are installed on it.

Each Tronbyt regularly sends heartbeats to the Tronbyt cloud, announcing what it has cached. The Tronbyt cloud decides which app needs to be rendered next, and executes the appropriate Starlark script. It encodes the result as a WebP image, and sends it back to the device.

To mimic how apps are hosted, pixlet render executes the Starlark script and pixlet push pushes the resulting WebP to your Tronbyt.

Config

When running an app, Pixlet passes a config object to the app's main():

def main(config):
    who = config.get("who")
    print("Hello, %s" % who)

The config object contains values that are useful for your app. You can set the actual values by:

  1. Passing URL query parameters when using pixlet serve.
  2. Setting command-line arguments via pixlet render.

When apps are published to the Tronbyt apps repo, users can install and configure them with the Tronbyt app. Define a schema for your app to enable this.

Your app should always be able to render, even if a config value isn't provided. Provide defaults for every config field, or check if the value is None. This will ensure the app behaves as expected even if config was not provided.

For example, the following ensures there will always be a value for who:

DEFAULT_WHO = "world"

def main(config):
    who = config.get("who", DEFAULT_WHO)
    print("Hello, %s" % who)

The config object also has helpers to convert config values into specific types:

config.str("foo") # returns a string, or None if not found
config.bool("foo") # returns a boolean (True or False), or None if not found

Caching

Many apps retrieve data from external services and APIs. It's important both to be mindful of the request rates hitting these services, and of the possibility of bad internet weather causing some requests to fail. Caching plays an important role here.

For the majority of apps, it's sufficient to rely on the built-in cache functionality of the HTTP module. Simply pass in ttl_seconds when making a request, and the HTTP response will be cached accordingly. This allows subsequent requests (until the TTL has expired) to be served quickly, without hammering the upstream API.

Fail

The fail() function will immediately end the execution of your app and return an error. It should be used incredibly sparingly, and only in cases that are permanent failures.

For example, if your app receives an error from an external API, try these options before fail():

  1. Return a cached response.
  2. Display a useful message or fallback data.
  3. print() an error message.
  4. Handle the error in a way that makes sense for your app.

Performance profiling

Some apps may take a long time to render, particularly if they produce a long and complex animation. You can use pixlet profile to identify how to optimize the app's performance. Most apps will not need this kind of optimization.

$ pixlet profile path_to_your_app.star

When you profile your app, it will print a list of the functions which consume the most CPU time. Improving these will have the biggest impact on overall run time.