I've done a lot of quite complex digital signage projects in the past, mainly using Raspberry Pis but also some other platforms, talking to box office systems, scheduling systems, providing dynamic wayfinding etc but a recent project called for something simple.

We need a few screens that can be easily updated by anyone, without much technical knowledge, from multiple places, static images no video, no dynamic content.

Everything I've used before would be overkill and whilst off-the-shelf offerings like PiSigngage, Concerto, Yodeck etc can all achieve this there's a cost associated with all of them and it's hard to use the pi for anything else.

The "Slideshow"

So, first thing we need is a really simple slideshow application. Again, there's literally 100s of Javascript slideshows out there but we wrote something very simple (I used the Papa library for CSV parsing for laziness and 'free' validation, more on why this matters later)

<style type="text/css">
body { background-color: #000; }
#c {
  position: fixed; top: 0; left: 0;
  background-position: center;
  background-size: cover;
  background-image: url("/assets/signage_imgs/blank.png");
  height: 100%; width: 100%;
  transition: opacity 0.3s linear;
.hidden { display: none; }
<script src="/js/papa.js" language="javascript"></script>
<script language="javascript">
var monitorId = 'upstairs'; // this is the name of this monitor 
var slideshowPos = -1;
var playlist = [];
var curImage = '';

function updatePlaylist() {
	Papa.parse("/assets/signage_imgs/"+monitorId+".txt", {
		download: true,
		dynamicTyping: true,
		skipEmptyLines: true,
		complete: function(results) {
			if (Array.isArray(results.data)) { playlist = results.data; }

function nextSlide() {
	// if we don't have any slides yet, ignore it until we do
	if (playlist.length == 0) {
		setTimeout(nextSlide, 1000);
	// increment the counter
    // if we've reached the end of the list, start over
	if (playlist.length-1 < slideshowPos) {
		slideshowPos = 0;

	// buffer the next image
	if (playlist.length-1 > slideshowPos) {
		document.getElementById("buf").src = '/assets/signage_imgs/'+playlist[slideshowPos+1][0];

	// set timer for the next one
	setTimeout(nextSlide, playlist[slideshowPos][1]*1000);

function showImage(img) {
	if (curImage == img) { return true; }

	// change image
	curImage = img;

	document.getElementById("c").style.opacity = 0;
	setTimeout(function() {
		document.getElementById("c").style.backgroundImage = 'url(/assets/signage_imgs/'+curImage+')';
		setTimeout(function() { document.getElementById("c").style.opacity = 1; }, 200);
	}, 600);
setInterval(updatePlaylist, 5000);
<div id="c"></div>
<div class="hidden"><img id="buf" src="#"></div>

Simple. This script will read a .txt file (in this case upstairs.txt), whcih it expects to find a simple CSV with a filename and a duration in seconds e.g.


It will then show each of these images for the duration given (slide1.png for 10s, slide2.png for 6s etc)

This couldn't be simpler, all static files, no databases, all stored locally on a pi, just works.

So I then considered building a tool (probably in php) to upload images, manage slides etc etc... but there's a simpler way.

What do we already use?

Dropbox. The entire team uses Dropbox already to collaborate on various things, and everyone shares files on Dropbox, so rather than introduce a new tool we created a "Signage" directory on Dropbox and then all the clever bit is on the Pi.

To change the slides, anyone in the team just needs to put a file in that directory and edit the text files of the screens they want it to appear on, no additional software required, no new user interface to learn, no logins to worry about.

The rclone application will sync Dropbox to a specified directory on the pi, so we run this by cron every couple of minutes

*/2 * * * * rclone sync dropbox:/Signage /var/www/html/assets/signage_imgs >> /dev/null 2>&1

Now if we got to the Pi's IP address the slideshow will load and use whatever file(s) are in that directory.

Making the Pi auto-boot to the slideshow

There are literally 100s of ways of making a "kiosk mode" Pi. Use whatever means suits you best.

The simplest way I've found is as follows;

Install the following;

apt-get install --no-install-recommends xserver-xorg x11-xserver-utils xinit openbox chromium-browser

Edit /etc/xdg/openbox/autostart and put the following in it;

xset s off
xset s noblank
xset -dpms

setxkbmap -option terminate:ctrl_alt_bksp

sed -i 's/"exited_cleanly":false/"exited_cleanly":true/' ~/.config/chromium/'Local State'
sed -i 's/"exited_cleanly":false/"exited_cleanly":true/; s/"exit_type":"[^"]\+"/"exit_type":"Normal"/' ~/.config/chromium/Default/Preferences

while true ;
	do chromium-browser --fast --fast-start --start-fullscreen --kiosk --noerrdialogs --disable-translate --no-first-run --disable-pinch --overscroll-history-navigation=disabled --disable-features=TouchpadOverscrollHistoryNavigation --disable-restore-session-state --disable-infobars --kiosk '';
	sleep 1;

Edit ~/.bash_profile and put the following in it;

[[ -z $DISPLAY && $XDG_VTNR -eq 1 ]] && startx -- -nocursor

Edit /etc/systemd/system/getty@tty1.service.d/autologin.conf and replace the existing ExecStart line with ExecStart=-/sbin/agetty --skip-login --noclear --noissue --login-options "-f pi" %I $TERM (change pi to your username if not using the default)

To make it a little cleaner, edit /boot/config.txt and add the line disable_splash=1

Edit /boot/cmdline.txt and at the end of the first line add silent quiet splash loglevel=0 logo.nologo vt.global_cursor_default=0 also replace console=tty1 with console=tty3

Lastly, run touch ~/.hushlogin

Reboot your Pi

Reboot the pi and after the normal (hopefully quieter than usual) boot sequence, your slide show will start.

To change the slides, just upload new ones to the dropbox (1920x1080 pixels) and update the corresponding .txt file.