Building a Pixel Art Bitcoin Tracker
Building a Pixel Art Bitcoin Tracker
Mats Linander

Pixel-based apps for fun & profit

We've been working on Tidbyt for about a year now. Developing the individual pixel-based apps that run on the platform is a big part of what's made the project fun. Today, we're happy to be able to share some of that joy!

Pixlet is the open source version of our internal app development environment. It makes it pretty darn simple to build pixel apps. In our production environment, Pixlet is running 24/7 behind the scenes to power everything on Tidbyt from stock prices or weather information to tracking the NYC Ferry.

In this guide, I'll walk you through how to build a pixel-based Bitcoin price tracker.

What is this thing?

Pixlet is a command-line interface that runs and renders Pixlet scripts. The scripts are written in a language that's very similar to Python. They can use Pixlet's library of "widgets" for laying out and drawing graphics. The result of running a script is a GIF or WebP animation, which can be viewed in a browser or "pushed" to a Tidbyt device via the Tidbyt API.

Down the road, we hope to fully open up our systems to allow Pixlet apps to be published and made available to other users.

A picture and some code

This Bitcoin tracker is fully implemented with Pixlet. It fetches the current price of a Bitcoin (in USD) and displays it on a Tidbyt device. You might be surprised by how simple the code is.

To get started, let's install Pixlet and make sure you are able to run it. Check out the installation instructions if you're using Linux. If you're on macOS and have Homebrew installed, you should be able to just run:

brew install tidbyt/tidbyt/pixlet

Once you have Pixlet installed, create a file named bitcoin.star and write a simple Hello World script:

load("render.star", "render")
def main():
    return render.Root(
        child = render.Text("Hello, World!")
    )

If you've installed Pixlet correctly, you should be able to execute this script with:

$ pixlet serve --watch bitcoin.star

Browse to http://localhost:8080 and you should see a "Hello, World!"

Getting Bitcoin data

To get closer to a truly useful app, we'll be pulling in some Bitcoin data. CoinDesk's Bitcoin Price Index API is free to use and requires no authentication. We'll use the http module to retrieve the data.

Pixlet includes several modules from the Starlib library. This is sort of a standard library for Starlark (the langauge that Pixlet scripts are written in), and it's very handy when building anything but the simplest applet.

Update your bitcoin.star to get actual Bitcoin data:

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

COINDESK_PRICE_URL = "https://api.coindesk.com/v1/bpi/currentprice.json"

def main():
    rep = http.get(COINDESK_PRICE_URL)
    if rep.status_code != 200:
        fail("Coindesk request failed with status %d", rep.status_code)

    rate = rep.json()["bpi"]["USD"]["rate_float"]

    return render.Root(
        child = render.Text("BTC: %d USD" % rate)
    )

Save the file and refresh your browser. You should now see the live price of Bitcoin.

Voilà. A perfectly functional Bitcoin price tracker.

Adding an icon

To make our applet a bit snazzier, I went over to Pixilart and drew this simple Bitcoin icon:

Pixlet allows us to embed graphics in our scripts through the Image widget. We'll use the encoding/base64 module to embed the image in our source code. Finally, we'll use the Row Widget to lay out the icon and the price of Bitcoin, side-by-side:

load("render.star", "render")
load("http.star", "http")
load("encoding/base64.star", "base64")

COINDESK_PRICE_URL = "https://api.coindesk.com/v1/bpi/currentprice.json"

# Load Bitcoin icon from base64 encoded data
BTC_ICON = base64.decode("""
iVBORw0KGgoAAAANSUhEUgAAABEAAAARCAYAAAA7bUf6AAAAlklEQVQ4T2NkwAH+H2T/jy7FaP+
TEZtyDEG4Zi0TTPXXzoDF0A1DMQRsADbN6MZdO4NiENwQbAbERh1lWLzMmgFGo5iFZBDYEFwuwG
sISCPUIKyGgDRjAyBXYXMNIz5XgDQga8TpLboYgux8DO/AwoUuLiEqTLBFMcmxQ7V0gssgklIsL
AYozjsoBoE45OZi5DRBSnkCAMLhlPBiQGHlAAAAAElFTkSuQmCC
""")

def main():
    rep = http.get(COINDESK_PRICE_URL)
    if rep.status_code != 200:
        fail("CoinDesk request failed with status %d", rep.status_code)

    rate = rep.json()["bpi"]["USD"]["rate_float"]

    return render.Root(
        child = render.Row( # Row lays out its children horizontally
                children = [
                    render.Image(src=BTC_ICON),
                    render.Text("$%d" % rate),
                ],
        )
    )

This clearly leaves something to be desired as far as layout is concerned, but the individual elements (the icon and the price) aren't too shabby!

Beautification

By default, Row will pack its children as closely together as it possibly can. That's often a useful behaviour, but in this case perhaps not so much. We can instruct Row to use as much horizontal space as possible by passing expanded=True, and then use the main_align and cross_align parameters to adjust how the children are spaced out. For details on these parameters, check out the full Widget reference.

We'll also place the Row itself in a Box to ensure that it's placed in the vertical center of the screen. Box actually centers horizontally as well, but since our Row is expanded that won't matter.

Don't worry if all this alignment stuff feels a bit confusing at first. It'll be a lot clearer when you've had a chance to play around with it.

load("render.star", "render")
load("http.star", "http")
load("encoding/base64.star", "base64")
load("cache.star", "cache")

COINDESK_PRICE_URL = "https://api.coindesk.com/v1/bpi/currentprice.json"

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

def main():
    rate_cached = cache.get("btc_rate")
    if rate_cached != None:
        print("Hit! Displaying cached data.")
        rate = int(rate_cached)
    else:
        print("Miss! Calling CoinDesk API.")
        rep = http.get(COINDESK_PRICE_URL)
        if rep.status_code != 200:
            fail("Coindesk request failed with status %d", rep.status_code)
        rate = rep.json()["bpi"]["USD"]["rate_float"]
        cache.set("btc_rate", str(int(rate)), ttl_seconds=240)

    return render.Root(
        child = render.Box(
            render.Row(
                expanded=True,
                main_align="space_evenly",
                cross_align="center",
                children = [
                    render.Image(src=BTC_ICON),
                    render.Text("$%d" % rate),
                ],
            ),
        ),
    )

Now that's a Bitcoin tracker.

What's next

If you're happy with the applet, you can render it to a GIF or WebP animation with pixlet render. And if you have a Tidbyt, you can push the rendering to it. To run the script, and to push the resulting WebP image to a Tidbyt device, you'd do something like this:


pixlet render bitcoin.star
pixlet push YOUR-DEVICE-ID bitcoin.webp --api-token=YOUR-API-TOKEN

Your device's ID and API token are available in the Tidbyt smartphone app. Go to Settings and tap "Get API key".

The Pixlet tutorial over on GitHub offers more detail on this particular applet, as well as documentation on Pixlet's full library of Widgets. Happy hacking!