Adafruit-NextBus/nextbus-charlieplex.py
2017-02-23 15:53:32 -08:00

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