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/radio_vienna_live.star
pixlet push --api-token=“[YOUR API TOKEN HERE]” /path/to/folder/radio_vienna_live.webp
wait_period=$(($wait_period+30))
if [ $wait_period -ge 50400 ];then
break
else
sleep 30
fi
done
This script code renders the .star file called “VIE_Monitor” every 30 seconds for 14 hours (10800 / 30+30 / 60 = 14). I named the script “radiowien_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: (Never mind the red label on the top)
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.radiowien.push</string>
<key>ProgramArguments</key>
<array>
<string>/bin/sh</string>
<string>/path/to/folder/radiowien_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>
<dict>
<key>Hour</key>
<integer>9</integer>
<key>Minute</key>
<integer>0</integer>
<key>Weekday</key>
<integer>6</integer>
</dict>
</array>
</dict>
</plist>
This LaunchAgent starts every weekday at 6:30am and starts the radiowien_render.sh script which then runs for 14 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. (Note: I have used some code tibits from other apps - apologies for not being able to give credit where credit is due - I forgot where I copied it from…)
I have added some comments in the code - not sure if at all helpful for anyone. Screenshot at the end of the post.
"""
Applet: ORF Radio Wien Live
Summary: Show What's Playing and Album art
Description: Show title, artist and album art from Austria's Radio Wien/Vienna.
Author: Joerg Windbichler
"""
load("encoding/base64.star", "base64")
load("http.star", "http")
load("render.star", "render")
load("schema.star", "schema")
load("time.star", "time")
def main(config):
timezone = config.get("timezone") or "Europe/Vienna"
jetzt = time.now().in_location(timezone)
now = time.now()
metric = time.now().unix * 1000
# Radio Wien live json - found via live player and web inspector - Sources - fetches
url = "https://audioapi.orf.at/wie/json/4.0/live"
response = http.get(url)
alldata = response.json()
# index number to find currently playing song
arraynumber = 0
# searching which "magazine" (broadcast) currently is running - only relevant when magazines are switching
for mag in alldata:
# if magazine has not ended yet, take first json item [mag_item=0]
if mag["end"] > metric:
mag_item = 0
#print(f"Magif: {mag_item}")
break
# stops the for loop and moves on (for some reason the loop goes on after the else: and overwrites mag_item with 0 again
else:
# if magazine has ended, take second json item [mag_item=1]
mag_item = 1
#print(f"Magelse: {mag_item}")
break
# stops the for loop and moves on (for some reason the loop goes on after the else: and overwrites mag_item with 0 again
# title of magazine/broadcast
sendung = response.json()[mag_item]['title']
# items within magazine to find what's currently playing
data = response.json()[mag_item]['items']
for count in data:
if count["end"] < metric:
arraynumber = arraynumber + 1
data2 = response.json()[mag_item]['items'][arraynumber]
# data2 is last song within items array
type = data2['type']
end_t = data2["endISO"]
end_time = end_t.split("T",1)[1][:8]
# type gives what is playing: M=music; j=jingle; N=news; ...
if type == "M":
current_song = data2["title"]
artist = data2["interpreter"]
image = data2['images'][0]['versions'][1]
image_url = image["path"]
img = http.get(image_url)
else:
current_song = data2['title']
artist = "Radio Wien"
image_url = "https://play-lh.googleusercontent.com/9sGSok5kd2h0ZOmppRhCAWJKh7B96AT_gyVDT7u3xOdrCNz3D9beI5k3kFiwuRc6G3U=w480-h960"
img = http.get(image_url)
albumWidget = render.Image(src = img.body(), height = 16, width = 16)
return renderIt(now, albumWidget, current_song, sendung, artist, end_time)
def renderIt(now, albumWidget, current_song, sendung, artist, end_time):
return render.Root(
child = render.Stack(
children = [
render.Padding(
pad = (16, 1, 0, 0),
child = render.Text(
content = now.format("15:04"),
font = "tom-thumb",
color = "#fff",
),
),
render.Padding(
pad = (37, 1, 0, 0),
child = render.Text(">>%s" % end_time.format("15:04"),
font = "tom-thumb",
color = "#777",
),
),
render.Box(
color = "#00FF0000",
child = render.Padding(
pad = (0, 3, 0, 0),
color = "#FF000000",
child = render.Column(
cross_align = "end",
main_align = "start",
expanded = False,
children = [
render.Box(
color = "#0000FF00",
height = 4,
),
render.Marquee(
width=48,
child=render.Text("%s" % sendung, color = "#DC6F2E"),
offset_start=0,
offset_end=0,
),
render.Box(
color = "#0000FF00",
height = 2,
),
render.Marquee(
width=64,
child=render.Text("%s" % current_song, color = "#FFF"),
),
render.Marquee(
width=64,
child=render.Text("%s" % artist, color = "#DF312F"),
offset_start=0,
offset_end=0,
),
],
),
),
),
render.Padding(
pad = (0, 0, 0, 0),
child = albumWidget,
),
],
),
)
Picture: Album cover
First line: Start time of song >> End time of song
Second line: Name of radio show
Third line: Song title
Fourth line: Artist