Skip to content

Tymotex/InkMemories

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

60 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

InkMemories

Ink Memories is a Raspberry Pi eInk project that fetches images from an album in Google Photos and displays them to the eInk screen.

demo.mp4

Table of Contents

Components

  • Raspberry Pi Zero W and its power supply.
  • Inky Impression 5.7" 600x448p (7 colour eInk HAT). Source.
  • Optional 3D printed stand.

Usage Notes

Follow the 'Setup Instructions' to set up and begin running Ink Memories on a new Pi zero.

InkMemories will automatically display a new image to the screen every hour.

  • Press the top left button (labeled 'A') to force refresh a new image.
  • Press the second button from the top (labeled 'B') to enter debug mode which displays some recent logs from the main Python script.
    • Pressing 'B' while in debug mode refreshes the displayed logs. Due to the screen's long refresh rate, the display can't be real-time. Faster monochrome refresh is not supported
    • Pressing 'A' while in debug mode switches back to showing images regularly.
  • Press the bottom left button (labeled 'D') to gracefully shut down the system.
    • Unplug after several seconds to disconnect power.
    • The image will persist on the eInk display indefinitely and without power.
    • Upon reconnecting to power, the system will automatically begin refreshing the image periodically again.

Beware: disconnecting the Raspberry Pi directly from power can corrupt the SD card. Always click the bottom left button to perform a graceful shutdown before unplugging from power.

  • As a one-off operation, you can make the screen display a specific image with: cd displayer_service and python display_image.py $IMAGE_FILE_PATH. For example: python display_image.py test-images/ultrawide-wallpaper.png.

Note: This project needs to be run with root privileges in order to headlessly shut down the Pi. Without root privileges, the process will prompt for a password.

  • To configure display parameters, such as the time taken between automatic image refreshes, modify displayer_service/display_config.json, then reload the displayer service with sudo systemctl restart ink-memories-displayer for the config changes to be applied.

Setup Instructions

These instructions assume that you have set up Raspbian OS.

  1. Using sudo raspi-config, enable I2C and SPI. This is necessary for getting the e-ink display to work.

  2. Run sudo apt update to ensure packages fetched in a later step are up to date.

  3. Clone this repo: git clone https://github.com/Tymotex/InkMemories.git.

  4. Set up Google API credentials. Follow Google's getting started guide to:

    1. Set up a new project.
    2. Enable Google Photos API.
    3. Request an OAuth 2.0 client ID. As a result, you'll get a client ID and client secret that you'll need to supply rclone later.
  5. Run sudo bash ./setup.sh to be taken through an interactive setup of rclone, and the .service files to run the Image Source and Displayer services.

    • Create a new remote and set the remote name to GooglePhotos.

      No remotes found, make a new one?
      n) New remote
      s) Set configuration password
      q) Quit config
      n/s/q> n
      
      Enter name for new remote.
      name> GooglePhotos
    • Next, select the storage service to be whichever ID corresponds to Google Photos.

    • This should start up the Image Source Service and Displayer Service. Verify that they work by running:

      sudo systemctl status ink-memories-image-source.service
      sudo systemctl status ink-memories-displayer.service
    • Manage Ink Memories with systemctl:

      # Kill the service.
      sudo systemctl stop ink-memories-image-source.service
      sudo systemctl stop ink-memories-displayer.service
    • For reference, in my case I supplied these args to setup.sh when it prompted for them:

      Image source path: /home/pi/Pictures/InkMemories
      Project directory (this repo's root path): /home/pi/InkMemories
      Album name: 🥑🍉
      
    • After the services are running, an image should be displayed after a minute or two.

  6. Edit displayer_service/display_config.json, setting image_source_dir to be the chosen image source path in the previous step. In my case, /home/pi/Pictures/InkMemories.

  7. Run unit tests: pytest from the displayer_service directory.

  8. (Optional) Run the image displayer direcly with python app.py in the displayer_service directory.

How it works

In essence, there are two major components:

  1. Image Source Service. This is just rclone running as a daemon under systemd.
  2. Displayer Service. This is a daemon running a Python project, displayer_service, that handles writing images to the screen, pulling new images from the Image Source Service, button presses, etc.

Both are configured to run on system startup.

Ink Memories architecture

Displayer Service architecture

Google Photos API

The photos are sourced from a shared Google Photos album.

Note: As of 2023, the free tier permits 10000 requests per day for operations such as uploading images, and 75000 requests per day for reading images. In other words, it's almost certainly enough for this project but may require some image caching on our side to reduce requests for very large albums and very short image refresh periods.

This project does not interact with the Google Photos API directly - instead it uses rclone to connect to the Google Photos API and mount a photo album to a directory in the local filesystem, making it available to the displayer service to consume with filesystem semantics. The benefit of this approach is that displayer service is decoupled from the image service used. This means it would be easy to swap out Google Photos for other cloud storage services such as Dropbox or S3, or for local image files.

Image Processing

Python has a library, Pillow, for manipulating images that this project relies on heavily.

The display used in this project is 600x448p. Ideally, images should have their aspect ratio preserved while resized to fit into the display. The (imperfect) solution chosen in this project is to crop the center.

A better solution would be to use AI to pick the best frame to crop from an image (e.g. one that includes the apparent subject of the photo or the photographee's faces.)

center crop demonstration

The image processing module can also extract EXIF data and burn it into the image for display. In this case, the DateTime field is formatted and displayed on each image.

Potential Features & Improvements

This was a rushed project. Here are some ideas for how to improve upon the MVP:

  • On failure to connect to Google Photos or otherwise, display an error image to prompt the user to troubleshoot. Even better, render an HTML page with instructions and logs.
  • Design a better stand: e.g. one that fits better, secures the display better, has engravings, etc.
  • Use an AI model to identify photos containing only human faces and display only those.
  • Embed the image into an HTML page, allowing the freedom to adding HTML/CSS customisations. The Inky Impressions library supports the display of HTML pages.
    • Idea: Could add in image metadata like the date the photo was taken and the location as well as description, if one exists.
  • The displayer code doesn't cope well with portrait photos - it takes a central crop which works maybe 95% of the time. Use a better framing algorithm.
  • Design and deploy a configuration web frontend.
  • Photos queueing and rotations (similar to a Spotify music queue and playlist).
  • Prevent randomised picking algorithm from re-picking the same photo as last time.
  • Refresh the countdown for the image refresh when manually invoked.
  • Cache photos fetched from Google Photos API (this prevents being bottlenecked by network and should improve performance a lot, and it prevents the unlikely situation of hitting Google Photos' free tier quota).
  • Clean up tech debt in the codebase and have more exhaustive unit test suites.
  • Build and distribute an executable binary instead of directly invoking the Python interpreter. Alternatively, dockerise the project and run these services as containers.
  • Improve the setup script:
    • Skip steps.
    • Arg validation and confirmation prompts.

Showcase image

About

A cloud-powered, eInk, smart photo frame.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published