Web Kiosk with a Raspberry Pi
Background
On a recent project, one of my co-workers and I were tasked with creating some kind of page to be displayed on TVs located by the elevators in our building. The page needed to display the following:
- A rotating list of floors, and what / who was on each floor.
- A rotating list of news items for the company
The page would need to be managed by an administrator. The administrator would need to be able to perform the standard CRUD operations on both the floors and the news items.
I won't go into details on the actual application part of this, just know that was built as a SPA using Ember, Node (with Express), and Mongo.
We decided to host the SPA on a Mac Mini, and display the SPA on each TV using a Raspberry Pi (one per TV). I did some work on the application, but was mainly tasked with getting the Raspberry Pis up and running.
This post will detail all of the work I had to do in order to make this work nicely. If you're tasked with something similar, I hope this post will save you some time and frustration.
The Goal
My initial goals for our Raspberry Pi Kiosk setup were:
- On boot, display a terminal with 'credits' to the creators (myself, co-worker, and designer)
- Open a web page, full screen, pointing to our SPA
Seems simple enough right?
Wrong.
Wrong because of all the little nuances that I ran into; some because of our internal network setup, some because of how the Raspberry Pi functioned and how we wanted the end product to look.
Here are the steps I took to make everything work.
Startup Script
I created a startup script (startup.sh
) and included it in the source control for the project. This startup script did the following:
- Turn off mouse cursor (detailed later)
- Displayed credits (just some ASCII text) to show who contributed to the project.
- Do an initial pulse check to make sure that the SPA was up, running, and reachable
- Set the date and time (we'll get to the reasoning for this later)
- Launch a browser pointing to the SPA
Here is the script:
# Turn off mouse cursor
unclutter &
# ASCII Text Credits
# Removed because...well do you really care?
DOMAIN="http://our.domain.com:3000"
# Check to see if the server is up
# Start with a 15 second interval, and double it
# until we reach 2 minutes.
sleepTime=$((15))
while [ 1 ] ; do
curl -Is "$DOMAIN" > /dev/null
re=$?
if [ $re -eq 0 ] ; then
echo "Successfully contacted web server!"
break
fi
printf "Connection failed, trying again in %d seconds.\n" $sleepTime
sleep $sleepTime
sleepTime=$((sleepTime * 2))
if [ $sleepTime -ge 120 ] ; then
sleepTime=$((120))
fi
done
# Server was contacted successfully, continue by setting the date and time
DATE=`curl -s "$DOMAIN"/date`
TIME=`curl -s "$DOMAIN"/time`
sudo date --set "$DATE" > /dev/null
sudo date --set "$TIME" > /dev/null
# Kick off chromium in incognito and kisok mode,
# pointing to the SPA
chromium --incognito --kiosk "$DOMAIN"
You can read through the script and get the idea of what's going on. The pulse check simply tries to contact the SPA and, if not, will check again after a set interval. The interval starts out at 15 seconds, then doubles after every failure, up to a max of 2 minutes. This was in an attempt to minimize how many times we were trying to reach out to the server. If for some reason we made it to 2 minutes, it would just have to wait until somebody got there in the morning to troubleshoot the issue.
Running The Script
There are many ways to launch a script on boot / login in Linux, however some of them don't work on Raspbian (the OS we used). In the end I decided to launch the startup script on login. I modified ~.bashrc
to run the script:
sh /path/to/script/startup.sh
Then, I modified autostart
to launch the terminal and, consequently, my startup script. In Raspbian, the autostart
file is located at /etc/xdg/lxsession/LXDE
. I added the following to the autostart
file:
@lxterminal --geometry=230x65
The --geometry
flag is used to specify the size of the terminal window. We were using an HD TV (1920x1080), and this setting of --geometry=230x65
got us pretty close to full-screen.
Disable Mouse Cursor
If you look online, you'll find a ton of different methods on disabling the mouse cursor in Linux. Some of them work in Raspbian, others don't. The method I ended up using was installing a program called 'unclutter' (sudo apt-get install unclutter
). You can look at the setup script above and see that it's called via unclutter &
.
Disable Screen Blanking
We didn't encounter this until we actually hooked the Raspberry Pis up to the TVs. We hooked up the first one, went to the next floor to hook up another, and when we came back up later the screen was black.
If you look online, you will find numerous forum posts / blog posts about how to fix this issue in Linux. You may even find some posts claiming it fixes the issue on a Raspberry Pi. I was unable to find a single post that would fix this issue though. In the end, it was a mix of a couple different articles that solved my screen blanking problem.
I modified /etc/X11/xinit/xinitrc
and added the following lines (after the first line that was already there):
xset s off # Don't activate the screensaver
xset -dpms # Disable DPMS (Energy Star) features
xset s noblank # Don't blank the video device
Additionally, I modified the X server startup process. In /etc/lightdm/lightdm.conf
, I added the following line, directly below [SeatDefaults]
:
xserver-command=X -s 0 -dpms
Company Issue - Setting Time
This issue was specific to my company's network setup, so feel free to skip over this section (unless you happen to have the same issue I'm about to explain). At my company, our network is pretty locked down. Not a whole lot of outbound traffic is allowed, including NTP. This meant that I was not able to properly set the time on the Raspberry Pis. Setting the time was important, because we wanted the Pis to restart overnight, every night, just so that they had a fresh start each morning.
In order to get around this problem, I had my co-worker provide an API endpoint in our application that would serve up the date and time. If you look back up in my startup.sh
file, you'll see close to the bottom where I'm calling those API endpoints and setting the system date and time with the results.
Rebooting Overnight
As I said above, we wanted to reboot the Pis overnight, so that we didn't overload the devices too much and had a fresh start every morning. Maybe we could have gotten away with once a week, or maybe this isn't necessary at all. Regardless, I'm going to show you how to do this with a cronjob.
I modified my crontab with sudo crontab -e
and put in the following:
# Log the shutdown right before it happens (at 1:59 AM every day)
59 1 * * * echo "Getting ready to reboot on $(date) as $(whoami)" >> /var/log/applicationCron.log
# Reboot the Pi at 2:00 AM every day
0 2 * * * sudo /sbin/shutdown now -r
# Clear the logfile every Sunday at 1:30 AM
30 1 * * 0 sudo rm /var/log/applicationCron.log
Wrap-Up
In all, this was a pretty interesting experience. A lot more effort went into getting the Raspberry Pi configured correctly than I had originally anticipated. Needless to say, I immediately created a disk image of the final result so I could flash new SD cards at will. No way was I going back through that process again.
I hope that this post was informative and, if you have to do something similar with a Raspberry Pi, proves to be helpful to you.
As always, if you have any questions / comments, don't hesitate to drop me a line on twitter (@awgreenarrow), shoot me an e-mail at andrew@greenarrow.me, or write your own blog post and let me know about it via one of the first two methods!