I recently got a camera to use with my 3D printer which includes integrated LED illumination. It has a physical switch to control the lights, but I want to turn them on and off, either from OctoPrint, or from Home Assistant, based on the printer status. Wanting the simplest solution possible, I’m going to turn off power to the Pi’s USB hub when I don’t need the camera lighting. This has a side effect of also disabling the camera itself due to lack of power, which I will also need to deal with, but it doesn’t disable the USB data lines, so the 3D printer (which has its own power supply) might not be affected, depending on the printer model.
Basics of USB Hub Control⌗
The key to this project is the uhubctl command, which allows us to control the power state of USB hub ports from the command line. It specifically supports the built-in hub(s) on the Raspberry Pi B+, 2B, 3B, 3B+, and 4B, although the commands vary slightly based on each model. In my case, I am using a Raspberry Pi 3B, but you can change the commands if you have a 3B+ or 4B (B+, 2B, 3B are all the same). For the Pi, all of the ports share a single power output, so we can’t control power individually. Other hubs don’t have this limitation, so if this is a deal breaker for your setup, you should get another hub on the supported list to control your outputs, OR use any old hub to power the devices which still need power (since uhubctl doesn’t actually disable communication on the ports, just turn off the power to them).
I’m starting with a completely bare OctoPi install in this case, but Raspbian / Raspberry Pi OS should be similar. I first need to install uhubctl, which is packaged with Debian (and thus Raspbian). SSH in and install it with apt.
sudo apt install uhubctl
Next, we need to give uhubctl permission to operate on the hub, or use sudo all the time. To start, we can test that it works at all using sudo.
This should return a list of all of the hubs and ports. For my Pi, there is one hub (1-1) and it has 5 ports. Port 1 controls the WiFi and Ethernet, port 2-5 are the user-accessible USB ports. Controlling the power to port 2 controls all of the user ports. We can try toggling it with sudo:
#Turn off hub 1-1 port 2 (all external ports on the Pi)
sudo uhubctl -l 1-1 -p 2 -a 0
#Turn on hub 1-1 port 2 (all external ports on the Pi)
sudo uhubctl -l 1-1 -p 2 -a 1
In my case, the lights on my camera turned off (well, the whole camera turned off), and then came back on. Since we toggled power to the camera in addition to just the lights, the OctoPi camera streamer isn’t very happy with us, so we need to restart it
#The OctoPi webcam service is named webcamd
sudo systemctl restart webcamd
At this point, if we just wanted hub control, we could give user pi permission to control the hubs via udev rules. uhubctl gives us instructions for doing this. However, since we will always need sudo to restart webcamd, the script / tool we use to do this needs to have sudo permissions anyway.
Control using Node-Red⌗
If you only want to control the USB hub to come on/off with the printer, you can use the OctoPrint PSU Control plugin to do this. However, I’m already using the PSU Control plugin to control my PSU, using a Sonoff S31 over MQTT (See Project), so I can’t use that plugin again. I decided to install Node-Red on the Pi, give it full passwordless sudo permissions (dangerous, I know), and have it subscribe to the same MQTT topic used to control the Sonoff, and respond to ON/OFF messages with the corresponding uhubctl commands.
I have another quirk in my setup, which you are also likely to encounter if your printer has a well-designed controller. Some printers have isolation between the USB port and the printer’s internal power supply, and rely on USB power to supply the USB-serial converter and/or USB isolator. In my case, my Prusa i3 MK3S has a USB-UART chip which is powered by the USB power, followed by a digital isolator for the serial lines which is also powered by USB power. So, at a minimum, I need to keep the USB bus power enabled as long as I want to connect to my printer. Since I am having the USB bus power directly follow the AC power to the printer, this should be perfect, since the PSU Control plugin can be configured to wait after it confirms AC power is on before it connects to the printer and it disconnects from the printer before it poweres off.
I installed node-red using the apt package, but the node-red website does have a script to install it on your own (and install a more recent version). Since I’m not particularly concerned about the version, I used the one in the distro repository.
sudo apt update
sudo apt install nodered
After that, I needed to allow no-password sudo access. Node-red runs as user pi, who already has sudo permissions, but would need to re-type their own password to use sudo. Since node-red can’t do that, I’m going to give user pi no-password sudo access, to allow node-red to execute systemctl to restart the webcam server and also control uhubctl without modifying udev rules. We could get around the uhubctl issue, but systectl must always run as root, so we still need to give root access to node-red anyway. To do this, we need to edit the sudoers file, and this must be done using visudo:
#If it asks which editor you want, Nano is the easiest to use
#Find the following line
%sudo ALL=(ALL:ALL) ALL
#Replace with this line
%sudo ALL=(ALL:ALL) NOPASSWD: ALL
Now, setup a node-red flow to listen on the command topic and respond by executing the system commands to turn on/off USB power. Click Here for my Flow. You will need to setup the MQTT broker and topic to match your setup, I have examples listed.