Resilent Plant Floor Dashboards

A plant floor dashboard only earns trust if it’s on the screen every time someone walks by. If the display shows a desktop, a login prompt, or a browser error after a power blip, people stop looking at it. This guide walks through configuring an OnLogic FR201 to boot straight into a full-screen Chromium dashboard and recover on its own, with no keyboard or mouse attached.
Why the FR201
Section titled “Why the FR201”The OnLogic FR201 is an industrial computer built around the Raspberry Pi Compute Module 4. It’s fanless, DIN-rail mountable, accepts industrial power input, has dual ethernet ports (one of which is PoE), a serial interface, M.2 SATA SSD, and handles the temperature swings and dust you’d expect near a production line. Not to mention they are really fun to work with. In other words, it’s a Raspberry Pi you can actually leave on a plant floor for years. Everything in this guide also works on a standard Raspberry Pi 4 if you’re prototyping at your desk.
This setup assumes the device is running Raspberry Pi OS Lite. That’s intentional. The Lite image has no desktop environment, so we install only the minimal X11 stack we need. There’s less to update, less to break, and nothing extra running behind the dashboard.
What You’ll Need
Section titled “What You’ll Need”Before you start, have the following ready:
- An FR201 (or Raspberry Pi 4+) flashed with Raspberry Pi OS Lite (64-bit)
- A network connection that can reach your dashboard URL
- An admin account with sudo access (this guide uses
pi, but use whatever account you created when imaging the device) - The URL of the dashboard you want to display
How It Fits Together
Section titled “How It Fits Together”The boot sequence is simple: systemd starts an X session as a dedicated kiosk user, and that session runs a script that launches Chromium in kiosk mode pointed at your dashboard.
graph LR
A["systemd<br/>kiosk.service"] --> B["startx"]
B --> C[".xinitrc"]
C --> D["kiosk.sh"]
D --> E["Chromium<br/>(kiosk mode)"]
E --> F["Dashboard URL"]
If Chromium crashes or the script exits, systemd restarts the whole session automatically. That’s the piece that makes this reliable enough for a 24/7 operation.
First things first
Section titled “First things first”If you have a new FR201 from OnLogic, you will need to do a few things before you can start configuring it as a kiosk. The device will be configured for Great Britain, which means your timezone, locale, and keyboard layout will be set accordingly. Additionally, SSH isn’t enabled by default. Here is a script that will do the following:
- Set the hostname to
fr201-kiosk - Set the locale to
en_US.UTF-8 - Set the timezone to
America/Chicago - Set the keyboard layout to
us - Enable SSH
- Run the following scripts on the device.
Initial configuration script #!/bin/bash# Set hostnamesudo raspi-config nonint do_hostname fr201-kiosk# Set timezone# If you're in Pacific time, change the last argument to America/Los_Angeles# If you're in Eastern time, change the last argument to America/New_York# If you're in Mountain time, change the last argument to America/Denversudo raspi-config nonint do_change_timezone America/Chicago# Set keyboard layoutsudo raspi-config nonint do_configure_keyboard us# Set localesudo raspi-config nonint do_change_locale en_US.UTF-8# Enable SSHsudo raspi-config nonint do_ssh 0
Set Up SSH Key Access
Section titled “Set Up SSH Key Access”The goal is to run these devices headless on the plant floor, so SSH is how you’ll manage them after deployment. Set up key-based authentication now, before the device leaves your desk. It’s more secure than passwords, and it makes scripted maintenance across multiple devices painless.
-
First, generate a key pair on your machine if you don’t already have one:
Generate an SSH key pair ssh-keygen -t ed25519 -C "fr201-kiosks" -
Accept the default file location and set a passphrase. Don’t forget the passphrase you just set. Then copy the public key to the device:
Copy your public key to the device ssh-copy-id pi@<device-ip>Windows doesn’t ship
ssh-copy-id, but PowerShell gets you the same result:Copy your public key to the device type $env:USERPROFILE\.ssh\id_ed25519.pub | ssh pi@<device-ip> "mkdir -p ~/.ssh && cat >> ~/.ssh/authorized_keys && chmod 700 ~/.ssh && chmod 600 ~/.ssh/authorized_keys" -
Verify the key works by opening a new session. You should land at a shell without a password prompt (you may be asked for your key passphrase instead):
Test key-based login ssh pi@<device-ip> -
Once key login works, disable password authentication so the device only accepts ssh keys:
Disable password authentication sudo nano /etc/ssh/sshd_config -
Scroll down until you see
PasswordAuthenticationand set the following:/etc/ssh/sshd_config PasswordAuthentication no -
Then restart the SSH service:
Restart sshd sudo systemctl restart ssh
Install the Required Packages
Section titled “Install the Required Packages”-
Update the system and install a minimal X11 stack, a lightweight window manager, and Chromium:
Install packages sudo apt updatesudo apt install -y --no-install-recommends \xserver-xorg \xinit \x11-xserver-utils \xserver-xorg-legacy \matchbox-window-manager \chromium-browser \unclutter \xdotool \cec-utilsA quick rundown of the less obvious choices:
matchbox-window-manageris a tiny window manager that does nothing but keep the browser full screen,unclutterhides the mouse cursor after a moment of inactivity,xdotoollets you script keystrokes if you ever need to refresh the page remotely, andcec-utilsgives you HDMI-CEC control so you can turn the attached TV on and off from the command line.
Create the Kiosk User
Section titled “Create the Kiosk User”-
Run the kiosk under a dedicated account rather than your admin user. If the browser session is ever compromised, the blast radius is a locked-down account that can’t sudo.
Create the kiosk user sudo useradd -m -s /bin/bash kiosksudo passwd kiosk -
Create the
.xinitrcfile thatXwill run when the session starts:Create .xinitrc sudo nano /home/kiosk/.xinitrc -
Add:
/home/kiosk/.xinitrc #!/bin/shexec /home/kiosk/kiosk.sh -
Set permissions and ownership:
Set permissions sudo chmod +x /home/kiosk/.xinitrcsudo chown -R kiosk:kiosk /home/kioskThe
chmod +xis important. If.xinitrcisn’t executable, the X session will fail to start and you’ll be left at a blank screen. Thechownensures the kiosk user owns its home directory and all the files in it.
Create the Kiosk Configuration File
Section titled “Create the Kiosk Configuration File”-
Keep configuration separate from logic. The script that launches the browser never changes; the config file holds everything that varies per device or per site. When you deploy to more than one location, this file is the only thing you touch.
Create the config file sudo -u kiosk nano /home/kiosk/kiosk-config.sh -
Add the following content:
/home/kiosk/kiosk-config.sh #!/bin/bash# Kiosk Dashboard Configuration## Everything that varies per device or per site lives here.# The kiosk.sh script sources this file at startup.# The dashboard URL to displayDASHBOARD_URL="https://dashboard.example.com/display/$(hostname)"# Logging configurationLOG_FILE="/home/kiosk/kiosk-startup.log"LOG_ENABLED=trueLOG_PERMISSIONS=664 # Owner read/write, group read/write, others read# Network connectivity test settingsCONNECTIVITY_TEST_TIMEOUT=10CONNECTIVITY_TEST_RETRIES=3# Browser profile cleanup settingsCLEANUP_BROWSER_PROFILE=trueBROWSER_PROFILE_DIR="/tmp/chromium-profile-$(hostname)" -
Make it executable and set ownership:
Set config permissions sudo chmod 755 /home/kiosk/kiosk-config.shsudo chown kiosk:kiosk /home/kiosk/kiosk-config.sh
Create the Kiosk Script
Section titled “Create the Kiosk Script”-
And now for the meat and potatoes. This is the script that runs inside the X session. It configures the display, cleans up any stale browser state, checks that the dashboard is reachable, and launches Chromium in kiosk mode. Everything it does is logged so you can troubleshoot over SSH later.
Create the kiosk script sudo -u kiosk nano /home/kiosk/kiosk.sh -
Add the following content:
/home/kiosk/kiosk.sh #!/bin/bash# Source kiosk configurationSCRIPT_DIR="$(dirname "${BASH_SOURCE[0]}")"if [ -f "$SCRIPT_DIR/kiosk-config.sh" ]; thensource "$SCRIPT_DIR/kiosk-config.sh"elseecho "ERROR: kiosk-config.sh not found in $SCRIPT_DIR"exit 1fi# Function to log messageslog_message() {if [ "$LOG_ENABLED" = true ]; thenecho "$(date '+%Y-%m-%d %H:%M:%S'): $1" >> "$LOG_FILE"chmod "${LOG_PERMISSIONS:-644}" "$LOG_FILE" 2>/dev/nullfiecho "$1"}# Function to test network connectivitytest_connectivity() {local url="$1"local retries="$CONNECTIVITY_TEST_RETRIES"for i in $(seq 1 $retries); doif curl -s --connect-timeout "$CONNECTIVITY_TEST_TIMEOUT" --head "$url" > /dev/null 2>&1; thenreturn 0filog_message "Connectivity test attempt $i failed for $url"sleep 2donereturn 1}# Start logginglog_message "=== Kiosk startup initiated ==="# Display setup: disable power management, screen saver, and blankingxset -dpmsxset s offxset s noblankmatchbox-window-manager -use_titlebar no &unclutter &# Set the display resolution to match your screenxrandr --output HDMI-1 --mode 1920x1080 --rate 60# Clean up browser profile if enabledif [ "$CLEANUP_BROWSER_PROFILE" = true ]; thenlog_message "Cleaning up browser profile data"rm -rf /home/kiosk/.config/chromium/Singleton*rm -rf /home/kiosk/.config/chromium/Default/Singleton*rm -rf "$BROWSER_PROFILE_DIR"filog_message "Dashboard URL: $DASHBOARD_URL"# Test connectivity to dashboardlog_message "Testing connectivity to dashboard..."if test_connectivity "$DASHBOARD_URL"; thenlog_message "Dashboard connectivity test passed"elselog_message "WARNING: Dashboard connectivity test failed - proceeding anyway"fi# Launch browserlog_message "Launching Chromium browser in kiosk mode"chromium-browser \--user-data-dir="$BROWSER_PROFILE_DIR" \--noerrdialogs \--disable-infobars \--kiosk \--window-position=0,0 \"$DASHBOARD_URL"log_message "=== Kiosk session ended ===" -
Just like the
.xinitrcand/home/kioskfolder, make it executable and set the ownership:Set script permissions sudo chmod +x /home/kiosk/kiosk.shsudo chown kiosk:kiosk /home/kiosk/kiosk.shA few notes on the details:
- The
xrandrline assumes a 1080p display on the first HDMI port. Runxrandrwith no arguments from inside a session to list the modes your display actually supports, and adjust to match. - The
Singleton*cleanup removes Chromium’s lock files. If the device loses power mid-session, Chromium leaves these behind and then refuses to start cleanly. Deleting them at startup makes power cuts a non-event. - The connectivity check is a warning, not a gate. If the network is slow coming up, the browser still launches and Chromium retries on its own. You get a log entry either way.
- The
Configure User Groups and Permissions
Section titled “Configure User Groups and Permissions”-
Give the
kioskuser access to the hardware it needs, and add your admin user to thekioskgroup so you can read the startup log without sudo:Configure groups sudo usermod -aG tty,video,input,dialout,audio kiosksudo usermod -a -G kiosk pisudo chmod 750 /home/kioskYou’ll need to log out and back in (or run
newgrp kiosk) before the group change takes effect for your admin user.
Allow Xorg to Start from a Service
Section titled “Allow Xorg to Start from a Service”-
By default, Xorg only starts from a console login. Since systemd will be starting it as the
kioskuser, relax the wrapper configuration:Edit the Xwrapper config sudo nano /etc/X11/Xwrapper.configSet the following:
/etc/X11/Xwrapper.config allowed_users=anybodyneeds_root_rights=yes
Create the systemd Service
Section titled “Create the systemd Service”-
The service ties everything together. It waits for the network, starts X as the
kioskuser, and restarts the session automatically if it ever exits:Create the service file sudo nano /etc/systemd/system/kiosk.serviceAdd this configuration:
/etc/systemd/system/kiosk.service [Unit]Description=Kiosk DashboardAfter=systemd-user-sessions.service network-online.target getty@tty1.serviceWants=network-online.target[Service]User=kioskEnvironment=DISPLAY=:0ExecStart=/usr/bin/startx -- vt1 -keeptty -nocursorStandardOutput=inheritStandardError=inheritRestart=alwaysRestartSec=10[Install]WantedBy=multi-user.targetRestart=alwayswith a ten second delay is the heart of the reliability story. Browser crash, script error, X server failure: whatever goes wrong, the dashboard is back within seconds without anyone touching the device.
Enable and Start the Service
Section titled “Enable and Start the Service”-
Reload systemd, enable the service so it starts at boot, and start it now:
Enable and start sudo systemctl daemon-reloadsudo systemctl enable kiosk.servicesudo systemctl start kiosk.serviceThen reboot to confirm the full boot-to-dashboard path works end to end:
Reboot sudo rebootThe device should come up, sit briefly at the console, and then switch to the full-screen dashboard with no interaction.
Optional: Auto-Login on the Console
Section titled “Optional: Auto-Login on the Console”-
If you also want the console on tty1 to log in as the kiosk user automatically, add a getty override:
Create the override directory sudo mkdir -p /etc/systemd/system/getty@tty1.service.dsudo nano /etc/systemd/system/getty@tty1.service.d/override.confAdd the following content:
/etc/systemd/system/getty@tty1.service.d/override.conf [Service]ExecStart=ExecStart=-/sbin/agetty --autologin kiosk --noclear %I $TERMType=idleReload systemd to pick up the change:
Reload systemd sudo systemctl daemon-reload
Optional: Quick Stop and Restart Shortcuts
Section titled “Optional: Quick Stop and Restart Shortcuts”-
Here’s a small quality-of-life trick for when you’re standing at the device with a keyboard plugged in and you need to get to the terminal and make some changes. Pressing
Alt+F4closes Chromium and drops you to the terminal, but the service relaunches the browser ten seconds later, which is rarely enough time to type a fullsystemctlcommand. These two single-letter scripts solve that problem by allowing you to stop and restart the service with a single keystroke.:Create the s and r shortcuts # Create a shortcut to stop the kiosk service when you press 's' in the terminal and hit Enter.sudo bash -c 'cat > /usr/local/bin/s << EOF#!/bin/bashsudo systemctl stop kiosk.serviceecho "Kiosk service stopped"EOF'# Create a shortcut to restart the kiosk service when you press 'r' in the terminal and hit Enter.sudo bash -c 'cat > /usr/local/bin/r << EOF#!/bin/bashsudo systemctl restart kiosk.serviceecho "Kiosk service restarted"EOF'# Make them executable or they won't work.sudo chmod +x /usr/local/bin/ssudo chmod +x /usr/local/bin/rNow when when you hit
Alt+F4, the browser closes and the terminal appears, typesand pressEnterbefore the service restarts the browser. The service stops and the terminal is yours. Typerwhen you’re done and the dashboard comes right back.
Troubleshooting
Section titled “Troubleshooting”Everything you need to diagnose issues that may arise. And you can do it entirely over SSH.
Check the service status:
sudo systemctl status kiosk.serviceView the service logs from systemd’s journal:
journalctl -u kiosk.service -eView the kiosk startup log written by the script:
# As your admin user (if in the kiosk group):tail -20 /home/kiosk/kiosk-startup.log
# Or with sudo:sudo tail -20 /home/kiosk/kiosk-startup.logCheck the Xorg log if the display never comes up:
cat /home/kiosk/.local/share/xorg/Xorg.0.logVerify file permissions if the session starts but the script fails:
ls -la /home/kiosk/Confirm the configuration file loads correctly:
cd /home/kiosksudo -u kiosk bash -c 'source ./kiosk-config.sh && echo "URL: $DASHBOARD_URL" && echo "Logging: $LOG_ENABLED"'Wrap-Up
Section titled “Wrap-Up”You now have an FR201 that boots straight into a full-screen dashboard, restarts itself after crashes and power outages, and can be managed entirely over SSH with key authentication. The pieces worth remembering:
- A dedicated, unprivileged
kioskuser runs the browser session - Configuration lives in
kiosk-config.sh, so per-device changes never touch the logic - systemd’s
Restart=alwayshandles recovery without human intervention - SSH keys mean you never type a password to a plant floor device again
If you’re rolling this out across multiple sites, image one device, verify it, and clone the image. The only file that changes per device is the config file, and if your dashboard serves per-device views by hostname, even that stays identical.
Get the Code
Section titled “Get the Code”Everything above is in a companion repo. If you’re new to linux and the terminal, going step by step is a great way to learn. If you’re comfortable with the command line, cloning the repo and running the installers is faster than copying snippets.:
git clone https://github.com/jakeashcraft/plant-floor-dashboard.gitcd plant-floor-dashboard
# at the device, keyboard attachedsudo ./first-boot.sh
# then over SSHsudo ./install.sh --url 'https://dashboard.example.com/display/$(hostname)'Every flag defaults to the value used in this guide, so a bare sudo ./install.sh reproduces exactly what you just read. Both scripts take --dry-run if you’d rather see what they touch before running on your device. The installer also detects the Chromium package name for you, which is the one difference between a Bullseye and a Bookworm image that will otherwise stop you cold.
See Also
Section titled “See Also”External references: