“Public display mode“ for embedded boards
I've been working on information displays for quite a while and one major feature of these displays is that they are required to display as little as possible of the underlying system they are using.
No one wants to see a BSOD, a boot sequence or any kind of error message pop up on a large public screen.
So how do we do that ?
As we rely on different software layers to display the content we really want to display (say, a webpage), we have to make sure that every one of them is not verbose so the user experience is controlled.
In the following article I will focus on two boards : the Raspberry Pi B 3 (ARM) and the Upboard (Intel-based)
The Pi has a standard raspbian jessie lite (should work with stretch, too) on it, based on debian jessie. On the upboard, I decided to go the Ubuntu Server route, since ubilinux (the distribution that upboard proposes) doesn't have the same community yet. I felt a simple 16.04 was more adequate.
I didn't install the linux-upboard kernel flavour on the upboard since I didn't need the GPIO support but I guess this should be harmless for our use case here
Silent boot
First things first : the boot sequence.
On recent systems, it's generally quite easy to get rid of all the output at boot.
Raspberry Pi
A few steps are required to have a perfectly blank screen.
Mute locale warnings by setting the locale in /etc/environmnent
(you can choose any installed locale):
LC_ALL=en_GB.UTF-8
LANG=en_GB.UTF-8
Make sure date and time are correctly configured, to avoid timezone warnings :
sudo dpkg-reconfigure tzdata
Add this in /boot/config.txt
:
disable_splash=1
avoid_warnings=1
And change your /boot/cmdline.txt
to something along those lines:
dwc_otg.lpm_enable=0 root=/dev/mmcblk0p2 rootfstype=ext4 elevator=deadline fsck.repair=yes rootwait logo.nologo console=null quiet vt.global_cursor_default=0
The main part is
logo.nologo
,console=null
andquiet
. You should remove the part about the serial console too
Upboard
It's a bit simpler if you use GRUB as the bootloader.
Edit /etc/default/grub
and add :
GRUB_CMDLINE_LINUX_DEFAULT="quiet loglevel=0 logo.nologo console=tty12 --quiet"
GRUB_CMDLINE_LINUX="quiet loglevel=0 logo.nologo console=tty12 --quiet" #Don't show kernel text
GRUB_HIDDEN_TIMEOUT=0
And then run sudo update-grub2
.
This will disable the GRUB bootloader menu, and remove all text logs from the screen (hopefully). It appears that some errors may still show but I guess they can be specifically removed by disabling the correct modules or so.
Disable login screen
Next, you want to disable the login screen on tty1 so no prompt appears after the boot :
sudo systemctl disable getty@tty1
This is valid for both platforms.
That's it ! You should have a perfect black screen all the way, and no output whatsoever (via HDMI, at least).
Displaying things
What's the best way to get rid of any kind of error messages ? Well, how about having no window manager in the first place ?
In this scenario, we want to display a single app : a web browser, and expect no user interaction whatsoever.
It makes sense to only use a display server for this application, make it fullscreen, and call it a day.
This allows for a clutter-free screen, no error messages (at least from the X server) and a minimal installation footprint.
And it everything crashes, you end up with a black screen by construction which is quite a good fallback (except if your web app crashes with a 500 or displays giberrish, but that's out of the scope here).
In the following installation, I will configure the display manager to automatically start our chosen browser fullscreen with the relevant options.
Installation
First, some packages are necessary : xorg
, xserver-legacy
and xinit
:
sudo apt-get update
sudo apt-get install build-essential unclutter xorg xinit xserver-xorg-legacy -y
unclutter
hides the mouse cursor, it's a neat little utility.
Configuration
Then run this to allow anybody to start X (you don't want to run it as root):
sudo dpkg-reconfigure xserver-xorg-legacy
You may want to add the user to the tty group too :
sudo usermod -a -G tty {yourUserName}
Creating necessary X startup files
Create /home/{yourUserName}/.xserverrc
and add the below content:
#!/bin/sh
# Start an X server with power management disabled so that the screen never goes blank
exec /usr/bin/X -s 0 -dpms -nolisten tcp "$@"
This will allow to start X with the display sleep management shut off, so your display is always on.
Then, to automatically start the browser when X launches, create /home/{yourUserName}/.xsession
and add the below content:
#!/bin/sh
exec /usr/bin/chromium-browser %u \
--kiosk --start-fullscreen --incognito \
--no-first-run \
--disable-session-crashed-bubble \
--disable-infobars \
--disable-restore-background-contents \
--disable-translate \
--disable-new-tab-first-run \
--noerrdialogs \
--no-sandbox \
--user-data-dir=/tmp \
--window-size=1600,900 \
http://localhost:3000
Change the url to match yours, of course, and even if the "start-fullscreen" flag is on, it seems to be a bit buggy since we don't have a window manager, so I add the actual resolution of the screen to be sure the window takes the full width and height.
There is a load of options here, that I carefully tested. To my opinion this is the best flag list for this use. Do not hesitate if you have any other input or improvement over this !
As well, we're assuming we're using chromium here, but you can replace that with
/usr/bin/google-chrome
. See below for the two options
You can then run directly the whole thing with :
/usr/bin/startx -- /home/admin/.xserverrc -nocursor
or make a unit file to start on boot directly via systemd :
[Unit]
Description=Public Display device
After=network-online.target
Before=multi-user.target
DefaultDependencies=no
[Service]
User={yourUserName}
ExecStartPre=/bin/sleep 5
ExecStart=/usr/bin/startx -- /home/{yourUserName}/.xserverrc -nocursor
Restart=always
RestartSec=10
[Install]
WantedBy=multi-user.target
Now, for the choice of browser !
Using Chromium
There is apparently a bug in Chromium (a bit related to https://github.com/RPi-Distro/repo/issues/58 but not exactly) that makes the latest versions crash when runnning in kiosk mode on embedded devices. This seems to be linked to starting Chrome in Xorg directly.
The last known to work version is 48.0.2564.82 afaik, so we need to install this manually.
First remove the previous packages if any :
sudo apt-get remove chromium-codecs-ffmpeg-extra chromium-browser chromium-browser-l10n
And install the correct version. There are two scenarii, one for the Pi (ARM based), and one for the Upboard.
Raspberry Pi
Get the packages :
wget https://launchpad.net/~canonical-chromium-builds/+archive/ubuntu/stage/+build/8883797/+files/chromium-codecs-ffmpeg-extra_48.0.2564.82-0ubuntu0.15.04.1.1193_armhf.deb https://launchpad.net/~canonical-chromium-builds/+archive/ubuntu/stage/+build/8883797/+files/chromium-browser_48.0.2564.82-0ubuntu0.15.04.1.1193_armhf.deb http://launchpadlibrarian.net/234938396/chromium-browser-l10n_48.0.2564.82-0ubuntu0.15.04.1.1193_all.deb https://launchpad.net/~ubuntu-security/+archive/ubuntu/ppa/+build/8993250/+files/libgcrypt11_1.5.3-2ubuntu4.3_armhf.deb
Install them :
sudo dpkg -i libgcrypt11_1.5.3-2ubuntu4.3_armhf.deb
sudo dpkg -i chromium-codecs-ffmpeg-extra_48.0.2564.82-0ubuntu0.15.04.1.1193_armhf.deb
sudo dpkg -i chromium-browser_48.0.2564.82-0ubuntu0.15.04.1.1193_armhf.deb
sudo dpkg -i chromium-browser-l10n_48.0.2564.82-0ubuntu0.15.04.1.1193_all.deb
? BOOM. Done.
Upboard
Get the packages :
wget http://launchpadlibrarian.net/234938404/chromium-browser_48.0.2564.82-0ubuntu0.15.04.1.1193_amd64.deb http://launchpadlibrarian.net/234938396/chromium-browser-l10n_48.0.2564.82-0ubuntu0.15.04.1.1193_all.deb http://launchpadlibrarian.net/234938406/chromium-codecs-ffmpeg-extra_48.0.2564.82-0ubuntu0.15.04.1.1193_amd64.deb
Install them :
sudo dpkg -i chromium-codecs-ffmpeg-extra_48.0.2564.82-0ubuntu0.15.04.1.1193_amd64.deb
sudo dpkg -i chromium-browser_48.0.2564.82-0ubuntu0.15.04.1.1193_amd64.deb
At some point you might need to correct dependencies because the adm64 version depends on a bit more things apparently :
sudo apt install -f
? BOOM. Done.
Holding packages
Don't forget to block updates on chromium packages :
sudo apt-mark hold chromium-codecs-ffmpeg-extra chromium-browser chromium-browser-l10n
To unhold the packages :
sudo apt-mark unhold chromium-codecs-ffmpeg-extra chromium-browser chromium-browser-l10n
Using Google Chrome
Add a new repo list file: sudo vi /etc/apt/sources.list.d/google-chrome.list
with the following content :
deb [arch=amd64] http://dl.google.com/linux/chrome/deb/ stable main
Add the Google signing key :
wget -q -O - https://dl-ssl.google.com/linux/linux_signing_key.pub | sudo apt-key add -
And then install Chrome:
sudo apt update && sudo apt install google-chrome-stable
It only works on Intel-based architectures as far as I could test, so Chromium is the only option for now on Raspberry Pi
All good
So far, so good. I have tested this on a stock Raspberry Pi B3 and an Upboard and it runs smoothly. Crashes have happened, and
But maybe I have missed some options / interesting additions ? Do not hesitate to tell me in the comments.