Self-serving app: Vienna Public Transportation departures

Hi,

as mentioned in this post (URL), I have set up my Tidbyt to serve some apps locally in my network with the help of my 24/7 server.

There are 2 more apps which can be found here (URL) and here (URL) for reference.

Let’s start with the main question: how can you self-serve apps to your Tidbyt.

First off, you have to install all the necessary tools that you need to develop apps - this can be found here: Tidbyt | Dev

Then you need a server that can run LaunchAgents via a crontab or similar (I’m using a Mac mini as the server and LaunchControl to write and run LaunchAgents on this server). I took some pointers from here (Pixlet install on linux/RaspberryPi). There are a few threads on the community discussion board that talk about setting up bash scripts and crontabs which I read and then came up with the following solution.

You need to write a bash script which serves the app on a regular basis:

#!/bin/bash
while true; do
	pixlet render /path/to/folder/VIE_Monitor.star
	pixlet push --api-token=“[ENTER YOUR API TOKEN HERE]” /path/to/folder/VIE_Monitor.webp
		wait_period=$(($wait_period+30))
		if [ $wait_period -ge 10800 ];then
			break
		else
			sleep 30
		fi
done

This script code renders the .star file called “VIE_Monitor” every 30 seconds for 3 hours (10800 / 30+30 / 60 = 3). I named the script “VIEMonitor_render.sh” and saved it in a specific folder (same as the .star and .webp files).

Now this script needs to be started at a certain time automatically by the server in order to show up on your Tidbyt. This can be done with a crontab or a LaunchAgent.

So I set up a LaunchAgent which looked like this:

If you prefer the XML code:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
	<key>Disabled</key>
	<false/>
	<key>EnvironmentVariables</key>
	<dict>
		<key>PATH</key>
		<string>/usr/local/bin:/usr/local/sbin:/usr/bin:/bin:/usr/sbin:/sbin</string>
	</dict>
	<key>KeepAlive</key>
	<false/>
	<key>Label</key>
	<string>com.localhost.VIEMontior.push</string>
	<key>ProgramArguments</key>
	<array>
		<string>/bin/sh</string>
		<string>/path/to/folder/VIEMonitor_render.sh</string>
	</array>
	<key>StandardErrorPath</key>
	<string>/tmp/local.job.err</string>
	<key>StandardOutPath</key>
	<string>/tmp/local.job.out</string>
	<key>StartCalendarInterval</key>
	<array>
		<dict>
			<key>Hour</key>
			<integer>6</integer>
			<key>Minute</key>
			<integer>30</integer>
			<key>Weekday</key>
			<integer>1</integer>
		</dict>
		<dict>
			<key>Hour</key>
			<integer>6</integer>
			<key>Minute</key>
			<integer>30</integer>
			<key>Weekday</key>
			<integer>2</integer>
		</dict>
		<dict>
			<key>Hour</key>
			<integer>6</integer>
			<key>Minute</key>
			<integer>30</integer>
			<key>Weekday</key>
			<integer>3</integer>
		</dict>
		<dict>
			<key>Hour</key>
			<integer>6</integer>
			<key>Minute</key>
			<integer>30</integer>
			<key>Weekday</key>
			<integer>4</integer>
		</dict>
		<dict>
			<key>Hour</key>
			<integer>6</integer>
			<key>Minute</key>
			<integer>30</integer>
			<key>Weekday</key>
			<integer>5</integer>
		</dict>
	</array>
</dict>
</plist>

This LaunchAgent starts every weekday at 6:30am and starts the VIEMonitor_render.sh script which then runs for 3 hours and stops automatically.

Of course, the PATH key might be different for you depending on your setup (my installations are all standard, so if you haven’t tinkered with the setup it should work).

There is one small quirk: as I’m rendering several apps they sometimes “conflict”, meaning that they override each other leading to only short times on the screen of the Tidbyt. I haven’t figured out how to solve that but it’s basically only a nuisance not a showstopper.

And now for the pixlet code. As mentioned, for this code I have used some code from Alex Miller’s Minnesota Light Rail app which can be found on Github (community/apps/mnlightrail at main · tidbyt/community · GitHub) to get started with the coding and help me with the rendering.

The code currently works for one specific stop (in the example that’s the stop number 266 within the Vienna public transportation grid). This code can be found via this tool: WL RBL/StopId Search (Note: if the station only has one platform, the app does not work - you would have to comment out all of the code for the line2 in the code below in that case)

Everything else is called from the servers automatically - see screenshot at the end of the post.

"""
Applet: Vienna Public Transportation
Summary: Bus/Train Departure Times
Description: Shows Tram/Bus Departure Times from Selected Stops in Vienna, Austria.
Author: Joerg Windbichler (using some code from Alex Miller's mn_light_rail.star community app)
"""

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

