the public repo for the e-paper bulletin board. It's a bulletin board that syncs over scuttlebutt :)
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

407 lines
14 KiB

5 months ago
5 months ago
4 months ago
5 months ago
4 months ago
5 months ago
4 months ago
5 months ago
4 months ago
4 months ago
5 months ago
4 months ago
5 months ago
4 months ago
5 months ago
5 months ago
5 months ago
5 months ago
5 months ago
5 months ago
5 months ago
  1. #!/usr/bin/python
  2. # -*- coding:utf-8 -*-
  3. # This is the main program of the EBB: E-Paper Bulletin Board. More info at https://git.laboratoryb.org/trav/ebb
  4. #
  5. # carousel iterates through all posts in posts.json
  6. import os
  7. import epd4in2
  8. import traceback
  9. import time
  10. import signal
  11. import uuid
  12. import subprocess
  13. import buttonshim
  14. import addtoDB
  15. import refreshdb
  16. import ConfigParser
  17. from tinydb import TinyDB, Query
  18. from PIL import Image,ImageDraw,ImageFont
  19. ## INITIALIZE ##
  20. #Let the user know we're booting: with a PURPLE led
  21. buttonshim.set_pixel(0xFF, 0x00, 0xFF)
  22. #intervalTime is how many seconds before moving to next image (300 = 5 minutes)
  23. #sync time is how many seconds before refreshing the DB from SSB
  24. #this should be moved into the config file...
  25. intervalTime = 1800
  26. syncTime = 3600
  27. #keep track of where we are moving through intervalTime, syncTime and where we are in the db, and what flier we're currently displaying
  28. timeIndex = 1200
  29. syncIndex = 0
  30. dbIndex = 1
  31. flierPath = "noneyet"
  32. button_flag = "null"
  33. #pull some vals from the config
  34. configParser = ConfigParser.RawConfigParser()
  35. configFilePath = r'config.txt'
  36. configParser.read(configFilePath)
  37. imagesPath = configParser.get('ebb-config', 'imagesPath')
  38. dbPath = configParser.get('ebb-config', 'dbPath')
  39. #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...
  40. db = TinyDB(dbPath)
  41. howmany = Query()
  42. dbCount = 0
  43. for item in db:
  44. dbCount = item.doc_id
  45. print dbCount
  46. #initialize display and display boot image
  47. #grab the entry at dbIndex, newFlier is a dict
  48. newFlier = db.get(doc_id=dbIndex)
  49. flierPath = "/home/pi/images/" + newFlier["path"]
  50. Limage = Image.new('1', (epd4in2.EPD_HEIGHT, epd4in2.EPD_WIDTH), 255) # 255: clear the frame
  51. try:
  52. font18 = ImageFont.truetype('/usr/share/fonts/truetype/wqy/wqy-microhei.ttc', 36)
  53. epd = epd4in2.EPD()
  54. epd.init()
  55. bmp = Image.open(str(flierPath))
  56. Limage.paste(bmp)
  57. draw = ImageDraw.Draw(Limage)
  58. draw.text((2, 280), 'EBB', font = font18, fill = 255)
  59. draw.text((2, 300), 'version .02', font = font18, fill = 255)
  60. epd.display(epd.getbuffer(Limage))
  61. time.sleep(2)
  62. epd.sleep()
  63. except:
  64. print 'traceback.format_exc():\n%s' % traceback.format_exc()
  65. exit()
  66. ## button press contingencies ##
  67. @buttonshim.on_press(buttonshim.BUTTON_A)
  68. def button_a(button, pressed):
  69. global button_flag
  70. buttonshim.set_pixel(0xFF, 0x00, 0x00)
  71. button_flag = "button_1"
  72. @buttonshim.on_press(buttonshim.BUTTON_B)
  73. def button_b(button, pressed):
  74. global button_flag
  75. buttonshim.set_pixel(0x00, 0x00, 0xFF)
  76. button_flag = "button_2"
  77. @buttonshim.on_press(buttonshim.BUTTON_C)
  78. def button_c(button, pressed):
  79. global button_flag
  80. buttonshim.set_pixel(0x00, 0x00, 0xFF)
  81. button_flag = "button_3"
  82. @buttonshim.on_press(buttonshim.BUTTON_D)
  83. def button_d(button, pressed):
  84. global button_flag
  85. buttonshim.set_pixel(0xFF, 0x00, 0x00)
  86. button_flag = "button_4"
  87. @buttonshim.on_press(buttonshim.BUTTON_E)
  88. def button_e(button, pressed):
  89. global button_flag
  90. buttonshim.set_pixel(0x00, 0x00, 0xFF)
  91. button_flag = "button_5"
  92. buttonshim.set_pixel(0x00, 0x00, 0x00)
  93. buttonshim.set_pixel(0x00, 0x00, 0x00)
  94. ## MAIN LOOP ##
  95. while True:
  96. # chill for a bit, keep track of how long we're chilling
  97. time.sleep(2)
  98. print "time:", timeIndex, "/", intervalTime, "index", dbIndex, "/", dbCount, "sync time:", syncIndex, "/", syncTime
  99. #iterate through syncTime and image time
  100. syncIndex+=2
  101. timeIndex+=2
  102. #time to sync db with ssb
  103. if syncIndex >= syncTime:
  104. #light up red while syncing db
  105. buttonshim.set_pixel(0xFF, 0x00, 0x00)
  106. refreshdb.fresh()
  107. #db count may have changed
  108. for item in db:
  109. dbCount = item.doc_id
  110. syncIndex = 0
  111. buttonshim.set_pixel(0x00, 0x00, 0x00)
  112. #time to iterate through images
  113. if timeIndex >= intervalTime:
  114. #reset the index
  115. timeIndex = 0
  116. # iterate where we are in the db
  117. dbIndex+=1
  118. # if dbIndex is now out of range, reset to beginning (skipping 1 because that's the boot image)
  119. if dbIndex > dbCount:
  120. dbIndex=2
  121. #grab the entry at dbIndex, newFlier is a dict
  122. print ("getting db at")
  123. print (dbIndex)
  124. newFlier = db.get(doc_id=dbIndex)
  125. #if that's an empty spot in the db we gotta keep lookin
  126. while newFlier == None:
  127. dbIndex+=1
  128. newFlier = db.get(doc_id=dbIndex)
  129. flierPath = newFlier["path"]
  130. #display the image
  131. try:
  132. epd.init()
  133. bmp = Image.open(flierPath)
  134. Limage.paste(bmp)
  135. epd.display(epd.getbuffer(Limage))
  136. time.sleep(2)
  137. timeIndex+=2
  138. epd.sleep()
  139. except:
  140. print 'traceback.format_exc():\n%s' % traceback.format_exc()
  141. exit()
  142. #in case the LED was on because we're buttoning through images
  143. buttonshim.set_pixel(0x00, 0x00, 0x00)
  144. #take pic
  145. if button_flag == "button_1":
  146. #generate unique file_name
  147. unique = uuid.uuid4()
  148. unique = str(unique)
  149. jpgpath = imagesPath + unique + '.jpg'
  150. #generate unique bmp name
  151. bmpath = imagesPath + unique + '.bmp'
  152. #loop in case we wanna re-take it
  153. exitPhotoMode = False
  154. while exitPhotoMode == False:
  155. #take photo
  156. try:
  157. result = subprocess.call(['raspistill', '-o', jpgpath, '-vf', '-hf', '-w', '300', '-h', '400', '-t', '1000'])
  158. except:
  159. print 'traceback.format_exc():\n%s' % traceback.format_exc()
  160. exit()
  161. #convert to bmp
  162. im = Image.open(jpgpath)
  163. im = im.convert("1")
  164. im.save(bmpath)
  165. #display the image
  166. epd.init()
  167. bmp = Image.open(bmpath)
  168. Limage.paste(bmp)
  169. epd.display(epd.getbuffer(Limage))
  170. #wait for user decision on photo
  171. waited = 0
  172. button_flag = "none"
  173. #strobe light green yellow red for a while
  174. while (button_flag == "none") and (waited < 30):
  175. buttonshim.set_pixel(0xFF, 0x00, 0x00)
  176. time.sleep(.2)
  177. buttonshim.set_pixel(0xFF, 0xFF, 0x00)
  178. time.sleep(.2)
  179. buttonshim.set_pixel(0x00, 0xFF, 0x00)
  180. time.sleep(.2)
  181. buttonshim.set_pixel(0xFF, 0xFF, 0x00)
  182. time.sleep(.2)
  183. waited+=1
  184. #if still not sure, give em the menu
  185. if button_flag == "none":
  186. #ok time to display menu
  187. draw = ImageDraw.Draw(Limage)
  188. font18 = ImageFont.truetype('/usr/share/fonts/truetype/wqy/wqy-microhei.ttc', 24)
  189. draw.text((2, 0), 'press button 1 to:', font = font18, fill = 255)
  190. draw.text((2, 40), 'post online', font = font18, fill = 255)
  191. draw.text((2, 80), 'button 2: post photo', font = font18, fill = 255)
  192. draw.text((2, 120), 'here only', font = font18, fill = 255)
  193. draw.text((2, 160), 'button 3: retake', font = font18, fill = 255)
  194. draw.text((2, 200), 'buttons 4 or 5:', font = font18, fill = 255)
  195. draw.text((2, 240), 'exit photo mode', font = font18, fill = 255)
  196. epd.display(epd.getbuffer(Limage))
  197. #now just wait for them to press something
  198. while button_flag == "none":
  199. buttonshim.set_pixel(0xFF, 0x00, 0x00)
  200. time.sleep(.2)
  201. buttonshim.set_pixel(0xFF, 0xFF, 0x00)
  202. time.sleep(.2)
  203. buttonshim.set_pixel(0x00, 0xFF, 0x00)
  204. time.sleep(.2)
  205. buttonshim.set_pixel(0xFF, 0xFF, 0x00)
  206. time.sleep(.2)
  207. #led to thinking mode
  208. buttonshim.set_pixel(0x00, 0x00, 0xFF)
  209. #post to ssb
  210. if button_flag == "button_1":
  211. button_flag == "null"
  212. #add the photo to the db/ssb
  213. addtoDB.addFile(bmpath,dbPath,0)
  214. #update dbCount
  215. for item in db:
  216. dbCount = item.doc_id
  217. exitPhotoMode = True
  218. #display the image to clear the menu (also display text "posted")
  219. font18 = ImageFont.truetype('/usr/share/fonts/truetype/wqy/wqy-microhei.ttc', 24)
  220. bmp = Image.open(bmpath)
  221. Limage.paste(bmp)
  222. draw = ImageDraw.Draw(Limage)
  223. draw.text((2, 240), 'PHOTO POSTED', font = font18, fill = 255)
  224. draw.text((2, 260), 'online!', font = font18, fill = 255)
  225. epd.display(epd.getbuffer(Limage))
  226. #post only locally
  227. if button_flag == "button_2":
  228. button_flag == "null"
  229. #add that file to the db. -1 tells addtoDB not to post to ssb.
  230. addtoDB.addFile(bmpath,dbPath,-1)
  231. #update dbCount
  232. for item in db:
  233. dbCount = item.doc_id
  234. exitPhotoMode = True
  235. #display the image to clear the menu (also display text "posted")
  236. font18 = ImageFont.truetype('/usr/share/fonts/truetype/wqy/wqy-microhei.ttc', 24)
  237. bmp = Image.open(bmpath)
  238. Limage.paste(bmp)
  239. draw = ImageDraw.Draw(Limage)
  240. draw.text((2, 240), 'PHOTO POSTED', font = font18, fill = 255)
  241. draw.text((2, 260), 'to this device only', font = font18, fill = 255)
  242. epd.display(epd.getbuffer(Limage))
  243. #reshoot the picture
  244. if button_flag == "button_3":
  245. button_flag == "null"
  246. #nothin! loop around... exitPhotoMode still false
  247. #exit photomode, delete photo
  248. if (button_flag == "button_4") or (button_flag == "button_5"):
  249. button_flag == "null"
  250. exitPhotoMode = True
  251. os.remove(bmpath)
  252. os.remove(jpgpath)
  253. #led off
  254. buttonshim.set_pixel(0x00, 0x00, 0x00)
  255. time.sleep(3)
  256. timeIndex+=2
  257. epd.sleep()
  258. button_flag = "null"
  259. #move to the next image
  260. elif button_flag == "button_2":
  261. print ("next image")
  262. timeIndex = intervalTime
  263. button_flag = "null"
  264. #go back to the previous image
  265. elif button_flag == "button_3":
  266. print ("previous image")
  267. #we go back 2 because we have to account for calling timeIndex = intervalTime iterates forward by 1
  268. dbIndex-=2
  269. if dbIndex < 2:
  270. dbIndex = dbCount-1
  271. timeIndex = intervalTime
  272. button_flag = "null"
  273. #delete current image
  274. elif button_flag == "button_4":
  275. button_flag = "null"
  276. #can't delete the default image
  277. if dbIndex != 1:
  278. #CONFIRM WITH USER THEY WANT TO DELETE
  279. epd.init()
  280. #Limage = Image.new('1', (epd4in2.EPD_HEIGHT, epd4in2.EPD_WIDTH), 255) # 255: clear the frame
  281. draw = ImageDraw.Draw(Limage)
  282. font18 = ImageFont.truetype('/usr/share/fonts/truetype/wqy/wqy-microhei.ttc', 24)
  283. draw.text((2, 0), 'Are you sure', font = font18, fill = 255)
  284. draw.text((2, 40), 'you want to delete', font = font18, fill = 255)
  285. draw.text((2, 80), 'the current image?', font = font18, fill = 255)
  286. draw.text((2, 120), 'Press button 4', font = font18, fill = 255)
  287. draw.text((2, 160), 'again to confirm.', font = font18, fill = 255)
  288. draw.text((2, 200), 'Press any other', font = font18, fill = 255)
  289. draw.text((2, 240), 'button for no.', font = font18, fill = 255)
  290. epd.display(epd.getbuffer(Limage))
  291. epd.sleep()
  292. while button_flag == "null":
  293. buttonshim.set_pixel(0xFF, 0x00, 0x00)
  294. time.sleep(.1)
  295. buttonshim.set_pixel(0x00, 0x00, 0x00)
  296. time.sleep(.05)
  297. #sounds like delete time
  298. if button_flag == "button_4":
  299. Fruit = Query()
  300. #results = db.search(Fruit.path == flierPath)
  301. db.remove(Fruit.path == flierPath)
  302. #update dbCount
  303. for item in db:
  304. dbCount = item.doc_id
  305. #ok so it's deleted, let's force display of next image
  306. timeIndex = intervalTime
  307. else:
  308. #stay on same image and refresh to get rid of delete text
  309. dbIndex-=1
  310. timeIndex = intervalTime
  311. button_flag = "null"
  312. buttonshim.set_pixel(0x00, 0x00, 0xFF)
  313. #weather button!
  314. elif button_flag == "button_5":
  315. #ACTUALLY PARSE THE JSON DUH, THIS IS BROKEN
  316. #get the weather
  317. #proc = subprocess.Popen(["wget", "http://wttr.in/btv_FnQT.png"], stdout=subprocess.PIPE)
  318. proc = subprocess.Popen(["wget", "http://wttr.in/btv_FQT.png"], stdout=subprocess.PIPE)
  319. (out, err) = proc.communicate()
  320. #convert
  321. size = 400, 300
  322. #too pixely
  323. #im = Image.open("btv_FnQT.png")
  324. #im = im.rotate(90, Image.NEAREST, "expand=1")
  325. #sideways, no rotate
  326. im = Image.open("btv_FQT.png")
  327. im.thumbnail(size, Image.BICUBIC)
  328. im = im.convert("1")
  329. im.save("btv.bmp")
  330. #display the weather
  331. epd.init()
  332. Limage = Image.new('1', (epd4in2.EPD_HEIGHT, epd4in2.EPD_WIDTH), 255) # 255: clear the frame
  333. bmp = Image.open("btv.bmp")
  334. Limage.paste(bmp)
  335. epd.display(epd.getbuffer(Limage))
  336. time.sleep(3)
  337. timeIndex+=2
  338. epd.sleep()
  339. button_flag = "null"
  340. buttonshim.set_pixel(0x00, 0x00, 0x00)