Blog

Inky Impression Picture Frame #2: Creating My Node.js App

A couple of weeks ago I posted about picking up an Inky Impression picture frame and taking the initial steps to getting it working with a Node.js app.

That app is definitely not done, but it has continued to take shape over the last couple of weeks. I’ve been holding off on posting about it because – as I said in my original post above – one of the features I wanted was to have sync with a specific folder in my Google Photos account. Google have recently made some changes to their Photos API that in theory make what I want to do impossible.

I came up with an approach that shouldn’t work but somehow does. I came across someone else’s project with similar hardware (that I can’t find the link to right now, sorry) who was using a tool called rclone to sync from a Google Photos album. I’m familiar with rclone, and I use it to back up to my cloud storage provider. Google Photos is one of the providers it works with, so I thought I’d give it a try.

The changes to the Google Photos API that were going to affect me should be having the exact same impact on the rclone software but for some reason that just doesn’t seem to be the case. It’s now been four days since Google supposedly implemented their API changes and my setup is still working 🤞

Even better, someone has created a Node.js wrapper for rclone that I can simply plug directly into my app as a library.

Here’s my code (which is also available in my git repo for the project):

const rclone = require('rclone.js')
const schedule = require('node-schedule')
const fs = require('fs')
const sharp = require('sharp')
const inky = require('@aeroniemi/inky')

const frame = new inky.Impression73()
const folder = './gallery/'

var ticker = false
var filelist = false

function downloadImgs() {
	console.log('Starting download from Google Photos...')
	const sync = rclone.sync('gphotos:album/Family Room Photo Frame', folder)

	sync.stdout.on('data', function(data) {
		console.log(data.toString())
	})

	sync.on('exit', function() {
		console.log('Synchronized from Google Photos. Loading images...')
		loadImgs()
	})
}

function loadImgs() {
	fs.readdir(folder, (err, files) => {
		if (err || files.length == 0) {
			console.log('No images found. Exiting.')
			process.exit(1)
		} else {
			console.log(files.length + ' images found. Starting album display...')
			filelist = files
			displayAlbum()
		}
	})
}

function displayAlbum() {
	if (filelist.length == 0) loadImgs()
	else {
		imgref = Math.floor(Math.random() * filelist.length)
		imgfile = filelist.splice(imgref, 1)[0]
		console.log('Randomly displaying image ' + (imgref + 1) + ' of ' + (filelist.length + 1) + ' remaining: ' + imgfile)
		displayImage(imgfile)
		ticker = setTimeout(displayAlbum, 1800000)
	}
}

function displayImage(filename) {
	sharp(folder + filename)
	  .resize(800,480)
	  .toFile('output.png', (err, info) => {
		frame.display_png('output.png')
		frame.show()
	})
}

// We start by downloading images. Other functions will be called if this is successful...
downloadImgs()

// Define a cron-job to restart everything at 1:30am (including a refresh of the album)....
schedule.scheduleJob('0 30 1 * * *', function() {
	clearTimeout(ticker)
	console.log('Resetting...')
	downloadImgs()
})

It’s of course still a work in progress. There’s a lot of stuff here that should be a configurable option that I’ve just hardcoded in, and there’s not yet any way to interact with the app, but essentially what it does is pretty straight-forward:

  1. On startup, the code downloads the content of a Google Photos album named ‘Family Room Photo Frame’
  2. It displays a random photo and crosses that one off the list, waits 30 minutes, picks another random photo from what’s left, displays it, and repeats the cycle until all photos have been shown.
  3. When there are no photos left it reloads the list and starts the cycle over
  4. Every morning at 1:30am it stops what it’s doing and starts everything over, including doing a refresh of what’s in the Google Photos album

To set it up you mostly just need to clone the git repo and run it, but there are a couple of important extra steps. You need to run npx rclone config and set up an rclone remote named gphotos to connect to your Google Photos account. Inside that account you either need to have an album named Family Room Photo Frame or you need to edit line 15 of the code to match an album that you do have. It can be a shared album, so others in the household can contribute to it too.

I then used pm2, which is my favourite tool for managing the running of node apps, to start the app. PM2 takes care of both running the app as a service of sorts on system startup, and will also automatically restart the app in the event that it crashes.

More to come, but I’m already loving this thing.

Blog

New Nerdy Project: Inky Impression Picture Frame

The other day (coincidentally, on Pi Day) I posted on Mastodon about picking up some new Raspberry Pi and accessories for a couple of new projects.

Well, this is the first one! I’ve constructed an e-ink picture frame.

Over on YouTube, AKZ Dev has recently posted a few videos about the hardware I’m using: a 7.3″ e-ink display from Pimoroni called the Inky Impression.

He’s also been writing some python software to power it, and while I’m sure what he’s created is great the point of this project for me is as a learning exercise, so I’ll be writing my own code.

Hardware

First of all though, I had to assemble the hardware. This is pretty straight-forward:

I may still get a wooden picture frame to put this in for better aesthetics, but for now my 3D printed version is working great. You put the Pi into its 3D printed case, the display into its 3D printed case, and then you plug them together and give it some power. Done!

I wish that the colours on the display were a little more vibrant than they are (this aspect of the display is not quite as good as I was expecting it to be) and I wish it updated a little more quickly (this is exactly as I expected it to be, and it takes about 30 seconds), but on the whole I like this e-ink display much more than I like backlit LCD photo frames.

Software

I downloaded my new favourite OS for the Raspberry Pi, DietPi, as a base and let it guide me through the setup process to connect to WiFi, set the timezone, the usual stuff. A couple of particular options to pay attention to during setup were to enable I2C and SPI, which is how the Pi communicates with the screen through the GPIO header, and install a NodeJS environment which I did simply by selecting it from the DietPi software installer.

Pimoroni suggests Python as the language to use and offers a Python library for that purpose, but I have limited experience in Python and I’m much more comfortable with Node, so that’s what I’m going to use if I can.

As a proof of concept, I wrote a half-dozen lines of code that take an input image, resize and crop it to fit the display, and render it on the screen. I’m using the sharp library for the image manipulation, and the inky library from aeroniemi to interact with the display.

const sharp = require('sharp')
const inky = require('@aeroniemi/inky')
const frame = new inky.Impression73()
sharp('../img/OriginalImage.jpg')
  .resize(800, 480)
  .toFile('output.png', (err, info) => {
	frame.display_png('output.png')
	frame.show()
})

So far this is working great. I obviously plan to extend this, and I’ll make the eventual code available if anyone else wants to replicate what I’ve done. Features I’m currently thinking about are:

  • Connect to a specific album in my Google Photos account
  • Rotate through the images in the album, updating every five minutes or so
  • Ability to skip forward and back using the buttons on the side of the display, maybe
  • Ability to skip forward and back by issuing a command over MQTT (of course I’m integrating this thing with Home Assistant)
  • Ability to display some specific content (other than the usual album) based on an MQTT command or button press… what I’m thinking of here is my WiFi username and password that I can interrupt the slideshow to display for guests along with a QR code with the connection details
  • Maybe the ability to display a webpage by loading it headlessly and taking a screenshot (I believe AKZ Dev’s software can do this and it seems like it opens up lots of possibilities)
  • Maybe a REST API in case I want to use that rather than MQTT

This’ll all take me some time and I’m not in a rush, but I’ll post more as I work on it!