Skip to content

Latest commit

 

History

History
100 lines (66 loc) · 4.68 KB

authoring_apps.md

File metadata and controls

100 lines (66 loc) · 4.68 KB

Authoring Apps

If you haven't already, check out the tutorial for how to get started writing apps. This guide picks up where the tutorial leaves off and provides practices and philosophy on how to build apps using pixlet.

Architecture

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

Each Tidbyt regularly sends heartbeats to the Tidbyt cloud, announcing what it has cached. The Tidbyt 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 we host apps internally, pixlet render executes the Starlark script and pixlet push pushes the resulting WebP to your Tidbyt.

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 that are published to the Tidbyt Community repo, users can install and configure them with the Tidbyt smartphone 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 value, 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") or 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

Cache

Use the cache module to cache results from API requests or other data that's needed between renders. We require sensible caching for apps in the Tidbyt Community repo. Caching cuts down on API requests, and can make your app more reliable.

Make sure to create cache keys that are unique for the type of information you are caching. Each app has its own cache, but that cache is shared among every user. Two installations of the same app by two different users will share the same cache.

A good strategy is to create cache keys based on the config parameters or information being requested.

Secrets

Many apps need secret values like API keys. When publishing your app to the Tidbyt community repo, encrypt sensitive values so that only the Tidbyt cloud servers can decrypt them.

To encrypt values, use the pixlet encrypt command. For example:

# replace "googletraffic" with the folder name of your app in the community repo
$ pixlet encrypt googletraffic top_secret_google_api_key_123456
"AV6+...."  # encrypted value

Use the secret.decrypt() function in your app to decrypt this value:

load("secret.star", "secret")

def main(config):
    api_key = secret.decrypt("AV6+...") or config.get("dev_api_key")

When you run pixlet locally, secret.decrypt will always return None. When your app runs in the Tidbyt cloud, secret.decrypt will return the string that you passed to pixlet encrypt.

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.