#Default RLB Code
RBLCode = "266"

def main(config):
    #timezone = config.get("timezone") or "Europe/Vienna"
    #now = time.now().in_location(timezone)
    stop_code = config.get("stop_code", RBLCode)
    StopIDURL = "https://www.wienerlinien.at/ogd_realtime/monitor?stopid=" + stop_code
    rep = http.get(StopIDURL)
    monitor = rep.json()['data']['monitors'][0]
    diva = monitor['locationStop']['properties']['name']
    DIVAURL = "https://www.wienerlinien.at/ogd_realtime/monitor?diva=" + diva
    rep2 = http.get(DIVAURL)
    monitor1 = rep2.json()['data']['monitors'][0]
    monitor2 = rep2.json()['data']['monitors'][1]
    servertime = rep2.json()['message']['serverTime']
    station = monitor1['locationStop']['properties']['title'][:13]
    line = monitor1['lines'][0]
    line2 = monitor2['lines'][0]
    rblline = line['name'] 
    rbldirection = line['towards']
    rbldirection2 = line2['towards']
    rbltime = line['departures']['departure'][0]['departureTime']['countdown']
    rbltime2 = line['departures']['departure'][1]['departureTime']['countdown']
    rbltime3 = line['departures']['departure'][2]['departureTime']['countdown']
    rbltim = line2['departures']['departure'][0]['departureTime']['countdown']
    rbltim2 = line2['departures']['departure'][1]['departureTime']['countdown']
    rbltim3 = line2['departures']['departure'][2]['departureTime']['countdown']
        
    return render.Root(
            child = render.Column(
            expanded = True,
            main_align = "space_around",
            children = [
                render.Row(
                    expanded = True,
                    cross_align = "center",
                    main_align = "space_around",
                    children = [
                        render.Text((servertime.split("T",1)[1][:8]),font = "CG-pixel-4x5-mono"),
                        render.Text(str(rblline), font = "CG-pixel-4x5-mono", color = "#FFFF00"),
                    ],
                ),
                render.Row(
                    expanded = True,
                    cross_align = "center",
                    main_align = "space_evenly",
                    children = [
                        render.Text(str(station), font = "CG-pixel-4x5-mono", color = "#FFFF00"),
                    ],
                ),
                render.Row(
                    expanded = True,
                    cross_align = "center",
                    main_align = "space_around",
                    children = [
                        render.Text(">> ", font = "CG-pixel-4x5-mono"),
                        render.Text(str(rbldirection), font = "CG-pixel-4x5-mono", color = "#FFFFFF"),
                    ],
                ),
                render.Row(
                    expanded = True,
                    cross_align = "center",
                    main_align = "space_around",
                    children = [
                        render.Text(str(int(rbltime)), font = "CG-pixel-4x5-mono", color = "#FF0000"),
                        render.Text(" ", font = "CG-pixel-4x5-mono"),
                        render.Text(str(int(rbltime2)), font = "CG-pixel-4x5-mono", color = "#FFA500"),
                        render.Text(" ", font = "CG-pixel-4x5-mono"),
                        render.Text(str(int(rbltime3)), font = "CG-pixel-4x5-mono", color = "#00FF00"),
                    ],
                        ),
                    render.Row(
                        expanded = True,
                        cross_align = "center",
                        main_align = "space_around",
                        children = [
                            render.Text(">> ", font = "CG-pixel-4x5-mono"),
                            render.Text(str(rbldirection2), font = "CG-pixel-4x5-mono", color = "#FFFFFF"),
                            ],
                        ),
                    render.Row(
                        expanded = True,
                        cross_align = "center",
                        main_align = "space_around",
                        children = [
                            render.Text(str(int(rbltim)), font = "CG-pixel-4x5-mono", color = "#FF0000"),
                            render.Text(" ", font = "CG-pixel-4x5-mono"),
                            render.Text(str(int(rbltim2)), font = "CG-pixel-4x5-mono", color = "#FFA500"),
                            render.Text(" ", font = "CG-pixel-4x5-mono"),
                            render.Text(str(int(rbltim3)), font = "CG-pixel-4x5-mono", color = "#00FF00"),
                            ],
                                ),
            ],
                ),
                )

First line: Server time when information was grabbed and name of bus/tram/subway line (“74a”)

Second line: name of the station
Third line: Platform 1 (name of last station of this direction)
Fourth line: next 3 departure times in minutes for the platform
Fifth line: Platform 2 (name of last station of this direction)
Sixth line: next 3 departure times in minutes for the platform