Tuesday, August 20, 2019

Home Assistant in an LXD Container, USBIP to remote USB Z-Wave dongle

This post merely ties together a few existing items, and adds a few twiddly bits specific to Homeassistant, LXD, usbip, and my specific Z-Wave USB dongle.

     |
     |
     + Headless Machine / LXD Host (Ubuntu Server 19.04, 192.168.1.11)
     |    + usbip client
     |    + Some other LXD container     (192.168.1.111)
 LAN |    + Home Assistant LXD container (192.168.1.112)
     |
     + Remote Raspberry Pi (Raspbian, 192.169.1.38)
     |    + usbip server    
     |    + Some other Pi activity
     |    + Z-Wave Controller USB Dongle

Our goal is for Home Assistant, running inside an LXD container, to use the Z-Wave Controller plugged into an entirely different machine.

  1. Since this is a persistent service, harden the pi. SSH using keys only, etc.
  2. Set up usbip server on the Pi. Include the systemd service so it restarts and re-binds at reboot.
  3. Set up the usbip client on the host (the HOST, not the container)
  4. If you haven't already, create the container and install homeassistant into the container

The rest is specific to Ubuntu, to LXD, and to the USB Dongle.

The USB dongle is cheap and includes both Z-Wave and Zigbee, often sold under the 'Nortek' brand. When you plug it into a Linux system, it looks like this:

    $lsusb
        Bus xxx Device xxx: ID 10c4:8a2a Cygnal Integrated Products, Inc. 



When plugged in on the host (or forwarded via usbip), the dongle creates new nodes in /dev:

    host$ ls -l /dev/ | grep USB
        crw-rw---- 1 root dialout 188,   0 Aug 18 10:29 ttyUSB0   // Z-Wave
        crw-rw---- 1 root dialout 188,   1 Aug 18 10:29 ttyUSB1   // Zigbee


These old-style nodes mean that we can NOT use LXD's USB hotplug feature (but there's an alternative). Also, it means that Home Assistant cannot autodetect the dongle's presence (we must manually edit the HA config).

Shut down the container, or restart the container after making the Z-Wave node accessible to the container. Without a restart, the container won't pick up the change. I've seen promises that it should be hot-pluggable. Maybe it is...but I needed to restart the container after this command. It's very similar to the USB Hotplug command, but uses 'unix-char' instead.

    host$ lxc config device add home-assistant zwave unix-char path=/dev/ttyUSB0
        Device zwave added to home-assistant
            // home-assistant is the name of the LXD container
            // z-wave         is the name of this new config. We could name it anything we want
            // unix-char      is the method of attaching we are using (instead of 'usb')
            // path           is the path on the HOST

    host$ lxc restart home-assistant      // Restart the container


Now we move into a shell prompt in the CONTAINER (not host). My personal preference, since I'm used to VMs, is to treat the container like a VM. It has (unnecessary) ssh access (key-only, of course), and non-root users to admin and to run the 'hass' application. It also has the (unnecessary) Python venv. All of that bloat is a combination of preference and following the install documentation which simply did not expect that we might be able to run a as root here. Seems like a whole new blog post. The upshot is that inside the container I have a user prompt ($) and use sudo instead of a root prompt (#). Your mileage may vary.

    container$ ls -l /dev | grep USB
        crw-rw---- 1 root   root    188,   0 Aug 19 17:52 ttyUSB0

Look at the permissions: They have changed. And, as I said before, hass is not running as root within this container. Let's make a one-time change to make the Z-Wave USB dongle readable by hass.

    container$ sudo chown root:dialout /dev/ttyUSB0
        // There should be no output

    container$ ls -la /dev/ | grep USB
        crw-rw---- 1 root   dialout 188,   0 Aug 19 17:53 ttyUSB0

    container$ sudo adduser homeassistant dialout  // OPTIONAL - add the 'homeassistant' user to the correct group
                                                   // This is done early in most Home Assistant install instructions
                                                   // You may have done this already
                                                   // If not, restart the container so it takes effect


The chown seems to NOT persist across a reboot, so let's add a line to the systemd service so the chown occurs every time the container comes up.

    container$ sudo nano /etc/systemd/system/home-assistant@homeassistant.service

        // Add to the [Service] section
        ExecStartPre=/bin/chown root:dialout /dev/ttyUSB0

    container$ sudo systemctl daemon-reload


Edit Home Assistant's config file, so hass knows where to find the Z-Wave node.

    me@container$ sudo -u homeassistant -H -s
    homeassistant@container$ nano /home/homeassistant/.homeassistant/configuration.yaml

        zwave:
          usb_path: /dev/ttyUSB0

    homeassistant@container$ exit


Finally, debian-based systems must install one additional deb package to support Z-Wave.

    container$ sudo apt install libudev-dev


Restart Home Assistant (if it's running) to pick up the new config. Go into the Web Page, and try adding the Z-Wave integration


    container$ sudo systemctl restart home-assistant@homeassistant.service

No comments: