FreeBSD "kiosk" for home automation dashboard

My current, FreeBSD based server for self-hosting and home automation - now with a touchscreen!

Background

For quite a few years now I have been looking for a small touchscreen, because I wanted a local (no network involved) interface for my home automation server. I tried the following which didn't work:

  • A Raspberry Pi (3b) with the official touchscreen. The official screen is 800*600, which is sad in 2024 (it was sad in the early 2000s already), and I ended up with severe realibility issues due to the horrors of the Pi's USB architecture and the Sonoff Zigbee adapter I'm using.
  • Cheap, Chinese, HDMI display: 1024*600, without any decent feature of a display, like power management: it just kept telling me there's no signal without going to sleep.
  • Mimo displays - driver horrors on linux, I haven't had that for decades. They are not useful as a general displays at all becase they need the OS to boot first, so no picture until the OS boots.
  • ELO POS terminal (1215L) which, apart from being way too large and heavy, also needs it's own, picky, horribly drivers for the touch part to work. It's also ancient, with a foil based touch solution, that feels rather horrible.

I nearly gave up: the machine I choose, the Lenovo M600 tiny (I have an unneeded on I'm currently selling, if anyone wants a fanless mini pc1) only has 2 DisplayPort connectors, and DP -> HDMI adapters are not great. Then suddenly, I found the HP L7010t 10.1-inch Retail Touch Monitor2: Displayport + touch, no special drivers, and even FreeBSD's kernel can use the touch features out of the box.

Update: well, nothing is perfect. The HP's mounting space is about 2mm smaller, than the standard stands, so I ended up making a DIY stand by drilling a 10cm by 10cm square in the four corners into a steel bookend and bending it backwards.

Minimal X to run a single browser session on FreeBSD

On FreeBSD 14.1, the base system needed the following:

pkg install xorg xorg-server drm-kmod xf86-video-intel gsed surf-browser openbox

xf86-video-intel is not in the official documentation, but it's needed3.

Update: on FreeBSD 14.2, as long as 14.1 is supported, drm-kmod is broken4 because it's still built for 14.1 Instead use drm-515-kmod:

pkg install xorg xorg-server drm-515-kmod xf86-video-intel gsed surf-browser openbox

Tried twm, tinywm: they had issues with window size. Tried dwm but the menubar is annoying. Settled with openbox.

Tried midori, but wasn't working smooth, firefox-esr, but it consumed too much CPU and touch scrolling wasn't working fine, chromium which worked perfectly but had too much Google in it for this scenario, surf-browser which didn't work at all. settled with epiphany. Update: I realized I wasn't starting surf-browser5 they way I should have, and it's much lighter, then epiphany, so surf it is. Note: it's not NetSurf6 - I'd love to use NetSurf, but it lacks the JavaScript support needed for Domoticz and Zigbee2MQTT.

These are needed to enable graphics and auto-power off the screen:

sysrc kld_list+=i915kms
sysrc blanktime=300

Create a user that will be logged in automaticaly:

pw adduser -n kioskuser -d /home/kioskuser -s /bin/sh

Set up auto login for this user:

cp /etc/gettytab /etc/gettytab.backup
gsed -ri 's/root/kioskuser/g' /etc/gettytab

In /etc/ttys change

ttyv0 "/usr/libexec/getty Pc"   xterm onifexists secure

to

ttyv0 "/usr/libexec/getty autologin"   xterm onifexists secure

Then create:

/home/kioskuser/.profile

ENV=$HOME/.shrc; export ENV

if [ "$PWD" != "$HOME" ] && [ "$PWD" -ef "$HOME" ] ; then cd ; fi

XPID=`pgrep xinit`
if [ -z "$XPID" ] && [ `tty` == "/dev/ttyv0" ]; then
  startx
fi

And:

/home/kioskuser/.xinitrc

#!/bin/sh

userresources=$HOME/.Xresources
usermodmap=$HOME/.Xmodmap
sysresources=/usr/local/etc/X11/xinit/.Xresources
sysmodmap=/usr/local/etc/X11/xinit/.Xmodmap

# merge in defaults and keymaps
if [ -f $sysresources ]; then
  xrdb -merge $sysresources
fi

if [ -f $sysmodmap ]; then
  xmodmap $sysmodmap
fi

if [ -f "$userresources" ]; then
  xrdb -merge "$userresources"
fi

if [ -f "$usermodmap" ]; then
  xmodmap "$usermodmap"
fi

xset +dpms
xset s on
xset s blank
openbox &
exec /usr/local/bin/epiphany

Which should auto-start X with epiphany.

Note: this is not hardened, if the browser is closed, the user will be in the shell, X and epiphany will not auto-restart.

Extra: home automation jail on lo0 with PF firewall routing

I moved my services onto a single machine which now runs 3 jail: one for web and exposed to the world services, one for home automation, and one for minidlna. The first two listen on an extra 127.0.0.x on lo0, while the last has it's own IP. This also makes it quite simple to create a backup of them: I found zrepl7 and I can sync the jails onto a virtual machine on my laptop. Backup problem solved without needing to run yet another computer at home.

I ended up with this setup because my router - a FRITZ!Box 7530 AX - is surprisingly dumb, and can't comprehend the idea of multiple IPs on the same node, so to overcome any possible routing issue I decided to use a single IP initially and route everything with pf locally.

I mostly works, except for DLNA. I can't make the DLNA broascast packets work with this setup, so that jail is the exception, but I won't get into details with that now.

Zigbee2MQTT needs access to raw sockets, so that has to be reflected in the jail setup:

/etc/jail.conf

domoticz {
  exec.start = "/bin/sh /etc/rc";
  exec.poststart = "/bin/sh /usr/local/jails/domoticz/usr/local/etc/jail_dev_symlinks.sh";
  exec.poststart += "/usr/sbin/service pf reload";
  exec.stop = "/bin/sh /etc/rc.shutdown";
  exec.consolelog = "/var/log/jail_console_${name}.log";

  # PERMISSIONS
  allow.raw_sockets;
  allow.read_msgbuf;
  exec.clean;
  mount.devfs;
  devfs_ruleset = 3;
  allow.reserved_ports;

  # HOSTNAME/PATH
  host.hostname = "${name}";
  path = "/usr/local/jails/${name}";

  # NETWORK
  ip4.addr = 127.0.0.2;
  interface = lo0;
}

And to avoid issues with tty namings, I run a script to create the symlinks. I tried doing this properly with devd, but it's virtually impossible inside the jails to do it right.

/usr/local/jails/domoticz/usr/local/etc/jail_dev_symlinks.sh

#!/bin/sh

rootdir="/usr/local/jails/domoticz"
vendor="0x10c4"
product="0xea60"
ttyname=`sysctl dev.uslcom | grep "vendor=$vendor product=$product" | sed -r 's/.*ttyname=([^\s]+) .*/\1/'`
chgrp dialer $rootdir/dev/tty$ttyname
chown zigbee2mqtt $rootdir/dev/tty$ttyname 
chmod g+rw $rootdir/dev/tty$ttyname 
/bin/ln -s /dev/tty$ttyname $rootdir/dev/ttyUzigbee

As I mentioned, the two main jails listen on lo0 so they don't have issues of not having 127.0.0.1 resolved and so only what PF allows will be exposed this way.

/usr/local/etc/pf.conf

# vim: set ft=pf
# /usr/local/etc/pf.conf
 
lan="re0"
lo="lo0"
localnet = $lan:network
jail_domoticz="127.0.0.2"
local_ip="192.168.1.2"

# 8800: node-red
# 8880: zigbee2mqtt
# 8088: domoticz
# 1883: mqtt
rdr on $lan inet proto {tcp, udp} from any to $lan port {8800, 8880, 8088, 1883} -> $jail_domoticz
nat on $lan from { $jail_domoticz } to any -> $local_ip

block in all
block return

pass inet proto icmp icmp-type echoreq
pass quick proto pfsync
pass proto carp

# for jail
pass proto tcp from any to $jail_domoticz port { 8800, 8880, 8088, 1883 } keep state

# ssh - you probably want this
pass proto tcp from any to $lan port { 22 } keep state

pass from {$lan, $lo} to any keep state

pass out all keep state

Thoughts on Home Assistant

I gave Home Assistant yet another go. Every once in a while I try it out to see if it got simpler, but no: it's not even slowly becoming a bloated monster. It started as heaviweight, but it's just ridiculous now.

First and foremost their idea of supporting the last 2 versions of Python is already not true: I wasn't able to install the current version on 3.11, which FreeBSD 14.1 comes with. Not supporting stable Debian Python8 is a shitty decision.

In the end I wasn't able to install it at all. It's one thing that pip install needs to compile half the universe because it's not actually Python but C or Rust and it's not distributed for FreeBSD, but once it all looked good and I started hass is started to install even more Python modules.

This thing is badly designed, going entirely against ideas of simplicity and robustness, and I'm genuinely losing my belief in anything Python these days. So many projects out there will tell you "just use docker" thinking you want one more layer of complexity, or that it's available for you at all.

Dated or not, Domoticz is staying true to it's simle philosophy of it just works, and I'm going to stay loyal to it in the foreseable future.

Other notes

Don't try to run anything DLNA, like minidlna behind a firewall. It doesn't work.


  1. https://www.ebay.co.uk/itm/186603942366↩︎

  2. https://www.ebay.co.uk/itm/155788268323↩︎

  3. https://forums.freebsd.org/threads/unable-to-start-anything-x.95048/#post-672721↩︎

  4. https://forums.freebsd.org/threads/display-turns-black-when-the-i915kms-loads-freebsd-14-2.95973/↩︎

  5. https://surf.suckless.org/↩︎

  6. https://www.netsurf-browser.org/↩︎

  7. https://zrepl.github.io/↩︎

  8. https://wiki.debian.org/DebianBookworm↩︎

(Oh, by the way: this entry was written by Peter Molnar, and originally posted on petermolnar dot net.)