Skip to content

Resilent Plant Floor Dashboards

Plant Floor dashboards on an OnLogic FR201

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.

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.

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

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.

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
  1. Run the following scripts on the device.
    Initial configuration script
    #!/bin/bash
    # Set hostname
    sudo 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/Denver
    sudo raspi-config nonint do_change_timezone America/Chicago
    # Set keyboard layout
    sudo raspi-config nonint do_configure_keyboard us
    # Set locale
    sudo raspi-config nonint do_change_locale en_US.UTF-8
    # Enable SSH
    sudo raspi-config nonint do_ssh 0

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.

  1. 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"
  2. 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>
  3. 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>
  4. Once key login works, disable password authentication so the device only accepts ssh keys:

    Disable password authentication
    sudo nano /etc/ssh/sshd_config
  5. Scroll down until you see PasswordAuthentication and set the following:

    /etc/ssh/sshd_config
    PasswordAuthentication no
  6. Then restart the SSH service:

    Restart sshd
    sudo systemctl restart ssh
  1. Update the system and install a minimal X11 stack, a lightweight window manager, and Chromium:

    Install packages
    sudo apt update
    sudo apt install -y --no-install-recommends \
    xserver-xorg \
    xinit \
    x11-xserver-utils \
    xserver-xorg-legacy \
    matchbox-window-manager \
    chromium-browser \
    unclutter \
    xdotool \
    cec-utils

    A quick rundown of the less obvious choices: matchbox-window-manager is a tiny window manager that does nothing but keep the browser full screen, unclutter hides the mouse cursor after a moment of inactivity, xdotool lets you script keystrokes if you ever need to refresh the page remotely, and cec-utils gives you HDMI-CEC control so you can turn the attached TV on and off from the command line.

  1. 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 kiosk
    sudo passwd kiosk
  2. Create the .xinitrc file that X will run when the session starts:

    Create .xinitrc
    sudo nano /home/kiosk/.xinitrc
  3. Add:

    /home/kiosk/.xinitrc
    #!/bin/sh
    exec /home/kiosk/kiosk.sh
  4. Set permissions and ownership:

    Set permissions
    sudo chmod +x /home/kiosk/.xinitrc
    sudo chown -R kiosk:kiosk /home/kiosk

    The chmod +x is important. If .xinitrc isn’t executable, the X session will fail to start and you’ll be left at a blank screen. The chown ensures the kiosk user owns its home directory and all the files in it.

  1. 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
  2. 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 display
    DASHBOARD_URL="https://dashboard.example.com/display/$(hostname)"
    # Logging configuration
    LOG_FILE="/home/kiosk/kiosk-startup.log"
    LOG_ENABLED=true
    LOG_PERMISSIONS=664 # Owner read/write, group read/write, others read
    # Network connectivity test settings
    CONNECTIVITY_TEST_TIMEOUT=10
    CONNECTIVITY_TEST_RETRIES=3
    # Browser profile cleanup settings
    CLEANUP_BROWSER_PROFILE=true
    BROWSER_PROFILE_DIR="/tmp/chromium-profile-$(hostname)"
  3. Make it executable and set ownership:

    Set config permissions
    sudo chmod 755 /home/kiosk/kiosk-config.sh
    sudo chown kiosk:kiosk /home/kiosk/kiosk-config.sh
  1. 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
  2. Add the following content:

    /home/kiosk/kiosk.sh
    #!/bin/bash
    # Source kiosk configuration
    SCRIPT_DIR="$(dirname "${BASH_SOURCE[0]}")"
    if [ -f "$SCRIPT_DIR/kiosk-config.sh" ]; then
    source "$SCRIPT_DIR/kiosk-config.sh"
    else
    echo "ERROR: kiosk-config.sh not found in $SCRIPT_DIR"
    exit 1
    fi
    # Function to log messages
    log_message() {
    if [ "$LOG_ENABLED" = true ]; then
    echo "$(date '+%Y-%m-%d %H:%M:%S'): $1" >> "$LOG_FILE"
    chmod "${LOG_PERMISSIONS:-644}" "$LOG_FILE" 2>/dev/null
    fi
    echo "$1"
    }
    # Function to test network connectivity
    test_connectivity() {
    local url="$1"
    local retries="$CONNECTIVITY_TEST_RETRIES"
    for i in $(seq 1 $retries); do
    if curl -s --connect-timeout "$CONNECTIVITY_TEST_TIMEOUT" --head "$url" > /dev/null 2>&1; then
    return 0
    fi
    log_message "Connectivity test attempt $i failed for $url"
    sleep 2
    done
    return 1
    }
    # Start logging
    log_message "=== Kiosk startup initiated ==="
    # Display setup: disable power management, screen saver, and blanking
    xset -dpms
    xset s off
    xset s noblank
    matchbox-window-manager -use_titlebar no &
    unclutter &
    # Set the display resolution to match your screen
    xrandr --output HDMI-1 --mode 1920x1080 --rate 60
    # Clean up browser profile if enabled
    if [ "$CLEANUP_BROWSER_PROFILE" = true ]; then
    log_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"
    fi
    log_message "Dashboard URL: $DASHBOARD_URL"
    # Test connectivity to dashboard
    log_message "Testing connectivity to dashboard..."
    if test_connectivity "$DASHBOARD_URL"; then
    log_message "Dashboard connectivity test passed"
    else
    log_message "WARNING: Dashboard connectivity test failed - proceeding anyway"
    fi
    # Launch browser
    log_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 ==="
  3. Just like the .xinitrc and /home/kiosk folder, make it executable and set the ownership:

    Set script permissions
    sudo chmod +x /home/kiosk/kiosk.sh
    sudo chown kiosk:kiosk /home/kiosk/kiosk.sh

    A few notes on the details:

    • The xrandr line assumes a 1080p display on the first HDMI port. Run xrandr with 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.
  1. Give the kiosk user access to the hardware it needs, and add your admin user to the kiosk group so you can read the startup log without sudo:

    Configure groups
    sudo usermod -aG tty,video,input,dialout,audio kiosk
    sudo usermod -a -G kiosk pi
    sudo chmod 750 /home/kiosk

    You’ll need to log out and back in (or run newgrp kiosk) before the group change takes effect for your admin user.

  1. By default, Xorg only starts from a console login. Since systemd will be starting it as the kiosk user, relax the wrapper configuration:

    Edit the Xwrapper config
    sudo nano /etc/X11/Xwrapper.config

    Set the following:

    /etc/X11/Xwrapper.config
    allowed_users=anybody
    needs_root_rights=yes
  1. The service ties everything together. It waits for the network, starts X as the kiosk user, and restarts the session automatically if it ever exits:

    Create the service file
    sudo nano /etc/systemd/system/kiosk.service

    Add this configuration:

    /etc/systemd/system/kiosk.service
    [Unit]
    Description=Kiosk Dashboard
    After=systemd-user-sessions.service network-online.target getty@tty1.service
    Wants=network-online.target
    [Service]
    User=kiosk
    Environment=DISPLAY=:0
    ExecStart=/usr/bin/startx -- vt1 -keeptty -nocursor
    StandardOutput=inherit
    StandardError=inherit
    Restart=always
    RestartSec=10
    [Install]
    WantedBy=multi-user.target

    Restart=always with 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.

  1. Reload systemd, enable the service so it starts at boot, and start it now:

    Enable and start
    sudo systemctl daemon-reload
    sudo systemctl enable kiosk.service
    sudo systemctl start kiosk.service

    Then reboot to confirm the full boot-to-dashboard path works end to end:

    Reboot
    sudo reboot

    The device should come up, sit briefly at the console, and then switch to the full-screen dashboard with no interaction.

  1. 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.d
    sudo nano /etc/systemd/system/getty@tty1.service.d/override.conf

    Add the following content:

    /etc/systemd/system/getty@tty1.service.d/override.conf
    [Service]
    ExecStart=
    ExecStart=-/sbin/agetty --autologin kiosk --noclear %I $TERM
    Type=idle

    Reload 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”
  1. 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+F4 closes Chromium and drops you to the terminal, but the service relaunches the browser ten seconds later, which is rarely enough time to type a full systemctl command. 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/bash
    sudo systemctl stop kiosk.service
    echo "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/bash
    sudo systemctl restart kiosk.service
    echo "Kiosk service restarted"
    EOF'
    # Make them executable or they won't work.
    sudo chmod +x /usr/local/bin/s
    sudo chmod +x /usr/local/bin/r

    Now when when you hit Alt+F4, the browser closes and the terminal appears, type s and press Enter before the service restarts the browser. The service stops and the terminal is yours. Type r when you’re done and the dashboard comes right back.

Everything you need to diagnose issues that may arise. And you can do it entirely over SSH.

Check the service status:

Service status
sudo systemctl status kiosk.service

View the service logs from systemd’s journal:

Service logs
journalctl -u kiosk.service -e

View the kiosk startup log written by the script:

Startup log
# 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.log

Check the Xorg log if the display never comes up:

Xorg log
cat /home/kiosk/.local/share/xorg/Xorg.0.log

Verify file permissions if the session starts but the script fails:

Check permissions
ls -la /home/kiosk/

Confirm the configuration file loads correctly:

Test config loading
cd /home/kiosk
sudo -u kiosk bash -c 'source ./kiosk-config.sh && echo "URL: $DASHBOARD_URL" && echo "Logging: $LOG_ENABLED"'

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 kiosk user runs the browser session
  • Configuration lives in kiosk-config.sh, so per-device changes never touch the logic
  • systemd’s Restart=always handles 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.

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

Clone and install
git clone https://github.com/jakeashcraft/plant-floor-dashboard.git
cd plant-floor-dashboard
# at the device, keyboard attached
sudo ./first-boot.sh
# then over SSH
sudo ./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.

External references: