“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.

oops

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 and quiet. 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.