Stop missing the bus!

Show the timetable on an OLED display!

I put together a Raspberry Pi and a tiny OLED display to show a bus timetable, showing the departure time of the next bus at my local stop; All built from a ‘Beautiful Soup’ web scrape and not using an API.

OLED Bus Timetable Overview

Not exactly a massive project but a fun introduction for me to the Pimoroni 1.2″ Mono OLED .

Inspired by this post , I thought I’d put that little OLED to some use and see if I could:

  • Scrape a website to determine when the next bus to town is departing
  • Then write this info to the OLED
  • Then keep on refreshing

The Scripts

22_next.py

Shows the amount of time before the next departure from this bus stop.

#!/usr/bin/env python3

# General
import os
import time

# For the scraping
from bs4 import BeautifulSoup
import requests

# For the oled
from PIL import Image
from PIL import ImageFont
from PIL import ImageDraw
from threading import Thread
from luma.core.interface.serial import i2c
from luma.core.render import canvas
from luma.oled.device import sh1106

# Set up OLED
oled = sh1106(i2c(port=1, address=0x3C), rotate=2, height=128, width=128)

# Load fonts
rr_path = os.path.abspath(os.path.join(os.path.dirname(__file__), 'fonts', 'Roboto_Regular.ttf'))
rb_path = os.path.abspath(os.path.join(os.path.dirname(__file__), 'fonts', 'Roboto_Black.ttf'))
rr_12 = ImageFont.truetype(rr_path, 12)
rr_24 = ImageFont.truetype(rr_path, 24)

# The main loop that writes to the OLED
while True:
	# Page url to scrape
	url ='https://www.buses.co.uk/stops/149000006645'

	# Fetch the content from url
	page = requests.get(url, timeout=5)

	# Parse html
	soup = BeautifulSoup(page.content, "html.parser")

	# Extract the html element where the next time expected is stored
	time_expected = soup.find(class_='single-visit__time--expected')

	# Strip extraneous html
	next_bus = time_expected.text.strip()

	# Start to draw to the display
	background = Image.open("/home/foo/Dev/Raspi/busses/images/bus.png").convert(oled.mode)
	draw = ImageDraw.ImageDraw(background)

	# Draw the top line
	draw.rectangle([(0, 0), (128, 20)], fill="black")
	draw.line([(0, 20), (128, 20)], fill="white")

	# Draw the text
	draw.rectangle([(0, 108), (128, 128)], fill="black")
	draw.text((10, 40), "The next bus is in", fill="white", font=rr_12)
	draw.text((20, 60), next_bus, fill="white", font=rr_24)

	# Draw the bottom line
	draw.rectangle([(0, 108), (128, 128)], fill="black")
	draw.line([(0, 108), (128, 108)], fill="white")

	# Display on the OLED
	oled.display(background)
	time.sleep(0.05)

22_deps.py

Shows the upcoming departures from this bus stop.

#!/usr/bin/env python3

# General
import os
import time

# For the scraping
from bs4 import BeautifulSoup
import requests

# For the oled
from PIL import Image
from PIL import ImageFont
from PIL import ImageDraw
from threading import Thread
from luma.core.interface.serial import i2c
from luma.core.render import canvas
from luma.oled.device import sh1106

# Set up OLED
oled = sh1106(i2c(port=1, address=0x3C), rotate=2, height=128, width=128)

# Load fonts
rr_path = os.path.abspath(os.path.join(os.path.dirname(__file__), 'fonts', 'Roboto_Regular.ttf'))
rb_path = os.path.abspath(os.path.join(os.path.dirname(__file__), 'fonts', 'Roboto_Black.ttf'))
rr_12 = ImageFont.truetype(rr_path, 12)
rr_24 = ImageFont.truetype(rr_path, 24)

# The main loop that writes to the OLED
while True:
    # Page url to scrape
    url ='https://www.buses.co.uk/stops/149000006645'

    # Fetch the content from url
    page = requests.get(url, timeout=5)

    # Parse html
    soup = BeautifulSoup(page.content, "html.parser")

    # Extract the html element where the next times expected are stored
    for bus_times in soup.findAll('div', attrs={"class":"single-visit__time single-visit__time--expected"}):
            bus_deps = bus_times.text.strip()

    # Start to draw to the display
    background = Image.open("/home/foo/Dev/Raspi/busses/images/bus.png").convert(oled.mode)
    draw = ImageDraw.ImageDraw(background)

    # Draw the top line
    draw.rectangle([(0, 0), (128, 20)], fill="black")
    draw.line([(0, 20), (128, 20)], fill="white")

    # Draw the text
    draw.rectangle([(0, 108), (128, 128)], fill="black")
    draw.text((10, 40), "The next bus is in", fill="white", font=rr_12)
    draw.text((20, 60), bus_deps, fill="white", font=rr_24)

    # Draw the bottom line
    draw.rectangle([(0, 108), (128, 128)], fill="black")
    draw.line([(0, 108), (128, 108)], fill="white")

    # Display on the OLED
    oled.display(background)
    time.sleep(0.05)

    continue

Well, it works, so now I don’t have to keep looking at the bus app on my phone in a morning…

A tiny OLED display showing how long before the next bus is due to arrive. OLED Display Showing the Next Bus Departure

Hope this helps someone looking for a simple way to write basic text to the display.

(It doesn’t have to be a bus timetable that can be written to the OLED display, maybe tide tables or how long until the bin men arrive?)

All of the Mono OLED code examples can also be found on Forgejo .