This post is a revamped version of the previous post on the same topic. The new instructions include:
- installing mosquitto and Z2M in a separate jail,
- a symlink from the
/dev/cuaXX
to/zigbee
, and- allowing mosquitto to receive connections from outside the jail.
In a previous post, we got Home Assistant Core and it’s dependencies installed into a Bastille jail. We were able to start HA were greeted by the initial landing page. Now it’s time to make HA more useful by pulling in Zigbee data.
Why Zigbee? It’s what I have
Now there are two ways to let HA use Zigbee, Zigbee Home Automation and Zigbee2MQTT. I am going to use Zigbee2MQTT because I prefer to keep services separate where possible.
Software Link to heading
- Home Assistant Core 2024.4 (installed in a previous post)
- Zigbee2MQTT 1.36.1 (installed from Github)
- Mosquitto 2.0.18 (installed from FreeBSD ports)
Getting the Zigbee USB dongle ready Link to heading
I know that the Zigbee dongle is recognised by the FreeBSD kernel because I get this output from dmesg
when I pull it out and plug it back in:
ugen2.3: <ITead Sonoff Zigbee 3.0 USB Dongle Plus> at usbus2 (disconnected)
uslcom0: at uhub1, port 2, addr 2 (disconnected)
uslcom0: detached
ugen0.4: <ITead Sonoff Zigbee 3.0 USB Dongle Plus> at usbus0
uslcom0 on uhub2
uslcom0: <ITead Sonoff Zigbee 3.0 USB Dongle Plus, class 0/0, rev 2.00/1.00, addr 3> on usbus0
On my system, the dongle is accessible at
/dev/cuaU0
. So far, I haven’t worked out how to rename this to something unique to this dongle, for example/dev/zigbee
. I can usedevd
to create a symlink in/dev
but this symlink is not accessible within the jail. I asked on the FreeBSD forums and maximbo suggested that I usedevd
to create a symlink from/dev/cuaU0
to anywhere else in the jail.
On my system, the dongle shows up at /dev/cuaU0
.
However, this could change if we have a second USB device and restart the computer.
Since we know that the dongle will always have a name that starts with /dev/cuaXX
, we could simply allow the jail to see all of the /dev/cuaXX
devices.
But we would still have the problem of not knowing which /dev/cuaXX
device is the zigbee dongle.
So what I want to do is tell the FreeBSD host to create a symlink from /dev/cuaXX
to /zigbee
inside the jail when the dongle is plugged in.
To do this, we can use the devd
system on FreeBSD to run some commands when a particular device is attached or detached.
We can use the Vendor and Product IDs to ensure that devd
creates /zigbee
only for that type of device.
If you have two identical Zigbee dongles, you can use serial numbers help distinguish between dongles.
Getting the IDs of the Zigbee dongle Link to heading
The dmesg
output above shows that the address of the Zigbee dongle is ugen0.4
.
We could also get this address by running:
host # usbconfig
<other devices removed>
ugen0.4: <ITead Sonoff Zigbee 3.0 USB Dongle Plus> at usbus0, cfg=0 md=HOST spd=FULL (12Mbps) pwr=ON (100mA)
What we want is the Vendor and Product IDs of the Zigbee dongle:
host # usbconfig -d ugen0.4 -v
ugen0.4: <ITead Sonoff Zigbee 3.0 USB Dongle Plus> at usbus0, cfg=0 md=HOST spd=FULL (12Mbps) pwr=ON (100mA)
ugen0.4.0: uslcom0: <ITead Sonoff Zigbee 3.0 USB Dongle Plus, class 0/0, rev 2.00/1.00, addr 4>
bLength = 0x0012
bDescriptorType = 0x0001
bcdUSB = 0x0200
bDeviceClass = 0x0000 <Probed by interface class>
bDeviceSubClass = 0x0000
bDeviceProtocol = 0x0000
bMaxPacketSize0 = 0x0040
idVendor = 0x10c4 <------- Vendor ID
idProduct = 0xea60 <------- Product ID
bcdDevice = 0x0100
iManufacturer = 0x0001 <ITead>
iProduct = 0x0002 <Sonoff Zigbee 3.0 USB Dongle Plus>
iSerialNumber = 0x0003 <a6716f079912ec11975c20c7bd930c07> <------ You could also use this
bNumConfigurations = 0x0001 if you have multiple dongles.
<there is more output that is not relevant here>
Creating a custom devd rule Link to heading
Using this information, we create custom devd rules that only works when a device matches the vendor
and product
.
Thanks to tspi.at for some guidance here.
The first rule is activated when the dongle is attached and creates a symlink from /dev/cuaXX
to <root of the jail>/zigbee
and applies the appropriate permissions to the link.
The second rule is activated when the dongle is detached and removes the symlink.
host # vim /usr/local/etc/devd/zigbee.conf
attach 100 {
match "vendor" "0x10c4";
match "product" "0xea60";
action "ln -sf /dev/cua$ttyname /usr/local/bastille/jails/mqtt/root/zigbee && chown -h uucp:dialer /usr/local/bastille/jails/mqtt/root/zigbee";
};
notify 100 {
match "type" "DETACH";
match "vendor" "0x10c4";
match "product" "0xea60";
action "rm -rf /usr/local/bastille/jails/mqtt/root/zigbee";
};
Now we restart the devd
service:
host # service devd restart
Passing the /dev/cuaXX
device to the jail
Link to heading
By default, the /dev/cuaU0
device is not visible within the jail:
jail # ls -lah /dev/
dr-xr-xr-x 9 root wheel 512B Dec 24 18:15 .
drwxr-xr-x 14 root wheel 24B Dec 10 16:33 ..
crw------- 1 root wheel 0x27 Dec 24 18:15 bpf
lrwxr-xr-x 1 root wheel 3B Dec 24 18:15 bpf0 -> bpf
crw-rw-rw- 1 root wheel 0x42 Dec 13 11:55 crypto
dr-xr-xr-x 2 root wheel 512B Dec 24 18:15 fd
crw-rw-rw- 1 root wheel 0x14 Dec 24 19:11 null
crw------- 1 root wheel 0xab Dec 13 11:56 pf
dr-xr-xr-x 2 root wheel 512B Dec 24 18:15 pts
crw-r--r-- 1 root wheel 0x4 Dec 13 11:56 random
lrwxr-xr-x 1 root wheel 4B Dec 24 18:15 stderr -> fd/2
lrwxr-xr-x 1 root wheel 4B Dec 24 18:15 stdin -> fd/0
lrwxr-xr-x 1 root wheel 4B Dec 24 18:15 stdout -> fd/1
lrwxr-xr-x 1 root wheel 6B Dec 24 18:15 urandom -> random
crw-rw-rw- 1 root wheel 0x15 Dec 13 11:55 zero
crw-rw-rw- 1 root operator 0x3f Dec 13 11:55 zfs
To make it visible, we need to allow the mqtt jail to see cuaXX
devices.
First, we create a new ruleset on the host in /etc/devfs.rules
:
host # vim /etc/devfs.rules
[bastille_vnet=13] <---- This is what you will have if you are using VNET jails
#add path 'bpf*' unhide
#add include $devfsrules_jail
add include $devfsrules_hide_all
add include $devfsrules_unhide_basic
add include $devfsrules_unhide_login
add include $devfsrules_jail
add include $devfsrules_jail_vnet
add path 'bpf*' unhide
[bastille_mqtt=14] <---- This is what you need to create
add include $devfsrules_hide_all
add include $devfsrules_unhide_basic
add include $devfsrules_unhide_login
add include $devfsrules_jail
add include $devfsrules_jail_vnet
add include $bastille_vnet
add path 'cua*' unhide <---- The important part
bastille_vnet
is what you would have created when you were setting up the jails.
bastille_mqtt
is what we’ve created specially for the mqtt jail.
It includes all of the rules used by the bastille_vnet
ruleset, plus another rule that allows cuaXX
devices to be visible.
Next, you need to restart the devfs
service:
host # service devfs restart
Then, we change the ruleset used by the home-assistant jail:
host # vim /usr/local/bastille/jails/home-assistant/jail.conf
mqtt {
devfs_ruleset = 14; <----- Set this to the number of the ruleset you just created
enforce_statfs = 2;
exec.clean;
<Other lines unchanged>
}
Finally, restart the jail.
Now, from inside the jail you can see the newly unhidden devices in /dev
and the newly created link /zigbee
:
jail # ls -lah /dev/
dr-xr-xr-x 9 root wheel 512B Dec 24 18:15 .
drwxr-xr-x 14 root wheel 24B Dec 10 16:33 ..
crw------- 1 root wheel 0x27 Dec 24 18:15 bpf
lrwxr-xr-x 1 root wheel 3B Dec 24 18:15 bpf0 -> bpf
crw-rw-rw- 1 root wheel 0x42 Dec 13 11:55 crypto
crw-rw---- 1 uucp dialer 0xe1 Dec 24 13:22 cuaU0 <----- Here,
crw-rw---- 1 uucp dialer 0xee Dec 24 13:22 cuaU0.init <----- here,
crw-rw---- 1 uucp dialer 0xf2 Dec 24 13:22 cuaU0.lock <----- and here!
dr-xr-xr-x 2 root wheel 512B Dec 24 18:15 fd
crw-rw-rw- 1 root wheel 0x14 Dec 24 19:11 null
crw------- 1 root wheel 0xab Dec 13 11:56 pf
dr-xr-xr-x 2 root wheel 512B Dec 24 18:15 pts
crw-r--r-- 1 root wheel 0x4 Dec 13 11:56 random
lrwxr-xr-x 1 root wheel 4B Dec 24 18:15 stderr -> fd/2
lrwxr-xr-x 1 root wheel 4B Dec 24 18:15 stdin -> fd/0
lrwxr-xr-x 1 root wheel 4B Dec 24 18:15 stdout -> fd/1
lrwxr-xr-x 1 root wheel 6B Dec 24 18:15 urandom -> random
crw-rw-rw- 1 root wheel 0x15 Dec 13 11:55 zero
crw-rw-rw- 1 root operator 0x3f Dec 13 11:55 zfs
jail # ls -lah /
drwxr-xr-x 14 root wheel 25B Jan 8 21:07 .
drwx------ 3 root wheel 5B Jan 8 20:47 ..
drwxr-xr-x 18 root wheel 22B Nov 23 15:39 .bastille
-rw-r--r-- 1 root wheel 1.0K Nov 10 19:11 .cshrc
-rw-r--r-- 1 root wheel 495B Nov 10 19:11 .profile
drwxr-xr-x 2 root wheel 2B Jan 7 11:17 .template
-r--r--r-- 1 root wheel 6.0K Nov 10 19:49 COPYRIGHT
lrwxr-xr-x 1 root wheel 14B Jan 7 11:17 bin -> /.bastille/bin
lrwxr-xr-x 1 root wheel 15B Jan 7 11:17 boot -> /.bastille/boot
dr-xr-xr-x 9 root wheel 512B Jan 8 20:47 dev
drwxr-xr-x 30 root wheel 108B Jan 7 13:10 etc
lrwxr-xr-x 1 root wheel 8B Jan 7 11:17 home -> usr/home
lrwxr-xr-x 1 root wheel 14B Jan 7 11:17 lib -> /.bastille/lib
lrwxr-xr-x 1 root wheel 18B Jan 7 11:17 libexec -> /.bastille/libexec
drwxr-xr-x 2 root wheel 2B Nov 10 18:48 media
drwxr-xr-x 2 root wheel 2B Nov 10 18:48 mnt
drwxr-xr-x 2 root wheel 2B Nov 10 18:48 net
dr-xr-xr-x 2 root wheel 2B Nov 10 18:48 proc
lrwxr-xr-x 1 root wheel 17B Jan 7 11:17 rescue -> /.bastille/rescue
drwxr-x--- 2 root wheel 10B Jan 8 20:47 root
lrwxr-xr-x 1 root wheel 15B Jan 7 11:17 sbin -> /.bastille/sbin
drwxrwxrwt 6 root wheel 6B Jan 8 20:48 tmp
drwxr-xr-x 6 root wheel 15B Jan 7 11:17 usr
drwxr-xr-x 24 root wheel 24B Jan 8 20:47 var
lrwxr-xr-x 1 uucp dialer 10B Jan 8 21:07 zigbee -> /dev/cuaU0 <---- And here
Mosquitto MQTT Link to heading
As I mentioned earlier, I will be using Zigbee2MQTT. This requires a MQTT server. Luckily, Mosquitto is available in the FreeBSD ports. By default, mosquitto will only accept connections coming from the jail where it is installed. You will need to make a couple of changes to the configuration if you want to allow outside connections.
jail # pkg install mosquitto
jail # service mosquitto enable
jail # vim /usr/local/etc/mosquitto/mosquitto.conf
listener 1883 <------Add this around line 234
allow_anonymous true <------Add this around line 532
You can’t just add these two configuration options to the bottom of the config file. Mosquitto seems to be picky about the order of options and will fail to start if these lines are at the bottom. (I haven’t tried putting them at the top of the file).
Finally, start the mosquitto service
jail # service mosquitto start
Starting mosquitto.
1703409712: mosquitto version 2.0.15 starting
1703409712: Config loaded from /usr/local/etc/mosquitto/mosquitto.conf.
1703409712: Create a configuration file which defines a listener to allow remote access.
1703409712: Opening ipv4 listen socket on port 1883.
1703409712: Opening ipv6 listen socket on port 1883.
jail # sockstat -4
USER COMMAND PID FD PROTO LOCAL ADDRESS FOREIGN ADDRESS
nobody mosquitto 45787 3 tcp4 *:* *:*
nobody mosquitto 45787 4 tcp4 *:1883 *:*
Mosquitto is now accessible from anywhere on your network. I haven’t gone into detail on how to set up security, limit access, etc. That’s for another day
Zigbee2MQTT Link to heading
Unfortunately, Zigbee2MQTT is not available in the ports, but it is very easy to get up and running. I am going to install Z2M on the same jail as the MQTT broker. (These instructions are my adaptation of the official instructions.)
Requirements Link to heading
- You will need a working MQTT broker to get Z2M running.
- You will also need to install
node npm git gmake gcc
.
Installation Link to heading
Clone the git repository, install the node dependencies, update the configuration to point to your zigbee controller, and start.
jail # git clone --depth 1 https://github.com/Koenkk/zigbee2mqtt.git
jail # cd zigbee2mqtt
jail ~/zigbee2mqtt # npm ci
added 813 packages, and audited 814 packages in 7s
90 packages are looking for funding
run `npm fund` for details
found 0 vulnerabilities
jail ~/zigbee2mqtt # vim data/configuration.yaml
homeassistant: true
permit_join: true
mqtt:
base_topic: zigbee2mqtt
server: mqtt://127.0.0.1:1883
serial:
port: /zigbee
adapter: zstack
frontend: true
advanced:
log_output:
- console
log_level: info <----- debug will give a lot of output
timestamp_format: YYYY-MM-DD HH:mm:ss
channel: 25
jail ~/zigbee2mqtt # npm start
zigbee2mqtt@1.34.0 start
node index.js
<I've cut down the output here to show you the main things to look for>
Zigbee2MQTT:info 2023-12-24 20:30:26: Starting Zigbee2MQTT version 1.34.0 (commit #56589dc)
Zigbee2MQTT:info 2023-12-24 20:30:26: Starting zigbee-herdsman (0.25.0)
Zigbee2MQTT:info 2023-12-24 20:30:31: Coordinator firmware version: '{"meta":{"maintrel":1,"majorrel":2,"minorrel":7,"product":1,"revision":20210708,"transportrev":2},"type":"zStack3x0"}'
Zigbee2MQTT:warn 2023-12-24 20:30:31: `permit_join` set to `true` in configuration.yaml.
Zigbee2MQTT:warn 2023-12-24 20:30:31: Allowing new devices to join.
Zigbee2MQTT:warn 2023-12-24 20:30:31: Set `permit_join` to `false` once you joined all devices.
Zigbee2MQTT:info 2023-12-24 20:30:31: Zigbee: allowing new devices to join.
Zigbee2MQTT:info 2023-12-24 20:30:31: Connecting to MQTT server at mqtt://127.0.0.1:1883
Zigbee2MQTT:debug 2023-12-24 20:30:31: Using MQTT anonymous login
Zigbee2MQTT:info 2023-12-24 20:30:31: Connected to MQTT server
Zigbee2MQTT:info 2023-12-24 20:30:31: MQTT publish: topic 'zigbee2mqtt/bridge/state', payload '{"state":"online"}'
Zigbee2MQTT:info 2023-12-24 20:30:31: Started frontend on port 8080
Zigbee2MQTT:info 2023-12-24 20:30:31: Zigbee2MQTT started!
You will see of output here as you add devices to the network, especially if you set the logging to debug
.
We can set this up to run as a daemonised service with logging:
jail # vim /usr/local/etc/rc.d/zigbee2mqtt
#!/bin/sh
# PROVIDE: zigbee2mqtt
# REQUIRE: DAEMON NETWORKING
# BEFORE: LOGIN
# KEYWORD: shutdown
. /etc/rc.subr
name="zigbee2mqtt"
rcvar=zigbee2mqtt_enable
: ${zigbee2mqtt_enable:="NO"}
# daemon
pidfile="/var/run/${name}.pid"
node="/usr/local/bin/node"
script_js="/root/zigbee2mqtt/index.js"
command=/usr/sbin/daemon
#procname="daemon"
command_args="-S -l local0 -s info -P ${pidfile} -r ${node} ${script_js}"
load_rc_config $name
run_rc_command "$1"
jail # vim /etc/syslog.conf
#
# Spaces ARE valid field separators in this file. However,
# other *nix-like systems still insist on using tabs as field
# separators. If you are sharing this file between systems, you
# may want to use only tabs as field separators here.
# Consult the syslog.conf(5) manpage.
*.err;kern.warning;auth.notice;mail.crit /dev/console
*.notice;authpriv.none;kern.debug;lpr.info;mail.crit;news.err /var/log/messages
security.* /var/log/security
auth.info;authpriv.info /var/log/auth.log
mail.info /var/log/maillog
cron.* /var/log/cron
!-devd
*.=debug /var/log/debug.log
*.emerg *
daemon.info /var/log/daemon.log
local0.info /var/log/zigbee2mqtt.log <------ Add this line here
<Later lines removed>
After you restart the syslogd
service, enable and then start the zigbee2mqtt
service, you can head to jail-ip-address:8080 to see the Zigbee2MQTT dashboard.
If this is not working, check the log files for any misconfigurations.
Configuring devices Link to heading
In the Z2M dashboard, you can give custom names to devices as you add them. These names will be passed through to Home Assistant as device names.
Getting Zigbee into Home Assistant Link to heading
All that is needed now is to install and configure the MQTT integration into Home Assistant.
Go to Settings
-> Devices & services
-> + ADD INTEGRATION
.
Search for and add MQTT
.
Set the broker to the MQTT jail IP address and the port to 1883
.
Wait a few minutes and your added Zigbee devices should be discovered by Home Assistant.