Using Scriptable on iOS 14 to Show Real-Time SL Departures in a Widget

I finally got around to playing with the Scriptable app, something that had been on my list since I heard about the app’s newest ability — letting you turn custom Javascript into iOS 14 widgets.

The obvious place for me to start was to try and replicate the departure boards you see at Stockholm’s bus stops, since I had (very recently) also tried to write a macOS screensaver that would do just that, before life got in the way. Continuing that abandoned project in what seemed — and, as my experiments showed, definitely is — a “friendlier” environment seemed like the logical thing to do.

Getting started with Scriptable was pretty simple, thanks largely to the sample scripts within the app, specifically the View JSON and News in Widget scripts which do exactly what their names suggest. Taking the relevant bits from the two scripts and pointing them to SL’s awesome real-time API was as easy as I thought developing the screensaver would be.

I’ve released the script as a Gist (and embedded it below), and here are some thoughts on Scriptable and writing widgets on iOS 14, as well as the script itself:

  1. This is very much a quick-and-dirty script put together in a couple of hours, and is specific to my use case as someone who lives close to a bus stop (and no train line nearby) but hates waiting there. The idea is to monitor upcoming departures at a specific bus stop in one direction so I can time my own departure from the house to minimise waiting. It should be trivial to customise the script for a more generic requirement.
  2. I use a pure black background as my iPhone wallpaper, hence the motivation to use a pure black background colour for the widget. With that said, though the screenshot shows the widget being used on the home screen, I don’t foresee continuing to use it like this on a daily basis largely to the fact that there’s no way (that I’ve come across) to force the widget to update periodically (every minute would be ideal). Which is why I was forced to add the “Updated:” bit in the footer, so I know how dated the information that I’m looking at is, and to manually run the script again (which shows the updated widget as a popup), if necessary.
The widget in action on iPhone and iPad
  1. Scriptable offers plenty of options that let you customise the look and feel of the widget but I didn’t bother experimenting with most of those. Ideally, I would love for the widget to look exactly like the boards you see at bus stops — the “stacks” added in the current TestFlight build should make it easier to do stuff like that — giving this very much “functional” widget some much-needed “whimsy”.
  2. As you can see I went the good-old fashioned “tabs and fixed-width fonts” route for formatting. For some reason using “regularMonospacedSystemFont” wouldn’t return a fixed-width font (as I understood it should), so I ended up hardcoding the font name itself, though Menlo wouldn’t have been my first pick otherwise. If you know why the regularMonospacedSystemFont approach didn’t work, please let me know via Twitter or the comments.
  3. I did all the development on the iPad (Scriptable of course is available for both iPhone and iPad) and while the development process itself was pretty straightforward, figuring out how to add a Scriptable widget to the ‘Today View’ when I was done took a lot more time than I’m willing to publicly admit. I kept looking for Scriptable under (what I now know is the old) list of widgets you see when you go to Edit > Customize from the bottom of the Today view. Maybe it’s just me, but that “+” sign to add iOS 14-only widgets is really easy to miss, especially when you are in landscape mode (which is how I use the iPad 99 percent of the time).

While it’s convenient to have this information on the iPhone and iPad, the need to build that screensaver still remains — the idea is to have the Macs in the house act as giant departure boards you can glance at while getting ready. I hope to return to development soon and add to my screensaver collection of one.

Bespin Gotchas

In case of trouble connecting to Bespin from an external machine, try setting the IP address to 0.0.0.0

Either change the file pavement.py:

server=Bunch(
   # set to true to allow connections from other machines
   address="0.0.0.0",
   port="8080",
   try_build=False,
   dburl=None,
   async=False,
   config_file=path("devconfig.py"),
   directory=path("../bespinserver/").abspath(),
   clientdir=path.getcwd()
),

Or pass it as a command line argument:

paver server.address=0.0.0.0 server.port=8080 start

In case you haven’t noticed, you can even specify the port to start on i.e. run Bespin on a custom port.

Get email updates when your IP changes/ Python DynDns update client

