#!/usr/bin/python3 # -*- coding: utf-8 -*- #################### # Inspiration / Further Reading #################### # https://circuitpython.readthedocs.io/projects/bh1750/en/latest/ # https://raspi.tv/2013/how-to-use-interrupts-with-python-on-the-raspberry-pi-and-rpi-gpio-part-3 # https://stackoverflow.com/questions/16148735/how-to-implement-a-watchdog-timer-in-python # https://learn.adafruit.com/adafruit-bh1750-ambient-light-sensor # https://learn.adafruit.com/adafruit-mini-pitft-135x240-color-tft-add-on-for-raspberry-pi/overview import board import busio import dbus import digitalio import os import time from threading import Timer from PIL import Image, ImageDraw, ImageFont import RPi.GPIO as GPIO import adafruit_rgb_display.st7789 as st7789 import adafruit_bh1750 # Generic watchdog timer used for backlight controls class Watchdog: def __init__(self, timeout, userHandler=None): # timeout in seconds self.timeout = timeout self.handler = userHandler if userHandler is not None else self.defaultHandler self.timer = Timer(self.timeout, self.handler) # Do NOT start timer by default ; we want this triggered by reset events that come through # via the inotify filesystem watcher #self.timer.start() def reset(self): self.timer.cancel() self.timer = Timer(self.timeout, self.handler) self.timer.start() def stop(self): self.timer.cancel() def defaultHandler(self): raise self # Cheat for walking only one level deep via os.walk (faster than other options) def walklevel(some_dir, level=1): some_dir = some_dir.rstrip(os.path.sep) assert os.path.isdir(some_dir) num_sep = some_dir.count(os.path.sep) for root, dirs, files in os.walk(some_dir): yield root, dirs, files num_sep_this = root.count(os.path.sep) if num_sep + level <= num_sep_this: del dirs[:] # bh1740 lux sensor i2c = busio.I2C(board.SCL, board.SDA) sensor = adafruit_bh1750.BH1750(i2c) # Configuration for CS and DC pins (these are FeatherWing defaults on M0/M4): cs_pin = digitalio.DigitalInOut(board.CE0) dc_pin = digitalio.DigitalInOut(board.D25) reset_pin = None # Setup button pins as inputs GPIO.setup(23, GPIO.IN) GPIO.setup(24, GPIO.IN) # Config for display baudrate (default max is 24mhz): BAUDRATE = 64000000 # Setup SPI bus using hardware SPI: spi = board.SPI() # Create the ST7789 display: disp = st7789.ST7789( spi, cs=cs_pin, dc=dc_pin, rst=reset_pin, baudrate=BAUDRATE, width=135, height=240, x_offset=53, y_offset=40, ) # Create blank image for drawing. # Make sure to create image with mode 'RGB' for full color. height = disp.width # we swap height/width to rotate it to landscape! width = disp.height image = Image.new('RGB', (width, height)) rotation = 90 # Get drawing object to draw on image. draw = ImageDraw.Draw(image) # Draw a black filled box to clear the image. draw.rectangle((0, 0, width, height), outline=0, fill=(0, 0, 0)) disp.image(image, rotation) # Draw some shapes. # First define some constants to allow easy resizing of shapes. padding = -2 top = padding bottom = height - padding # Move left to right keeping track of the current x position for drawing shapes. x = 0 # Alternatively load a TTF font. Make sure the .ttf font file is in the # same directory as the python script! # Some other nice fonts to try: http://www.dafont.com/bitmap.php #font = ImageFont.truetype('/usr/share/fonts/truetype/dejavu/DejaVuSansMono-Bold.ttf', 24) font_small = font = ImageFont.truetype('/usr/share/fonts/truetype/dejavu/DejaVuSansMono-Bold.ttf', 24) font = ImageFont.truetype('/usr/share/fonts/truetype/dejavu/DejaVuSansMono-Bold.ttf', 48) # Turn on the backlight backlight = digitalio.DigitalInOut(board.D22) backlight.switch_to_output() backlight.value = True # Watchdog timer for screen on/off control based on button presses # Turns off screen 10s after last button press # Turns on screen for 10s when starting up # Restart fim slideshow when the screen blanks so there is an indicator when it's going to change the selection def backlight_callback(): global backlight backlight.value = False sysbus = dbus.SystemBus() systemd1 = sysbus.get_object('org.freedesktop.systemd1', '/org/freedesktop/systemd1') manager = dbus.Interface(systemd1, 'org.freedesktop.systemd1.Manager') job = manager.RestartUnit('fim.service', 'fail') watchdog = Watchdog(10.0, backlight_callback) # Figure out which albums are present as well as the special 'All' album albums = [ 'All' ] selected_album = 0 try: with open('/etc/default/fim_album_index', 'r') as f: selected_album = int(f.readline()) except IOError: pass except ValueError: pass for root, dirs, files in walklevel('/tank/pictures'): for folder in dirs: if folder == '.stfolder': continue albums.append(folder) albums.sort() # Method for drawing on the screen # Called by the button handlers since we don't need anything more than a basic while(1) main for this setup def refresh_screen(): global width, height, x, y, top, draw, selected_album, albums, font, font_small # Draw a black filled box to clear the image. draw.rectangle((0, 0, width, height), outline=0, fill=0) # Split the line on spaces and then add a blank line if needed to avoid crashes text_lines = albums[selected_album].split(' ') if len(text_lines) < 2: text_lines.append('') # Write some text y = top draw.text((x, y), text_lines[0], font=font, fill='#DCDCDC') y += font.getsize('0')[1] draw.text((x, y), text_lines[1], font=font, fill='#DCDCDC') lux = 'Light: %.2f Lux' % sensor.lux y = height - font_small.getsize(lux)[1] draw.text((x, y), lux, font=font_small, fill='#DCDCDC') # Display image. disp.image(image, rotation) # Write current album when restarting the slideshow def write_selected_album(): with open('/etc/default/fim_album_index', 'w') as f: f.write(str(selected_album)) with open('/etc/default/fim_album', 'w') as f: if not selected_album == 0: f.write('/tank/pictures/' + albums[selected_album]) else: f.write('/tank/pictures') # Interrupt callback for changing albums (up) def up_button(channel): global selected_album, albums, backlight backlight.value = True selected_album += 1 if selected_album >= len(albums): selected_album = 0 refresh_screen() write_selected_album() watchdog.reset() # Interrupt callback for changing albums (dn) def dn_button(channel): global selected_album, albums, backlight backlight.value = True selected_album -= 1 if selected_album < 0: selected_album = len(albums) - 1 refresh_screen() write_selected_album() watchdog.reset() # Setup button interrupts GPIO.add_event_detect(23, GPIO.FALLING, callback=up_button, bouncetime=300) GPIO.add_event_detect(24, GPIO.FALLING, callback=dn_button, bouncetime=300) # Draw initial screen state refresh_screen() # Kick off watchdog to start the 10s initial 'on' status watchdog.reset() # Work with the LCD / Buttons in an infinate loop while True: time.sleep(300)