Building a Flight Arrivals App for My Tidbyt Smart Display

This Christmas, I received a Tidbyt smart display as a gift. The Tidbyt, for those who were not successfully targeted with Instagram adds like me, is a 64x32 pixel display, housed inside a sleek walnut case, with a suite of simple apps immediately available for use. If you’re looking for an attractive small display to passively flash information like weather, word clocks, surf forecasts, etc. - I can’t recommend it enough.

But more than anything preloaded, what drew me to the Tidbyt was it’s extensibility - as it offers the ability to run custom apps, created using the Tidbyt team’s in-house Pixlet SDK. As you might imagine, it didn’t take me long to start programming my own app!

Concept

I love traveling, and have always loved looking at airport arrival and departure boards to see the many places around the world planes are coming in from and taking off for. Thusly inspired, my goal for this project was to figure out how to best build a flight arrivals board! After a little thinking, I aligned on the set of requirements below:

  • Requirement 1: show the upcoming flight arrivals at an airport of my choosing (LAX)
  • Requirement 2: present key information about the next N flights, such as time of arrival, airline, and departing airport
  • Requirement 3: exclude clutter from codeshare flights and ensure the Tidbyt display is visually attractive

Plan

With those requirements in mind, I figured it made sense to break my development process into three primary steps:

  1. Choose a Flights API: there are a ton of different APIs available online which allow flight information to be systematically accessed. However, they all contain different sets of fields, and have different pricing schemes
  2. Build the API Integration: let’s ensure that we can authenticate to the selected API, download the desired information as a JSON output, and line up the data we’d like to use in the app
  3. Design the output for the Tidbyt Display: the primary task here is to determine how much data to show, and what layout is best limited pixel real estate could best be used to show the flight information

Implementation

Flight Arrivals API

After much poking about, I decided to use the AirLabs Flight Schedules API to get the live flight data. While it doesn’t have the model of each plane, it has everything else I was looking for, and also allows us 1,000 API requests per month as part of its Free Tier, twice as much as other providers. This is crucial, because it will allow us to refresh the data more often, and rely less on cached (and potentially stale) data.

After deciding on Airlabs, it wasn’t hard to get a quick test version of the code below working, which simply stores all LAX arrivals as arrivals.json

import requests
import json

params = {
  'api_key': 'SECRET',
  'arr_iata': 'LAX'
}

method = 'schedules'
api_base = 'http://airlabs.co/api/v9/'
api_result = requests.get(api_base+method, params)
api_response = api_result.json()

with open('arrivals.json', 'w') as f:
  json.dump(api_response, f, ensure_ascii=False)

print(json.dumps(api_response, indent=4, sort_keys=True))

Tidbyt App

With the general mechanics of the API request good to go, I was excited to start development on the Tidbyt app itself. Since I had never used the Pixlet SDK before, I spent some time getting the Crypto Tracker tutorial running, and used some of that code to scaffold the start of my app as well.

Development Environment

One cool part of the Pixlet SDK is that it offers tools to quickly build and visualize code as you go, in a web browser. You simply need to run the pixlet serve command for whatever file you’re working on, and then you can access a preview of the output via http://127.0.0.1:8080/ in your browser. Any print statements in your code will write to the terminal, while any render outputs in your script will show right up!

pixlet serve scheduled_arrivals.star -w

Tidbyt App Prototyping

Application Code

Finally, the meat and potatoes of the operation! Here’s a quick breakdown of what we actually do in our app:

  • Fetch the arriving flights data from the AirLabs API
  • Utilize Tidbyt’s caching mechanisms, so that we can limit the number of API requests. We’ll set a time-to-live (TTL) of 7200 seconds, so that if we have data on hand that’s less than two hours old, we’ll use that instead of hitting the AirLabs API afresh
  • Sort through the list of flights in the JSON, keeping only flights which are yet to arrive, and which aren’t codeshare flights
  • Present the next four arriving flights on the display as three columns of information: arrival time, departure airport, and airline/flight number
load("render.star", "render")
load("http.star", "http")
load("cache.star", "cache")
load("encoding/json.star", "json")
load("humanize.star", "humanize")
load("time.star", "time")

method = 'schedules'
api_base = 'http://airlabs.co/api/v9/'
params = {
  'api_key': 'SECRET',
  'arr_iata': 'LAX',
  '_fields': 'flight_iata,airline_iata,dep_iata,arr_iata,arr_time,arr_estimated,\
  arr_delayed,status,arr_terminal,arr_gate,cs_flight_iata,arr_estimated_ts'
}

# 1,000 API calls per month
# ~33 per day
# so let's do every 2 hours to be safe (TTL of 7200 seconds)

