ebb/carousel.py

545 lines
18 KiB
Python
Executable File

#!/usr/bin/python
# -*- coding:utf-8 -*-
# This is the main program of the EBB: E-Paper Bulletin Board. More info at https://git.laboratoryb.org/trav/ebb
#
# carousel iterates through all posts in posts.json
import os
import epd4in2
import traceback
import time
import signal
import uuid
import subprocess
import buttonshim
import addtoDB
import refreshdb
import configparser
import pyqrcode
import qrcode
from tinydb import TinyDB, Query
from PIL import Image,ImageDraw,ImageFont
## INITIALIZE ##
#Let the user know we're booting: with a PURPLE led
buttonshim.set_pixel(0xFF, 0x00, 0xFF)
#intervalTime is how many seconds before moving to next image (300 = 5 minutes)
#sync time is how many seconds before refreshing the DB from SSB
#this should be moved into the config file...
intervalTime = 1800
syncTime = 3600
#keep track of where we are moving through intervalTime, syncTime and where we are in the db, and what flier we're currently displaying
timeIndex = 1200
syncIndex = 0
dbIndex = 1
flierPath = "noneyet"
button_flag = "null"
#pull some vals from the config
configParser = configparser.RawConfigParser()
configFilePath = r'config.txt'
configParser.read(configFilePath)
imagesPath = configParser.get('ebb-config', 'imagesPath')
dbPath = configParser.get('ebb-config', 'dbPath')
#initialize db and our place going through the db. We want dbCount instead of len(db) because len(db) doesn't account for the vacant holes in the db that I can't figure out how to get rid of...
db = TinyDB(dbPath)
howmany = Query()
dbCount = 0
for item in db:
dbCount = item.doc_id
print(dbCount)
#initialize display and display boot image
#grab the entry at dbIndex, newFlier is a dict
newFlier = db.get(doc_id=dbIndex)
flierPath = "/home/pi/images/" + newFlier["path"]
Limage = Image.new('1', (epd4in2.EPD_HEIGHT, epd4in2.EPD_WIDTH), 255) # 255: clear the frame
try:
font18 = ImageFont.truetype('/usr/share/fonts/truetype/wqy/wqy-microhei.ttc', 36)
epd = epd4in2.EPD()
epd.init()
bmp = Image.open(str(flierPath))
Limage.paste(bmp)
draw = ImageDraw.Draw(Limage)
# draw.text((2, 280), 'EBB', font = font18, fill = 255)
draw.text((2, 300), 'Scuttlebooth 2', font = font18, fill = 255)
epd.display(epd.getbuffer(Limage))
time.sleep(2)
epd.sleep()
except:
print('traceback.format_exc():\n%s' % traceback.format_exc())
exit()
## button press contingencies ##
@buttonshim.on_press(buttonshim.BUTTON_A)
def button_a(button, pressed):
global button_flag
buttonshim.set_pixel(0xFF, 0x00, 0x00)
button_flag = "button_1"
@buttonshim.on_press(buttonshim.BUTTON_B)
def button_b(button, pressed):
global button_flag
buttonshim.set_pixel(0x00, 0x00, 0xFF)
button_flag = "button_2"
@buttonshim.on_press(buttonshim.BUTTON_C)
def button_c(button, pressed):
global button_flag
buttonshim.set_pixel(0x00, 0x00, 0xFF)
button_flag = "button_3"
@buttonshim.on_press(buttonshim.BUTTON_D)
def button_d(button, pressed):
global button_flag
buttonshim.set_pixel(0xFF, 0x00, 0x00)
button_flag = "button_4"
@buttonshim.on_press(buttonshim.BUTTON_E)
def button_e(button, pressed):
global button_flag
buttonshim.set_pixel(0x00, 0x00, 0xFF)
button_flag = "button_5"
buttonshim.set_pixel(0x00, 0x00, 0x00)
buttonshim.set_pixel(0x00, 0x00, 0x00)
# img concat from https://note.nkmk.me/en/python-pillow-concat-images/
def get_concat_v(im1, im2):
dst = Image.new('RGB', (im1.width, im1.height + im2.height))
dst.paste(im1, (0, 0))
dst.paste(im2, (0, im1.height))
return dst
def get_concat_h(im1, im2):
dst = Image.new('RGB', (im1.width + im2.width, im1.height))
dst.paste(im1, (0, 0))
dst.paste(im2, (im1.width, 0))
return dst
## MAIN LOOP ##
while True:
# chill for a bit, keep track of how long we're chilling
time.sleep(2)
print("time:", timeIndex, "/", intervalTime, "index", dbIndex, "/", dbCount, "sync time:", syncIndex, "/", syncTime)
#iterate through syncTime and image time
syncIndex+=2
timeIndex+=2
#time to sync db with ssb
if syncIndex >= syncTime:
#light up red while syncing db
buttonshim.set_pixel(0xFF, 0x00, 0x00)
refreshdb.fresh()
#db count may have changed
for item in db:
dbCount = item.doc_id
syncIndex = 0
buttonshim.set_pixel(0x00, 0x00, 0x00)
#time to iterate through images
if timeIndex >= intervalTime:
#reset the index
timeIndex = 0
# iterate where we are in the db
dbIndex+=1
# if dbIndex is now out of range, reset to beginning (skipping 1 because that's the boot image)
if dbIndex > dbCount:
dbIndex=2
#grab the entry at dbIndex, newFlier is a dict
print ("getting db at")
print (dbIndex)
newFlier = db.get(doc_id=dbIndex)
#if that's an empty spot in the db we gotta keep lookin
while newFlier == None:
dbIndex+=1
newFlier = db.get(doc_id=dbIndex)
flierPath = newFlier["path"]
#display the image
try:
epd.init()
bmp = Image.open(flierPath)
Limage.paste(bmp)
epd.display(epd.getbuffer(Limage))
time.sleep(2)
timeIndex+=2
epd.sleep()
except:
print('traceback.format_exc():\n%s' % traceback.format_exc())
exit()
#in case the LED was on because we're buttoning through images
buttonshim.set_pixel(0x00, 0x00, 0x00)
#take pic
if button_flag == "button_1":
#generate 3 unique file_names
unique1 = uuid.uuid4()
unique1 = str(unique1)
jpgpath1 = imagesPath + unique1 + '.jpg'
unique2 = uuid.uuid4()
unique2 = str(unique2)
jpgpath2 = imagesPath + unique2 + '.jpg'
unique3 = uuid.uuid4()
unique3 = str(unique3)
jpgpath3 = imagesPath + unique3 + '.jpg'
#generate unique bmp names
bmpath1 = imagesPath + unique1 + '.bmp'
bmpath2 = imagesPath + unique2 + '.bmp'
bmpath3 = imagesPath + unique3 + '.bmp'
#loop in case we wanna re-take it
exitPhotoMode = False
while exitPhotoMode == False:
#take fullsize photos
try:
result = subprocess.call(['raspistill', '-rot', '180', '-o', jpgpath1, '-vf', '-hf', '-w', '600', '-h', '800', '-t', '1000'])
except:
print('traceback.format_exc():\n%s' % traceback.format_exc())
exit()
try:
result = subprocess.call(['raspistill', '-rot', '180', '-o', jpgpath2, '-vf', '-hf', '-w', '600', '-h', '800', '-t', '1000'])
except:
print('traceback.format_exc():\n%s' % traceback.format_exc())
exit()
try:
result = subprocess.call(['raspistill', '-rot', '180', '-o', jpgpath3, '-vf', '-hf', '-w', '600', '-h', '800', '-t', '1000'])
except:
print('traceback.format_exc():\n%s' % traceback.format_exc())
exit()
#create half-size bmps for cycling through
half1 = Image.open(jpgpath1)
half2 = Image.open(jpgpath2)
half3 = Image.open(jpgpath3)
newsize = (300, 400)
half1 = half1.resize(newsize)
half2 = half1.resize(newsize)
half3 = half1.resize(newsize)
half1 = half1.convert("1")
half1.save(bmpath1)
half2 = im2.convert("1")
half2.save(bmpath2)
half3 = im3.convert("1")
half3.save(bmpath3)
## CREATE screen image to display:
# create quarter sized bmps
im1 = Image.open(jpgpath1)
im2 = Image.open(jpgpath2)
im3 = Image.open(jpgpath3)
newsize = (150, 200)
im1 = im1.resize(newsize)
im2 = im2.resize(newsize)
im3 = im3.resize(newsize)
#add quarter sized photos and instructions to the image to be rendered
instructions = imagesPath + 'instructions.bmp'
im4 = Image.open(instructions)
Limage.paste(im1, (0,0))
Limage.paste(im2, (150,0))
Limage.paste(im3, (0,200))
Limage.paste(im4, (150,200))
#add the menu
# draw = ImageDraw.Draw(Limage)
# font18 = ImageFont.truetype('/usr/share/fonts/truetype/wqy/wqy-microhei.ttc', 10)
# draw.text((152, 200), 'press button 1 to:', font = font18, fill = 255)
# draw.text((152, 220), 'post online', font = font18, fill = 255)
# draw.text((152, 240), 'button 2: post photo', font = font18, fill = 255)
# draw.text((152, 260), 'here only', font = font18, fill = 255)
# draw.text((152, 280), 'button 3: retake', font = font18, fill = 255)
# draw.text((152, 00), 'buttons 4 or 5:', font = font18, fill = 255)
# draw.text((152, 640), 'exit photo mode', font = font18, fill = 255)
# display the image
epd.init()
epd.display(epd.getbuffer(Limage))
#pulse LED while we wait for user to press something
button_flag = "none"
while button_flag == "none":
buttonshim.set_pixel(0xFF, 0x00, 0x00)
time.sleep(.2)
buttonshim.set_pixel(0xFF, 0xFF, 0x00)
time.sleep(.2)
buttonshim.set_pixel(0x00, 0xFF, 0x00)
time.sleep(.2)
buttonshim.set_pixel(0xFF, 0xFF, 0x00)
time.sleep(.2)
#led to thinking mode
buttonshim.set_pixel(0x00, 0x00, 0xFF)
# if we're printing, generate image to print
if button_flag == "button_1" or button_flag == "button_2" or button_flag == "button_3":
#generate unique file_name
unique = uuid.uuid4()
unique = str(unique)
composite = imagesPath + unique + '.jpg'
# concatenate images and save concatenated image at composite (is filepath)
header = Image.open(composite)
im1 = Image.open(jpgpath1)
im2 = Image.open(jpgpath2)
im3 = Image.open(jpgpath3)
get_concat_v(header, im1).save(composite)
concaz = Image.open(conposite)
get_concat_v(concaz, im2).save(composite)
concaz = Image.open(conposite)
get_concat_v(concaz, im3).save(composite)
concaz = Image.open(conposite)
#post to ssb and print
if button_flag == "button_1":
button_flag == "null"
#add the photo to the db/ssb
key = addtoDB.addFile(bmpath,dbPath,0,composite)
# Create qr code
#from https://ourcodeworld.com/articles/read/554/how-to-create-a-qr-code-image-or-svg-in-python
qr = qrcode.QRCode(
version = 1,
error_correction = qrcode.constants.ERROR_CORRECT_H,
box_size = 4,
border = 1,
)
# Add data
qr.add_data(key)
qr.make(fit=True)
# Create an image from the QR Code instance
img = qr.make_image()
# concatenate QR instructions with QR
whereQRinstructionsAre = imagesPath + 'ssbqrinstgurct.bmp'
qrInstruct = Image.open(whereQRinstructionsAre)
# concatenate QR+instructions to full composite for print
#generate unique file_name
unique = uuid.uuid4()
unique = str(unique)
FULLcomposite = imagesPath + unique + '.jpg'
get_concat_v(concaz, (get_concat_h(qrInstruct, img))).save(FULLcomposite)
# bash lp of Fullcomposite
#update dbCount
for item in db:
dbCount = item.doc_id
exitPhotoMode = True
#display the image to clear the menu (also display text "posted")
font18 = ImageFont.truetype('/usr/share/fonts/truetype/wqy/wqy-microhei.ttc', 24)
bmp = Image.open(bmpath)
Limage.paste(bmp)
draw = ImageDraw.Draw(Limage)
draw.text((2, 240), 'PHOTO POSTED', font = font18, fill = 255)
draw.text((2, 260), 'online!', font = font18, fill = 255)
epd.display(epd.getbuffer(Limage))
#post only locally and print
if button_flag == "button_2":
button_flag == "null"
#add that file to the db. -1 tells addtoDB not to post to ssb.
addtoDB.addFile(bmpath,dbPath,-1)
#update dbCount
for item in db:
dbCount = item.doc_id
exitPhotoMode = True
#display the image to clear the menu (also display text "posted")
font18 = ImageFont.truetype('/usr/share/fonts/truetype/wqy/wqy-microhei.ttc', 24)
bmp = Image.open(bmpath)
Limage.paste(bmp)
draw = ImageDraw.Draw(Limage)
draw.text((2, 240), 'PHOTO POSTED', font = font18, fill = 255)
draw.text((2, 260), 'to this device only', font = font18, fill = 255)
epd.display(epd.getbuffer(Limage))
#just print
if button_flag == "button_3":
button_flag == "null"
#nothin! loop around... exitPhotoMode still false
try:
result = subprocess.call(['raspistill', '-rot', '180', '-o', jpgpath3, '-vf', '-hf', '-w', '600', '-h', '800', '-t', '1000'])
except:
print('traceback.format_exc():\n%s' % traceback.format_exc())
exit()
#reshoot
if (button_flag == "button_4"):
button_flag == "null"
exitPhotoMode = True
os.remove(bmpath)
os.remove(jpgpath)
#exit photo mode
if (button_flag == "button_5"):
button_flag == "null"
exitPhotoMode = True
os.remove(bmpath)
os.remove(jpgpath)
#led off
buttonshim.set_pixel(0x00, 0x00, 0x00)
time.sleep(3)
timeIndex+=2
epd.sleep()
button_flag = "null"
#move to the next image
elif button_flag == "button_2":
print ("next image")
timeIndex = intervalTime
button_flag = "null"
#go back to the previous image
elif button_flag == "button_3":
print ("previous image")
#we go back 2 because we have to account for calling timeIndex = intervalTime iterates forward by 1
dbIndex-=2
if dbIndex < 2:
dbIndex = dbCount-1
timeIndex = intervalTime
button_flag = "null"
#delete current image
elif button_flag == "button_4":
button_flag = "null"
#can't delete the default image
if dbIndex != 1:
#CONFIRM WITH USER THEY WANT TO DELETE
epd.init()
#Limage = Image.new('1', (epd4in2.EPD_HEIGHT, epd4in2.EPD_WIDTH), 255) # 255: clear the frame
draw = ImageDraw.Draw(Limage)
font18 = ImageFont.truetype('/usr/share/fonts/truetype/wqy/wqy-microhei.ttc', 24)
draw.text((2, 0), 'Are you sure', font = font18, fill = 255)
draw.text((2, 40), 'you want to delete', font = font18, fill = 255)
draw.text((2, 80), 'the current image?', font = font18, fill = 255)
draw.text((2, 120), 'Press button 4', font = font18, fill = 255)
draw.text((2, 160), 'again to confirm.', font = font18, fill = 255)
draw.text((2, 200), 'Press any other', font = font18, fill = 255)
draw.text((2, 240), 'button for no.', font = font18, fill = 255)
epd.display(epd.getbuffer(Limage))
epd.sleep()
while button_flag == "null":
buttonshim.set_pixel(0xFF, 0x00, 0x00)
time.sleep(.1)
buttonshim.set_pixel(0x00, 0x00, 0x00)
time.sleep(.05)
#sounds like delete time
if button_flag == "button_4":
Fruit = Query()
#results = db.search(Fruit.path == flierPath)
db.remove(Fruit.path == flierPath)
#update dbCount
for item in db:
dbCount = item.doc_id
#ok so it's deleted, let's force display of next image
timeIndex = intervalTime
else:
#stay on same image and refresh to get rid of delete text
dbIndex-=1
timeIndex = intervalTime
button_flag = "null"
buttonshim.set_pixel(0x00, 0x00, 0xFF)
#weather button!
elif button_flag == "button_5":
#ACTUALLY PARSE THE JSON DUH, THIS IS BROKEN
#get the weather
#proc = subprocess.Popen(["wget", "http://wttr.in/btv_FnQT.png"], stdout=subprocess.PIPE)
proc = subprocess.Popen(["wget", "http://wttr.in/btv_FQT.png"], stdout=subprocess.PIPE)
(out, err) = proc.communicate()
#convert
size = 400, 300
#too pixely
#im = Image.open("btv_FnQT.png")
#im = im.rotate(90, Image.NEAREST, "expand=1")
#sideways, no rotate
im = Image.open("btv_FQT.png")
im.thumbnail(size, Image.BICUBIC)
im = im.convert("1")
im.save("btv.bmp")
#display the weather
epd.init()
Limage = Image.new('1', (epd4in2.EPD_HEIGHT, epd4in2.EPD_WIDTH), 255) # 255: clear the frame
bmp = Image.open("btv.bmp")
Limage.paste(bmp)
epd.display(epd.getbuffer(Limage))
time.sleep(3)
timeIndex+=2
epd.sleep()
button_flag = "null"
buttonshim.set_pixel(0x00, 0x00, 0x00)