Lorenzo Miniero's blog

Presenting slides with a Raspberry Pi Zero W

My portable presenter device

At a previous edition of FOSDEM, I remember chatting with Saúl about how tiresome bringing your laptop just for presentations was, which is why he had shown up just with a much smaller tablet for the job. This year I started thinking if I could get to an even smaller device, and this is how I challenged myself a bit.

The requirements

The idea was fairly basic. I always present basic slides, so anything with support for PDFs and an ability to output HDMI would do the trick, coupled with a way to control it all of course. After all, what else do you really need? (unless you’re planning to do fancier things like showing a demo or something else, that is)

The first obvious answer that came to mind was, why not use my phone? Most modern phones do support HDMI out via their USB port. Unfortunately, that was a no go right away, since my old Xperia XZ1 Compact is not one of those modern phones at all. Getting HDMI output from my phone would have required expensive and bulgy items, which quickly convinced me I should try something else.

A first failed experiment

When discussing this in a chat with other geeks, someone suggested having a look cheap TV sticks: they do have HDMI out (they need to feed TVs, after all), and are in some cases hackable to do more than they were built for. Fire TV sticks were mentioned, as apparently there was a way to flash Linux on them. Considering those things are relatively cheap (the one I owned I gifted to my sister a few years ago), I decided to risk it and buy one, in order to try and do exactly that.

Unfortunately, the “they can be flashed” part I had really underestimated :grin: … If you check the link I shared above, the process is way too complicated for dumb old me (cause a short circuit to get a way in? solder stuff? I’m incompetent!!). As such, I decided to try and do it the soft way instead, and look for PDF viewer apps on the Fire TV store, and was disappointed once more: very few options, all behind a price wall and all with very low reviews. So, with my tail between my legs, I admitted defeat and put the Fire TV stick in a drawer, probably never to see it again (my TV is smart enough already).

Let’s try with a Pi zero

At this point I started to think: what’s the smallest thing I can already program somehow with my limited skills? This is when I started thinking of just getting a Raspberry Pi zero: definitely small enough, and although limited, with enough power to run a basic desktop too. I know Linux! I know scripts! That’s something I can pull off!

So that’s what I did. I bought a basic Pi zero W starter pack, which included HDMI and USB adapters (since all ports on the Pi zero are smaller), and started tinkering with it. The first step was installing the desktop version of Raspbian on it, and then I plugged my HDMI monitor in it, using a mini bluetooth keyboard/mouse to interact with it.

Automating the UI

The first part I started thinking about was how to pretty much automate it. Assuming I’d bring this device with me somewhere, I wouldn’t have any way to interact with it in the “wild”: it would need to boot up to a point where all I had to do was picking which slides I wanted to present, without making it look too much like a regular computer desktop.

In order to do that, I basically did these basic things:

The self-restarting script was obviously the most important thing, and I kept it pretty basic. I decided to simply use zenity as a picker: the script would check which PDFs were available in a certain folder and present them as a list; selecting a specific PDF file would have it opened with one of the available applications in fullscreen. Leaving the PDF application would bring the zenity picker back, so back at square zero.

This is what it looks like in practice: a simple selector that can be navigated easily with my clicker.

Zenity as a PDF picker

The choice of which PDF viewer to use was also part of the process. I started with evince just because it was the default viewer on Raspbian, but I didn’t like how very few configuration options it gave me. I tried impressive too, since it has fancy animations and has many scripting tweaks and capabilities, but it seemed to lean a bit too on the heavy side, for a device with limited capabilities. Eventually, I landed on xpdf because it seemed like the lightest of the bunch, especially on such an underpowered device. It still wasn’t as reactive as I hoped, when changing slides (more on that later, where I’ll share the full content of the script too), but for the moment I was ok with it.

Adding Bluetooth to the mix

After the basics were in, I started to wonder about the interactions aspects of it. I would use the clicker to choose the slide deck and then advance slides, but what about everything else? Like adding slides to present dynamically, or restarting/shutting down the device without just unplugging the power?

In order to address that, I decided to have a look at the Bluetooth capabilities of the Pi, and I thought that an easy way to do all that could be to have the Pi act as a Bluetooth file receiver, with something on the backend side monitoring stuff coming in. This way, I could use my phone to upload, e.g., a PDF, and have a script on the Pi move it to the right folder and restart the zenity script to show them in the list. To do that, I prepared a script using inotifywait to monitor changes in a specific folder (which I’ll talk about in a minute), and then I had the Pi autostart an obexpushd instance at startup, plus a few commands I found around to make it discoverable:

#!/bin/sh

# Don't require authentication
sudo hciconfig noauth
sleep 1
# Become discoverable
sudo hciconfig hci0 piscan
sleep 1
# Start Obex daemon
sudo obexpushd -B -o /home/lminiero/Downloads/pdf