def main():
	flights = cache.get("flights")
	if flights != None:
		print("Hit! Displaying cached data")
		api_response = flights
	else:
		print("Miss! Calling Airlabs API")
		print("Making request at: " + api_base+method)
		api_result = http.get(url = api_base+method, params = params)
		if api_result.status_code != 200:
			fail("Airlabs API request failed with status %d", api_result.status_code)
		api_response = api_result.body()
		cache.set("flights", api_result.body(), ttl_seconds = 7200)

	flight_list = json.decode(api_response)["response"]
	cleaned_flight_list = []
	print("Time is now: " + str(time.now().in_location("America/Los_Angeles")))
	for item in flight_list:
		if ("arr_estimated" in item.keys()) and (item["cs_flight_iata"] == None):
			if (item["arr_estimated"] >= str(time.now().in_location("America/Los_Angeles"))):
				cleaned_flight_list.append(item)
	cleaned_flight_list_sorted = sorted(cleaned_flight_list, key=lambda i: i["arr_estimated"])
	#print(cleaned_flight_list_sorted[0:4])

	return render.Root(
        render.Row(
        	children = [
        		render.Column(
        			children = [
        				render.Text("  LAX", font = 'tom-thumb', color = "#7695f5"),
        				render.Text("", height = 2),
        				render.Text("%s" % cleaned_flight_list_sorted[0]["arr_estimated"][11:], font = 'tom-thumb'),
        				render.Text("%s" % cleaned_flight_list_sorted[1]["arr_estimated"][11:], font = 'tom-thumb'),
        				render.Text("%s" % cleaned_flight_list_sorted[2]["arr_estimated"][11:], font = 'tom-thumb'),
        				render.Text("%s" % cleaned_flight_list_sorted[3]["arr_estimated"][11:], font = 'tom-thumb'),
        			]
        		),
        		render.Column(
        			children = [
        				render.Text(" Arr", font = 'tom-thumb', color = "#7695f5"),
        				render.Text("", height = 2),
        				render.Text(" %s" % cleaned_flight_list_sorted[0]["dep_iata"], font = 'tom-thumb'),
        				render.Text(" %s" % cleaned_flight_list_sorted[1]["dep_iata"], font = 'tom-thumb'),
        				render.Text(" %s" % cleaned_flight_list_sorted[2]["dep_iata"], font = 'tom-thumb'),
        				render.Text(" %s" % cleaned_flight_list_sorted[3]["dep_iata"], font = 'tom-thumb'),
        			]
        		),
        		render.Column(
        			children = [
        				render.Text("ivals", font = 'tom-thumb', color = "#7695f5"),
        				render.Text("", height = 2),
        				render.Text(" %s" % cleaned_flight_list_sorted[0]["flight_iata"], font = 'tom-thumb'),
        				render.Text(" %s" % cleaned_flight_list_sorted[1]["flight_iata"], font = 'tom-thumb'),
        				render.Text(" %s" % cleaned_flight_list_sorted[2]["flight_iata"], font = 'tom-thumb'),
        				render.Text(" %s" % cleaned_flight_list_sorted[3]["flight_iata"], font = 'tom-thumb'),
        			]
        		)
        	]
    	)
    )

Prototype Web Output

Tidbyt Preview - LAX Arrivals

Results

Deploying to Device

Pre-Render the Image to be Displayed

The Tidbyt’s whole custom app paradigm relies on the device itself actually staying completely “dumb” - all it actually does is show webp images that are sent to it!

So to show custom information, what we need to do is run our custom app code, render the output into a webp image, and then beam that image to the Tidbyt to display.

pixlet render scheduled_arrivals.star

Push the Rendered Image to the Tidbyt

For this step, you’ll need to go into the Tidbyt app on your phone (Settings > General > Get API Key) and make note of your Device ID and Key. Then run the code below to push the webp image you just built directly to your Tidbyt! It’ll stay on the display for a full “rotation”, before your regular apps rotate through again.

pixlet push "DEVICE_ID" scheduled_arrivals.webp --api-token="API_KEY"

Output

LAX Arrivals App Prototype Running on Tidbyt

The Next Steps

  • Encrypting API secrets: right now, I have my AirLabs API key in plaintext within my script - Tidbyt actually offers a secrets library that I’d like to leverage to manage the encryption and decryption of keys
  • Hardening the code: while my app is totally functional, it doesn’t have exception handling in case anything goes unexpectedly wrong - like the AirLabs API being unreachable, flight data being scrambled, or LAX not having any flights. While each of these is unlikely, it makes sense to quickly account for these potential cases
  • Publishing app to the Tidbyt app gallery: with the two enhancements above implemented, I think I’ll be ready to create an “official” Tidbyt app to submit to their app store!