139 lines
4.6 KiB
Python
139 lines
4.6 KiB
Python
# NextBus scrolling marquee display for Adafruit CharliePlex matrices.
|
|
# Requires IS31FL3731 library: github.com/adafruit/Adafruit_Python_IS31FL3731
|
|
# Also PIL or Pillow library for graphics.
|
|
|
|
import atexit
|
|
import math
|
|
import os
|
|
import time
|
|
from predict import predict
|
|
from Adafruit_IS31FL3731 import *
|
|
from PIL import Image
|
|
from PIL import ImageDraw
|
|
from PIL import ImageFont
|
|
|
|
|
|
# Configurable stuff ---------------------------------------------------------
|
|
|
|
i2c_address = 0x74 # IS31FL3731 display address
|
|
|
|
# Use this declaration for an IS31FL3731 16x9 breakout board:
|
|
#disp = CharlieBreakout(i2c_address)
|
|
|
|
# Or this for a 16x8 Pi bonnet:
|
|
disp = CharlieBonnet(i2c_address)
|
|
|
|
# Or this one for a 15x7 FeatherWing:
|
|
#disp = CharlieWing(i2c_address)
|
|
|
|
# List of bus lines/stops to predict. Use routefinder.py to look up
|
|
# lines/stops for your location, copy & paste results here. The 4th
|
|
# string on each line can then be edited for brevity if desired.
|
|
# Probably want to keep this short due to tiny display.
|
|
stops = [
|
|
( 'actransit', '210', '0702640', 'Ohlone College' ),
|
|
( 'actransit', '210', '0702630', 'Union Landing' ) ]
|
|
|
|
# Small bitmap version of Helvetica Regular from X11R6 distribution:
|
|
font = ImageFont.load(os.path.dirname(os.path.realpath(__file__)) +
|
|
'/helvR08.pil') # realpath stuff allows run from rc.local
|
|
y = -2 # Vertical position; moved up so descenders aren't cropped
|
|
|
|
maxPredictions = 3 # NextBus shows up to 5; limit to 3 for simpler display
|
|
minTime = 5 # Drop predictions below this threshold (minutes)
|
|
routeColor = 128 # Brightness for route numbers/letters
|
|
descColor = 16 # " for route direction/description
|
|
timeColor = 128 # " for prediction times
|
|
minsColor = 16 # Commas and 'minutes' labels
|
|
noTimesColor = 16 # "No predictions" color
|
|
|
|
image = Image.new('L', (disp.width, disp.height))
|
|
draw = ImageDraw.Draw(image)
|
|
fps = disp.width # Scroll width of screen in 1 second
|
|
|
|
# Draws text at position (x,y), returns x following cursor advance
|
|
def advanceX(x, y, label, color):
|
|
draw.text((x, y), label, font=font, fill=color)
|
|
return x + font.getsize(label)[0]
|
|
|
|
# Clear matrix on exit. Otherwise it's annoying if you need to break and
|
|
# fiddle with some code while LEDs are blinding you.
|
|
def clearOnExit():
|
|
disp.selectFrame(0)
|
|
disp.clear()
|
|
disp.update()
|
|
disp.showFrame(0)
|
|
|
|
|
|
# Main application -----------------------------------------------------------
|
|
|
|
currentTime = 0.0
|
|
prevTime = 0.0
|
|
backBuffer = 1
|
|
xx = 0 # Cursor X position for horizontal scroll
|
|
|
|
|
|
atexit.register(clearOnExit)
|
|
|
|
# Populate a list of predict objects (from predict.py) from stops[]
|
|
predictList = [] # Clear list
|
|
for s in stops: # For each item in stops[] list...
|
|
predictList.append(predict(s)) # Create object, add to predictList[]
|
|
|
|
while True: # Init done; loop forever...
|
|
|
|
# Clear background
|
|
draw.rectangle((0, 0, disp.width, disp.height), fill=0)
|
|
|
|
x = xx # Start at cursor X
|
|
while x < disp.width: # Repeat until X off right edge
|
|
for p in predictList: # For each bus line...
|
|
x = advanceX(x, y, p.data[1] + ' ', routeColor)
|
|
x = advanceX(x, y, p.data[3] + ' ', descColor)
|
|
if p.predictions == []:
|
|
x = advanceX(x, y,
|
|
'No Predictions', noTimesColor)
|
|
else:
|
|
isFirstShown = True
|
|
count = 0
|
|
for p2 in p.predictions:
|
|
t = p2 - (currentTime -
|
|
p.lastQueryTime)
|
|
m = int(t / 60)
|
|
if m <= minTime: continue
|
|
if isFirstShown:
|
|
isFirstShown = False
|
|
else:
|
|
x = advanceX(x, y - 2,
|
|
', ', minsColor)
|
|
x = advanceX(x, y, str(m), timeColor)
|
|
count += 1
|
|
if count >= maxPredictions: break
|
|
if count > 0:
|
|
x = advanceX(x, y, ' min', minsColor)
|
|
x = advanceX(x, 0, ' ', 0) # 2 spaces b/t buses
|
|
# If x is off left edge after all lines have been
|
|
# printed, reset cursor x to that position (outer loop
|
|
# repeats so bus lines are still printed)
|
|
if x < 0: xx = x
|
|
|
|
# Select back buffer and push PIL image there:
|
|
disp.selectFrame(backBuffer)
|
|
disp.image(image)
|
|
disp.update()
|
|
|
|
# Try to keep timing uniform-ish; rather than sleeping a fixed time,
|
|
# interval since last frame is calculated, the gap time between this
|
|
# and desired frames/sec determines sleep time...occasionally if busy
|
|
# (e.g. polling server) there'll be no sleep at all.
|
|
currentTime = time.time()
|
|
timeDelta = (1.0 / fps) - (currentTime - prevTime)
|
|
if(timeDelta > 0.0):
|
|
time.sleep(timeDelta)
|
|
prevTime = currentTime
|
|
|
|
# Display the newly-pushed image, then invert the front/back index:
|
|
disp.showFrame(backBuffer)
|
|
backBuffer = 1 - backBuffer
|
|
|
|
xx -= 1 # Move cursor start left by 1 pixel
|