I've been looking at getting into home automation for quite a bit now, and so far, I have a home assistant server running happily on my hypervisor, controlling Philips Hue lightbulbs and Ubiquiti mFi power strips.

While mFi is highly hackable, its future is quite dubious (since Ubiquiti seems to have all but abandoned the line), the "server" component is really old, and the current lineup of hardware isn't very diverse.

So I've turned my attention to Insteon. Seems like a nice protocol, there's tons of Insteon devices available, and there's even an ethernet-enabled hub!

And that's where our problem lies. While doing my research, I quickly found that the Insteon Hub would not really work well offline - and mandatory "cloud" connections are something I really hate.

All is not lost, however, as it turns out that there's a device - called the PowerLinc USB modem - that I could use for a 100% local Insteon setup!

That looks neat, but now we run into a different kind of problem. See, my home assistant server lives on a VM, inside a vSphere environment that I have running, and I don't really want to do USB passthrough, since that will limit my VM's ability to be migrated wherever I want.

Enter USB/IP

Looking around, there seems to be a solution for this after all! It turns out that within the Linux kernel itself is a module called usbip . In a nutshell, this allows one to "share" USB devices from one Linux system to another Linux system... over the network, which seems to be just about what I wanted!

So, enough talking, let's set this up!

"Server" side (i.e. where the USB device plugs into)

For this, I decided to make use of that spare Rock64 board that is lying at the bottom of my parts bin. Turns out there's a release of Ubuntu 20.04 LTS for it, fresh out of the build pipeline! So I first cobbled together all the hardware:

My DIY "Insteon bridge"

Essentially, I just velcroed together the Rock64 board, the Insteon PowerLinc modem, and the wall-wart needed for the board together. A touch of zip-ties, and we have this weird contraption that, in theory, will allow me to control Insteon devices from home assistant.

The first step here is to enable the usbip_host module on the board. After booting into Ubuntu and performing all the usual first-time setup tasks, we can simply edit /etc/modules-load.d/modules.conf so that it looks somewhat like this:

# /etc/modules: Kernel modules to load at boot time ... usbip_host

This will load the module on boot. We also want to enable it right now, so that we won't need a reboot, so just do sudo modprobe usbip_host .

A lot of the guides you see online mentions /usr/bin/usbipd or /usr/sbin/usbipd . It turns out that those wouldn't really work (it'll just keep crashing), so we need to go find our actual usbipd binary that works.

In my case, the binary that's installed by the package above is located in /usr/lib/linux-tools/5.4.0-26-generic/usbipd . So we're going to use that.

Now, we can create a systemd service file for the USB/IP daemon, so that it would be automatically started on boot. To do this, we create a systemd service file /lib/systemd/system/usbipd.conf :

[Unit] Description=USB-IP server After=network-online.target Wants=network-online.target [Service] Type=simple ExecStart=/usr/lib/linux-tools/5.4.0-26-generic/usbipd Restart=on-failure [Install] WantedBy=default.target

Then, we enable the service using standard procedures:

sudo systemctl daemon-reload sudo systemctl start usbipd sudo systemctl enable usbipd

To determine what USB devices should be passed through, we first take a look at lsusb :

... Bus 003 Device 002: ID: 10bf:0004 SmartHome SmartHome PowerLinc USB E ...

For my case, the USB modem is at 10bf:0004 . Using this information, we can now create a udev rule so that our device (in this case the PowerLinc modem) will be automatically "exported" by usbipd upon insertion:

# PowerLinc USBIP udev rules SUBSYSTEM=="usb" ATTRS{idVendor}=="10bf" ATTRS{idProduct}=="0004" RUN+="/usr/lib/linux-tools/5.4.0-26-generic/usbip bind -b $kernel"

This goes into, say, /etc/udev/rules.d/90-usbip.rules . Save and reboot. Our "server" side setup should now be done :)

"Client" side (the VM we want to use the USB device on)

First thing to do here is to configure the module vhci-hcd to be loaded on boot. We do this by the same method as above, editing /etc/modules-load.d/modules.conf .

Then, we take a peek again to see where our usbip binaries are. In my case, it's at /usr/lib/linux-tools/4.15.0-96-generic/

So we can create another systemd service file to automatically mount this, like so. In my case, the USB-IP server is at 192.168.0.1 :

[Unit] Description=USB-IP client After=network.target [Service] Type=oneshot RemainAfterExit=yes ExecStart=/bin/sh -c "/usr/lib/linux-tools/4.15.0-96-generic/usbip attach -r 192.168.0.1 -b $(/usr/lib/linux-tools/4.15.0-96-generic/usbip list -r 192.168.0.1 | grep '10bf:0004' | cut -d: -f1)" ExecStop=/bin/sh -c "/usr/lib/linux-tools/4.15.0-96-generic/usbip detach --port=$(/usr/lib/linux-tools/4.15.0-96-generic/usbip port | grep '<Port in Use>' | sed -E 's/^Port ([0-9][0-9]).*/\1/')" [Install] WantedBy=multi-user.target

Now, save and start the service. And now on the VM, if we run lsusb , we should be able to see the modem!

[email protected]:~# lsusb Bus 002 Device 001: ID 1d6b:0003 Linux Foundation 3.0 root hub Bus 001 Device 002: ID 10bf:0004 SmartHome Bus 001 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub

And there we have it! A USB device being accessed over the network :)