I use DynDns to map my ISP provided dynamic IP to a static hostname. For some reason, the DynDns provided update clients don’t always work for me and often leave my hostname pointing to a dead or (worse) someone else’s IP. I decided to take matter into my own hands and write a script that would email me my IP whenever my DHCP lease expired and my ISP issued me a fresh one. This would ensure I know how to reach back home, even if my hostname was pointing to an old IP.

While going through the DynDns API, I realized it was trivial to update the hostname as well, essentially replicating the functionality of the aforementioned client(s). So I decided to add that as well.

I know this functionality can be replicated via curl + sendmail, but Python is my tool of choice, so just live with it. Without further ado, here’s the script, with an explanation afterwards.

#!/usr/bin/env python
# encoding: utf-8
"""
Script to email IP whenever it changes. Also updates DynDns hostname.
Version 1.0

Created by Kunal Dua on 2010-05-10
Get email updates when your IP changes/ Python DynDns update client
This program is free software; you may redistribute it and/or modify it under the same terms as Python itself. """ def send_mail(subject, content): import smtplib from email.mime.text import MIMEText SERVER = "smtpserver" PORT = 587 #Use 25 if this doesn't work USER = "username" PASS = "password" FROM = "IPBot " TO = "user+folder@example.com" SUBJECT = subject TEXT = content message = MIMEText(TEXT) message['Subject'] = SUBJECT message['From'] = FROM message['To'] = TO server = smtplib.SMTP(SERVER, PORT) server.login (USER, PASS) server.sendmail(FROM, TO, message.as_string()) server.quit() def update_dyndns(theip): USERNAME = 'username' PASSWORD = 'password' HOSTNAME = 'example.dyndns.org' theurl = 'https://%s:%s@members.dyndns.org/nic/update?hostname=%s&myip=%s&wildcard=NOCHG&mx=NOCHG&backmx=NOCHG' % (USERNAME, PASSWORD, HOSTNAME, theip) import urllib conn = urllib.urlopen(theurl) #print conn.read() conn.close() if __name__ == '__main__': import urllib2, re conn = urllib2.urlopen('http://checkip.dyndns.com/') data = conn.read() conn.close() m = re.search('([0-9]*)(.)([0-9]*)(.)([0-9]*)(.)([0-9]*)', data) currip = m.group(0) lastfile = "lastip.txt" allfile = "history.txt" theipfile = open(lastfile,"r") lastip = theipfile.read() theipfile.close() if lastip == currip: #print "no change needed" exit() else: histfile = open(allfile, "a") import datetime thenow = datetime.datetime.now().ctime() histfile.write("%s %sn" % (thenow, currip)) histfile.close() theipfile = open(lastfile,"w") theipfile.write(currip) theipfile.close() send_mail(currip, '') update_dyndns(currip)
  • Lines 17-22 and 39-41 replace with your email and DynDns settings respectively.
  • Line 22 – My email provider supports redirecting mails to a folder by simply adding the name of the folder before @ sign. For example user+ipupdates@example.com will deliver mail in folder ipupdates of user@example.com. If your email provider supports this, it’s a useful trick to prevent these mails from cluttering up your inbox. If not, simply enter your regular email address.
  • Lines 60-61 initialize 2 files that I use. One is to store the current IP (or the last known IP) and the other is a history of all IP changes. The former is used to compare if the IP has changed since the script was last run and thus if an email needs to be sent + DynDns updated. The latter is not really needed for the script to function properly, and is used to maintain a log of all IP changes – because you can!
  • Line 78 – By default, the subject of the mail is the IP and the body/ text is blank. Feel free to obfuscate your IP if you feel paranoid about sending it in clear text or write sweet nothings to yourself in the body.
  • Note: Before you run this script for the first time, create an empty file called lastip.txt in the same directory as the script or the script will fail. I know I could write a trivial check for this, but I leave that as an exercise for the reader.

Recommended frequency of running this job via cron/ launchd is 10 minutes.

Update: (May 27) I am pretty sure the DynDns API is broken in some way because I can’t get it to update even with this script. The email part is working pretty good for me though!