Let me tell you about a problem I have. It’s not really a big problem, but every time it happens I promise myself that I will never, ever let it happen again. But it did happen, again and again. So what actually is this problem I’m facing? I missed the bus by two seconds and now I have to wait 15 minutes for the next one. I’m coming out of my house and there it is, passing by right in front of me without stopping because I’m still a few seconds away from the stop.
So, how do I step out of the loop and start avoiding this? Yes of course, I could just navigate through the local transport app and see the live bus arrival times. But, being a techy, I had to create a solution for this. And that’s what this post is about! I created a bus clock 🚌 🕚 for my house, so that I can always see when to hurry up and get out to catch the bus.
What is a bus clock?
Well, just like a clock:
- It tells you a time; the time until the bus arrives. All the time.
- It hangs from a wall in your house – just like an old-school clock.
And this is what Dall-E, the AI image generator from OpenAI, thinks about a bus clock:
How I built it
Since I already had an unopened Raspberry Pi 3B that I ordered in 2016 (6 years ago!!) I decided it was now the perfect opportunity to start using it. I also needed a screen to display the times but I was a bit hesitant to buy it before the project even started, considering I bought the Raspberry Pi and never even touched it. But just like many times before, I told myself this time it would be different and I would take this project over the finish line so I went ahead and ordered a 4-inch, plug-and-play (mostly) HDMI display.
Fortunately, the Raspberry PI runs a full Unix system and you can program it with whatever you want, unlike other simpler chips out there. On top of that, I didn’t have the patience to read a data sheet, program I/O pins or learn a chip-specific language – so I went with Python 🐍. I almost had a sad moment when I couldn’t create a modern Python 3+ Conda environment on the Raspberry Pi 3B, but berryconda is maintaining a miniconda installer for Python 3.6, so at least I got f-strings :D.
Once the Python environment was up and running, I had to somehow fetch the live bus data from TFL (Transport for London) and for that I decided to just scrape the local transport website using the good old requests
package and BeautifulSoup4
. The full code is available at github.com/purplehoisincoder/…/bus_time.py but here’s a snippet
# London Transport URLs
TFL_URL = "https://tfl.gov.uk/"
BUS_LINE_STOPS_URL = "https://tfl.gov.uk/bus/route/{bus_number}/?direction={in_or_out}"
def find_link_for_bus_stop(bus_route_page, bus_stop_name):
""" Given the page for the bus route, find the link for the input bus stop """
soup = BeautifulSoup(bus_route_page, "html.parser")
destination = None
bus_stop_link = None
h1s = soup.select("h1")
for h1 in h1s:
text = h1.text
if text.startswith("Towards"):
destination = text.replace("Towards ", "")
links = soup.select("a.stop-link")
for link in links:
link_text = link.text
if bus_stop_name.lower() in link_text.lower():
bus_stop_link = link.get('href')
break
return bus_stop_link, destination
def find_arrival_times(bus_stop_page):
""" Given a bus stop page for one bus route, extract the bus arrival times
Example page: https://tfl.gov.uk/bus/stop/490000022C/bethnal-green-station?lineId=254
"""
soup = BeautifulSoup(bus_stop_page, "html.parser")
arrival_items = soup.select("li.live-board-feed-item")
times = []
for item in arrival_items:
eta = item.select_one("span.live-board-eta")
if eta:
times.append(eta.text)
return times
def get_arrival_times(args):
bus_route_url = BUS_LINE_STOPS_URL.format(bus_number=args.bus_number, in_or_out=args.inbound_or_outbound)
bus_route_page = fetch_page(bus_route_url)
bus_stop_relative_url, destination = find_link_for_bus_stop(bus_route_page, args.bus_stop_name)
if not bus_stop_relative_url:
print(f"Could not get bus_stop_link for {args}")
return
bus_stop_url = f"{TFL_URL}{bus_stop_relative_url}"
bus_stop_page = fetch_page(bus_stop_url)
arrival_times = find_arrival_times(bus_stop_page)
return arrival_times, destination
Good now we need to do that periodically, so that every time I walk past my bus clock, I know if I should hurry up or not. I went for a 30 second refresh rate and this is how it looked:
ordinal_numbers = ['1st', '2nd', '3rd', '4th', '5th', '6th', '7th', '8th', '9th']
while True:
os.system('clear')
arrival_times, destination = get_arrival_times(args)
time_now = datetime.now().strftime("%c")
print(f"Bus {args.bus_number} to {destination} at stop '{args.bus_stop_name.title()}'")
for index, arrival_time in enumerate(arrival_times):
if not arrival_time:
continue
print(f"{ordinal_numbers[index]} {arrival_time}")
print(f"{time_now}")
time.sleep(30)
And this is the output for a random bus station on line 254:
$ python bus_time.py --bus-number 254 --bus-stop-name "Bethnal Green Station"
Bus 254 to Aldgate at stop 'Bethnal Green Station'
1st Due
2nd 1 min
3rd 1 min
4th 1 min
5th 8 mins
6th 11 mins
7th 17 mins
8th 19 mins
9th 19 mins
Wed Nov 2 21:48:40 2022
Nice! it works! 🎉 From this point onwards, we know the magic is there and we just need to make it more usable. So let’s make the text a bit bigger so it can be seen on the tiny Raspberry screen on a glance as you walk by. I did still want it to be a simple terminal app, so I started testing ASCII art and colors:
Pretty nice, right? The bus clock is now live and my Raspberry Pi is less lonely.
I would love to hear your thoughts via hello@purplehoisin.com or let’s connect via twitter.com/purplehoisin.
Future Work
- Now that my partner is also using the bus clock, she already asked for more bus lines and more than one direction.
- It bugs me that I’m fetching this information every 30 seconds even though most of the time I won’t use it. Perhaps I could add a microphone that only fetches the information when it hears steps near by. I think fetching it after a voice command would defeat the purpose since it might just be faster to open the transport app. But perhaps it’s interesting to add this anyway to be able to get the information from somewhere else in the house.
I would like to include a voice-activated alarm. Like, Siri ( or whatever), let me know when bus 2 is close. For example, I see I have fifteen minutes, just enough time to take a quick shower and come down, but I forget and start singing Bohemian Rhapsody mid-way through my shower. Fortunately I told Siri (or whatever), before I jumped in the shower to let me know when the bus is less than 3 minutes away, so she shouts: get the hell out of the shower! You’re gonna miss the fucking bus! So I jump out of the shower, shampoo and all, just in time to run down naked to the bus stop and not miss my bus.
Sounds good to me.
Appreciation to my father who informed me regarding this blog, this blog is genuinely
awesome.
You know when to fetch the next estimate, based on what you last got. You can count down numbers on the display just based on time, and fetch samples more often only close to deadlines.
So that amounts to once each five minutes, most of the time.
Cool project!
If you have a routine you might fetch the info only during relevant times; or if you know how much it takes you to walk to the bus tou can determine when it would be a good time to re-sync, in the meantime just apply walk clock delta.
Cheers!
idea: an proximity sensor that triggers the api call when it detects someone standing in front of it
interesting post
not working