#!/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,ImageEnhance ## 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 = 200 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)) 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 def get_concat_h_blank(im1, im2, color=(0, 0, 0)): dst = Image.new('RGB', (im1.width + im2.width, max(im1.height, im2.height)), color) dst.paste(im1, (0, 0)) dst.paste(im2, (im1.width, 0)) return dst def pulse(speed): time.sleep(1) for scale in range(0, speed): if scale >= 1: scale = scale / speed buttonshim.set_pixel(0x00, 0x00, int(0xff * scale)) for scale in range(speed, 0,-1): if scale >= 1: scale = scale / speed buttonshim.set_pixel(0x00, 0x00, int(0xff * scale)) time.sleep(1) for scale in range(0, speed): if scale >= 1: scale = scale / speed buttonshim.set_pixel(0x00, int(0xff * scale), 0x00) for scale in range(speed, 0,-1): if scale >= 1: scale = scale / speed buttonshim.set_pixel(0x00, int(0xff * scale), 0x00) time.sleep(1) for scale in range(0, speed): buttonshim.set_pixel(0xFF, 0x00, 0x00) time.sleep(.02) buttonshim.set_pixel(0xFF, 0xFF, 0x00) time.sleep(.02) buttonshim.set_pixel(0x00, 0xFF, 0x00) time.sleep(.02) buttonshim.set_pixel(0xFF, 0xFF, 0x00) time.sleep(.02) time.sleep(1) ## MAIN LOOP ## while True: #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() print ("uhhhhhhh skipping ssb refresh because broken also solpunk one stuck in customs rn :'(") #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)) 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 pulse(20) try: result = subprocess.call(['raspistill', '-rot', '180', '-o', jpgpath1, '--brightness', '55', '--contrast', '1.25','-vf', '-hf', '-w', '600', '-h', '800', '-t', '20']) except: print('traceback.format_exc():\n%s' % traceback.format_exc()) exit() pulse(20) try: result = subprocess.call(['raspistill', '-rot', '180', '-o', jpgpath2, '--brightness', '55', '--contrast', '1.25','-vf', '-hf', '-w', '600', '-h', '800', '-t', '20']) except: print('traceback.format_exc():\n%s' % traceback.format_exc()) exit() pulse(20) try: result = subprocess.call(['raspistill', '-rot', '180', '-o', jpgpath3, '--brightness', '55', '--contrast', '1.25', '-vf', '-hf', '-w', '600', '-h', '800', '-t', '20']) except: print('traceback.format_exc():\n%s' % traceback.format_exc()) exit() buttonshim.set_pixel(0x00, 0x00, 0xff) #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 = half2.resize(newsize) half3 = half3.resize(newsize) half1 = half1.convert("1") half1.save(bmpath1) half2 = half2.convert("1") half2.save(bmpath2) half3 = half3.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) headerIsAt = imagesPath + 'header.jpg' header = Image.open(headerIsAt) im1 = Image.open(jpgpath1) im2 = Image.open(jpgpath2) im3 = Image.open(jpgpath3) get_concat_v(header, im1).save(composite) concaz = Image.open(composite) get_concat_v(concaz, im2).save(composite) concaz = Image.open(composite) get_concat_v(concaz, im3).save(composite) concaz = Image.open(composite) #post to ssb and print if button_flag == "button_1": button_flag == "null" #add the photo strip to ssb key = addtoDB.addToSSB(composite,dbPath,0) # 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 = 6, border = 1, ) # Add data qr.add_data(key) qr.make(fit=True) # Create an image from the QR Code instance img = qr.make_image() whereToSaveQR = imagesPath + 'QR.jpg' img.save(whereToSaveQR) # generate full composite for printing color=(255, 255, 255) dst = Image.new('RGB', (600, 2833), color) dst.paste(concaz, (0, 0)) whereQRinstructionsAre = imagesPath + 'ssbqrinstgurct.jpg' qrInstruct = Image.open(whereQRinstructionsAre) dst.paste(qrInstruct, (0,2575)) dst.paste(img, (342,2575)) # whereInstructionsPlusQRis = imagesPath + 'instructionsPlusQR.jpg' # get_concat_h_blank(qrInstruct, img, (255, 255, 255)).save(whereInstructionsPlusQRis) # instr = Image.open(whereInstructionsPlusQRis) #generate unique file_name unique = uuid.uuid4() unique = str(unique) FULLcomposite = imagesPath + unique + '.jpg' # save the full composite image for printing dst.save(FULLcomposite) #add each photo to the db addtoDB.addToDB(bmpath1,dbPath,FULLcomposite) addtoDB.addToDB(bmpath2,dbPath,FULLcomposite) addtoDB.addToDB(bmpath3,dbPath,FULLcomposite) # concatenate existing composite image with the instructions+QR composite for full composite image for print # get_concat_v(concaz, instr).save(FULLcomposite) # print Fullcomposite try: result = subprocess.call('lp ' + FULLcomposite, shell=True) except: print('traceback.format_exc():\n%s' % traceback.format_exc()) exit() #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(bmpath1) Limage.paste(bmp) draw = ImageDraw.Draw(Limage) draw.text((2, 240), 'photostrip posted', font = font18, fill = 255) draw.text((2, 260), 'to SSB', font = font18, fill = 255) epd.display(epd.getbuffer(Limage)) # move dbIndex to current photo so that reprint works right dbIndex = dbCount #post only locally and print if button_flag == "button_2": #led to thinking mode buttonshim.set_pixel(0x00, 0x00, 0xFF) button_flag == "null" #add each photo to the db addtoDB.addToDB(bmpath1,dbPath,composite) addtoDB.addToDB(bmpath2,dbPath,composite) addtoDB.addToDB(bmpath3,dbPath,composite) #update dbCount for item in db: dbCount = item.doc_id # print concaz that we already generated try: result = subprocess.call('lp ' + composite, shell=True) except: print('traceback.format_exc():\n%s' % traceback.format_exc()) exit() 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)) # move dbIndex to current photo so that reprint works right dbIndex = dbCount #just print if button_flag == "button_3": #led to thinking mode buttonshim.set_pixel(0x00, 0x00, 0xFF) button_flag == "null" # print concaz that we already generated try: result = subprocess.call('lp ' + composite, shell=True) except: print('traceback.format_exc():\n%s' % traceback.format_exc()) exit() exitPhotoMode = True #reshoot if (button_flag == "button_4"): # delete images os.remove(bmpath1) os.remove(bmpath2) os.remove(bmpath3) os.remove(jpgpath1) os.remove(jpgpath2) os.remove(jpgpath3) # reset buttonflag and loop around to take pic again button_flag == "null" #delete images and exit photo mode if (button_flag == "button_5"): button_flag == "null" buttonshim.set_pixel(0x00, 0x00, 0xff) exitPhotoMode = True #delete the images we took os.remove(bmpath1) os.remove(bmpath2) os.remove(bmpath3) os.remove(jpgpath1) os.remove(jpgpath2) os.remove(jpgpath3) # display next image timeIndex = intervalTime #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": buttonshim.set_pixel(0x00, 0x00, 0xFF) print ("next image") timeIndex = intervalTime button_flag = "null" #go back to the previous image elif button_flag == "button_3": buttonshim.set_pixel(0x00, 0x00, 0xFF) 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_5": buttonshim.set_pixel(0x00, 0x00, 0xFF) 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 5', 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_5": 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) #reprint current set of pics elif button_flag == "button_4": button_flag = "null" print ("button flag set to null...") buttonshim.set_pixel(0x00, 0x00, 0xFF) #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 (probably wouldn't be in this case buuut whatever, this prevents errors) while newFlier == None: dbIndex+=1 newFlier = db.get(doc_id=dbIndex) # get composite from db flierPath = newFlier["composite"] if flierPath == None: print ("no flier here...") # print composite try: result = subprocess.call('lp ' + flierPath, shell=True) print ("printing...") except: print('traceback.format_exc():\n%s' % traceback.format_exc()) exit() buttonshim.set_pixel(0x00, 0x00, 0x00) button_flag = "null" # 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)