Work / kiosk
kiosk
Raspberry Pi 4B kiosk system — CW-2400 check-weigher dashboard. Docs, preflight checklist, and all WiFi/display stability fixes.
Stack
checkweigher · industrial-iot · kiosk · plc · raspberry-pi
Source on GitHub ↗ · Talk to me →
Readme
Raspberry Pi 4B kiosk running the CW-2400 check-weigher dashboard at Sai Samarth Engineering.
This repository is the system runbook: the full configuration, every stability fix applied, and the procedures to verify or restore it.
Contents
- System overview
- Hardware
- Software stack
- Network configuration
- WiFi stability fixes
- Display and kiosk startup fixes
- Current state
- Re-enabling the kiosk
- Troubleshooting
- File index
1. System overview
The Pi runs a full-screen kiosk showing the CW-2400 check-weigher dashboard. It serves a local web app from a Python HTTP server on port 8765 and displays it in Chromium kiosk mode under the Wayland compositor labwc, managed by lightdm with auto-login.
Hard requirement: the machine must be reachable over SSH at 192.168.0.212 whether or not the display is connected. Most of the fixes below exist to guarantee that.
2. Hardware
3. Software stack
4. Network configuration
Static IP on wlan0
wlan0 is pinned to a static address so SSH is always at the same place:
IP: 192.168.0.212/24
Gateway: 192.168.0.1
DNS: 8.8.8.8, 8.8.4.4
Metric: 100Set in /etc/NetworkManager/system-connections/sai @samarth.nmconnection:
[ipv4]
method=manual
address1=192.168.0.212/24,192.168.0.1
dns=8.8.8.8;8.8.4.4;
route-metric=100WiFi priority
IPv6
Disabled on wlan0 (method=disabled) to avoid address churn that can confuse SSH sessions.
5. WiFi stability fixes
These resolve WiFi slowness and SSH disconnects that occurred when the USB touchscreen or HDMI display was connected.
Fix 1 — WiFi power save disabled
Problem: BCM4345/6 power save puts the chip to sleep between packets. USB HID events from the touchscreen interrupt the sleep/wake cycle, causing latency spikes of several hundred milliseconds.
/etc/NetworkManager/NetworkManager.conf
[connection]
wifi.powersave=2 # 2 = always off, 3 = always on
[device]
wifi.scan-rand-mac-address=no # stable MAC during background scans/etc/NetworkManager/system-connections/sai @samarth.nmconnection (and D-link)
[wifi]
powersave=2/etc/NetworkManager/dispatcher.d/99-wifi-powersave-off
#!/bin/bash
# Re-enforce power save off every time wlan0 comes up (survives reconnects)
[ "$1" = "wlan0" ] && [ "$2" = "up" ] && iw dev wlan0 set power_save offVerify: iw dev wlan0 get power_save must print Power save: off.
Fix 2 — brcmfmac background roaming scans disabled
Problem: The driver runs background roaming scans roughly every 30 s. Each scan costs about 100 ms of packet loss.
/etc/modprobe.d/brcmfmac.conf
options brcmfmac roamoff=1Takes effect on the next reboot, or after sudo rmmod brcmfmac && sudo modprobe brcmfmac.
Fix 3 — USB device power management
Problem: USB autosuspend on the WaveShare touchscreen caused repeated connect/disconnect cycles (visible in dmesg). Each disconnect triggers a USB bus reset that can briefly stall other USB-adjacent drivers.
usbcore.autosuspend=-1 was already set globally in /boot/firmware/cmdline.txt; these device-specific udev rules add a second layer.
/etc/udev/rules.d/99-waveshare-usb.rules
# WaveShare WS170120 touchscreen
ACTION=="add", SUBSYSTEM=="usb", ATTRS{idVendor}=="0eef", ATTRS{idProduct}=="0005",
TEST=="power/autosuspend", ATTR{power/autosuspend}="-1"
ACTION=="add", SUBSYSTEM=="usb", ATTRS{idVendor}=="0eef", ATTRS{idProduct}=="0005",
TEST=="power/control", ATTR{power/control}="on"
# VIA Labs USB 2.0 Hub
ACTION=="add", SUBSYSTEM=="usb", ATTRS{idVendor}=="2109", ATTRS{idProduct}=="3431",
TEST=="power/control", ATTR{power/control}="on"Fix 4 — static IP applied live
After editing the NetworkManager profile, a reconnect is needed to switch from DHCP-assigned to fully static:
sudo nmcli con reload
sudo nmcli con up "sai @samarth"Verify: ip addr show wlan0 — the word dynamic must not appear next to the IP.
6. Display and kiosk startup fixes
These resolve SSH failure when HDMI and the USB touchscreen are connected at boot.
Fix 5 — lightdm waits for the network
Problem: lightdm.service had no dependency on network-online.target. Booting with the display connected, lightdm auto-logged in and launched labwc and Chromium while the WiFi driver was still associating. Chromium's GPU process (60% CPU) and network service immediately flooded the still-initialising link, dropping SSH.
/etc/systemd/system/lightdm.service.d/wait-for-network.conf
[Unit]
After=network-online.target
Wants=network-online.targetApply with sudo systemctl daemon-reload. Resulting boot sequence:
kernel → udev → NetworkManager → [WiFi associates + gets IP] → network-online.target → lightdm → labwc → appsFix 6 — Chromium kiosk removed from autostart
Problem: Chromium at boot consumed roughly 60% CPU (GPU process) plus 48% CPU (renderer) — about 110% of one core — starving the WiFi interrupt handler. Combined with the race condition in Fix 5, this made SSH impossible when the display was plugged in at boot.
Status: disabled. The Chromium block in ~/.config/labwc/autostart is commented out and ~/.config/systemd/user/kiosk-server.service is disabled. See section 8 to restore.
Fix 7 — touch device mapped to the correct output
Problem: rc.xml mapped the touchscreen to NOOP-1, a non-existent output. Every session start, labwc tried to apply the mapping, failed, and retried — producing continuous compositor errors and spurious CPU load.
~/.config/labwc/rc.xml
<!-- Before (wrong): -->
<touch deviceName="WaveShare WS170120" mapToOutput="NOOP-1" mouseEmulation="yes"/>
<!-- After (correct): -->
<touch deviceName="WaveShare WS170120" mapToOutput="HDMI-A-2" mouseEmulation="yes"/>HDMI-A-2 matches both the kanshi output profile and the physical connection.
Fix 8 — kanshi display profile populated
Problem: ~/.config/kanshi/config was empty (0 bytes). The display profile existed only in config.init and was never applied, so resolution and refresh rate were not set deterministically at login.
~/.config/kanshi/config
profile {
output HDMI-A-2 enable scale 1.000000 mode 1280x800@63.038 position 0,0 transform normal
}7. Current state
8. Re-enabling the kiosk
Step 1 — start the backend
systemctl --user enable --now kiosk-server.service
curl http://127.0.0.1:8765/api/status # verifyStep 2 — re-enable Chromium
Edit ~/.config/labwc/autostart and uncomment the Chromium block — remove the # from each line of the lwrespawn /usr/bin/chromium ... section.
Step 3 — restart the session
loginctl terminate-session $XDG_SESSION_ID # or simply reboot9. Troubleshooting
SSH unreachable after boot with the display connected
iw dev wlan0 get power_save # must be: off
ip addr show wlan0 # must not say: dynamic
systemctl cat lightdm.service | grep After # must include network-online.target
ps aux | grep chromium # should not be running
nmcli -f autoconnect-priority,name,state con show # check prioritiesWiFi drops when the touchscreen USB is plugged in
Read the full README on GitHub →
Thoughts about this
Logging Mitsubishi PLC Data in Real Time on a Raspberry Pi
How I bridge a Mitsubishi PLC to a Raspberry Pi for continuous, reconnect-safe register polling — and why the edge is the right place to do it. Industrial IoT · May 2024 · 8 min read
Building a High-Speed Check-Weigher with IR and Motion Sensors
Capturing a stable weight from a product moving at speed is mostly a timing problem. Here's how IR and motion sensors solve it. Embedded Hardware · Apr 2024 · 10 min read
Pushing Files Over SMB from a Headless Raspberry Pi
A small but load-bearing detail of factory IoT: getting generated files onto the network share reliably, from a device with no screen. Edge Computing · Feb 2024 · 5 min read
More work
plc-checkweigher
One-command installer for the Mitsubishi PLC check-weigher system. Python · Raspberry Pi · Mitsubishi PLC · PDF Reports · npm CLI
checkweigher
High-speed industrial check-weigher on the edge. Python · Raspberry Pi 4 · XT1000 · T16 Load Cell
Tovex-CRM
Modular CRM platform with real-time dashboards. TypeScript · TypeScript · Modular Architecture · Real-time Dashboards