Yeah, I know, the “don’t require authentication” part sucks, but I didn’t know how to make it require a static pin (what I found around didn’t work), and I couldn’t rely on any manual interaction to accept connections due to the lack of ways to interact with the device.

To make things super-easy, I decided to use file sharing as part of the control part too. In fact, I’m not a Bluetooth expert at all, and while I’d love to be able to know how to use it as bidirectional communication channel for control purposes, using files seemed like a much easier thing to do. As such, I configured the monitor script on the Pi so that, anytime it received a file with a specific name, it would implement a specific action. Namely, the filenames I used were the following:

If you’re wondering why all the “control” files have a .jpg extension, the reason is as simple as it is dumb: Android phones only allow you to send files via Bluetooth if they have certain extensions, and that was one of them! I also had to ensure the files were non-empty, or they wouldn’t be sent either. So yeah, quite ugly and quite hacky, but it worked!

The script monitoring uploads via Bluetooth is the following:

#!/bin/sh

UPLOADS="$HOME/Downloads/pdf/"
SLIDES="$HOME/Documents/slides/"

cd "$UPLOADS"

# Is there a command file?
if test -f "presenter_update.jpg"
then
	# There's new PDFs: copy them to the slides folder
	rm -f presenter_update.jpg
	mv *.pdf "$SLIDES"
	# Restart the presenter tool
	rm -f presenter_restart.jpg
	touch presenter_restart.jpg
fi

if test -f "presenter_restart.jpg"
then
	# We need to restart the presenter tool: kill
	# both zenity and xpdf, if they're running
	rm -f presenter_restart.jpg
	killall -9 zenity xpdf xpdf.real feh
fi

if test -f "presenter_reboot.jpg"
then
	# Reboot the Pi
	rm -f presenter_reboot.jpg
	rm -f presenter_shutdown.jpg
	sudo reboot
fi

if test -f "presenter_shutdown.jpg"
then
	# Shutdown the Pi
	rm -f presenter_reboot.jpg
	rm -f presenter_shutdown.jpg
	sudo shutdown -h now
fi

# Wait for new files in the download folder
inotifywait -e create "$UPLOADS"

# Restart
$(basename $0) && exit

As you can see, it’s very basic: every time it runs, it checks if there’s files with specific names, and if so it reacts in a specific way. If not, it starts inotifywait to monitor changes in the folder again.

Notice how there’s no custom reaction to PDF uploads: that’s because a new PDF upload would wake inotifywait up (a new file would be created in the folder), but there’s nothing we can do at that stage; we need to wait for the upload to finish, and the script doesn’t know when that’ll happen. The way I’ve handled it right now is you upload the PDF file first, and when you’re done you upload presenter_update.jpg to have the script move the file to the right place. There’s probably way better ways of handling this (maybe inotifywait has ways of detecting when the upload has ended? A file being closed maybe?), but this seemed good enough for me.

Improving the performance

I mentioned how I wasn’t very happy with how slow xpdf was when changing slides. The “solution” I came up with was basically pre-converting PDF slides to a series of images, and advance those using the clicker instead. As such, I modified my slide selector script to support both PDFs and folders with images in it as items that could be selected: PDFs would be opened via xpdf (as before), while for images I chose feh, since it’s a very lightweight and configurable image viewer application.

feh also allowed me to have more control on the interaction part. For instance, I could configure it to automatically exit after the last “slide”, and I could also map one of my clicker buttons to act as an “exit application” trigger too, which I coulnd’t do with xpdf. This is something you can do tweaking the contents of the $HOME/.config/feh/keys` file, which in my case looks like this:

lminiero@raspberrypi:~ $ cat ~/.config/feh/keys
next_img Right Up
prev_img Left Down
quit Return

As a result, the “final” version of my slide selector script looks like this:

#!/bin/sh

SLIDES="$HOME/Documents/slides"
cd $SLIDES
PDFS=`ls | grep pdf`
cd -

# Show options
RES=`zenity --list --title="Select a slide deck" --column "File" $PDFS`

if [ $? -eq 0 ]
then
	if [ -d $SLIDES/$RES ]
	then
		# Series of images
		feh -F -Y --on-last-slide quit $SLIDES/$RES/*
	else
		# PDF file
		xpdf -fullscreen $SLIDES/$RES
	fi
fi

$(basename $0) && exit

Again, nothing fancy or groundbreaking: it’s a very simple loop that uses zenity to present a choice, and then depending on what has been selected uses feh (for images) or xpdf (for PDFs).

This support for images indeed made the reaction to my clicks much faster, and considering I always use static PDF files anyway, didn’t make a difference at all from a presentation perspective. What I haven’t done yet (and should probably do) is automating the translation to images after uploading PDF files via bluetooth: in my tests I pre-converted them on my laptop, and ssh-ed them to where they needed to be.

How do I feed this thing?

Now, the thing was basically ready, but a big question came up: how do I feed this thing when I’m not home? In all my tests I had basically used a USB hub plugged in the AC to feed it, which is not really “portable”. UPS or custom batteries all seemed a bit too expensive or invasive.

Reading around I figured out the actual requirements for a Pi 0 were not that big, so I assumed any generic power bank could address them, but it turned out that wasn’t the case: or at least, my power bank definitely wasn’t enough. It turned out that, although the requirements are low, Pis generally require a pretty much constant and consistent 5V to work, and most power banks, even when advertised as able to do that, don’t always do that for real. Since they’re often just used to recharge phones and stuff like that, they’re much more lax with the requirements.

Going through forums, though, I stumbled upon this post that showed a Pi4B with screen and wireless keyboard being fed by a 13000mAh Anker power bank: a quick check online showed that this power bank was still available and relatively cheap, so I bought it and eureka, it really did the trick for me too!

A few problems to address

The thingie was ready, at this point, but there were a few problems to address, and two in particular:

  1. it took forever to boot (about 2 minutes until it shows the slides picker);
  2. if booted with no HDMI in, it wouldn’t hotplug HDMI when plugged in later on (no signal).

This was a considerable problem, because it meant I couldn’t just boot my thing first and then put the HDMI later on when it was my turn (nothing would show up), but at the same time I couldn’t keep people waiting two minutes until it was ready before I started myself.

On the boot times, there was unfortunately nothing I could do: the Pi zero is seriously underpowered, and booting to desktop is what takes the most time. I do know there are some tricks you can find online to speed things up a bit, but 1. I’m not that smart (I’d definitely break something), and 2., there’s only so much you can cut with a desktop involved. Getting rid of the desktop part is what would really make a difference, but that would mean restarting from scratch and using a different approach (more on that later).

On the second issue, though, I did find a partial solution. And no, not a solution on how to get hotplugging HDMI to work (I tried and failed), but a hack using an external device instead. More specifically, I figured out that if I started the Pi with my null-HDMI device plugged in (a tiny device that makes your computer think an HDMI device is plugged in, but really isn’t), I could later unplug it and plug a real HDMI screen and it would work! So that’s exactly what I did: this allowed me to pre-boot the device without access to the screen yet (e.g., shortly before my turn), and then swap the null-HDMI with the real HDMI cable.

Again, not really a solution: an ugly hack! But hey, I was experimenting, so it was fine by me :grin:

Let’s fly you to FOSDEM, thingie!

Eventually, I did bring the thingie to Brussels for FOSDEM, and actually used it for my AV1/SVC presentation in the Real Time Communications (RTC) devroom!

Me live at FOSDEM

Saúl introduced me by mentioning I had indeed brought a “crazy contraption”, that made my talk “almost a dangerous demo in and of itself”, which was quite hilarious :joy:

But, apart from maybe an incorrect use of the screen estate (you can see black corners around the slides, in the video, which suggest the Pi was not using all the pixels it could have), it worked just beautifully: I could use my clicker to select the correct set of slides (which you can’t see in the video, it starts after the presentation was opened), and then to navigate slides back and forth. When I was done, I unplugged the thingie from the HDMI, and used my phone to send the presenter_shutdown.jpg file to gracefully shut down the Pi before unplugging the power bank too. Eureka!

What’s next?

As a tiny little experiment, I was happy with the outcome, but it’s clear that this was not really the best or most optimized way of doing things…

Just use framebuffers, dummy!

In a much more polite way than this, this is basically what a few people I described my device to told me. Since a desktop is probably way too heavy for a tiny device like the Pi zero (especially for what concerns load times), why not write to the framebuffer directly, and get a much more optimized experience?

This is indeed what I plan to try next, as soon as I have some time. I guess this will mean getting rid of all the UI I came up for the job: no desktop means no zenity, no feh and no xpdf, which means I’ll have to code something to still use my clicker to choose what I want to present, and then render it accordingly (maybe sticking to just the pre-processed images to framebuffer?) in a custom application. I suspect it may have an impact on bluetooth capabilities too (maybe things that the desktop currently does for me automatically?), but that’s something I’ll only know when I start getting my hands dirty.

Feedback welcome!

This was just a personal and fun experiment, but of course I’d love to hear feedback if you have any, especially if you know of similar efforts (or devices) I could have used instead. I can also share my scripts in a repo somewhere, in case anyone is interested in getting a similar device for their own use, but as I said it’s a pretty basic and unoptimized setup, so I’m not sure I’d really be contributing anything of worth.

That said, please feel free to reach out on Mastodon for any question or comment related to this!