Słomkowski's technical musings

Playing with software, hardware and touching the sky with a paraglider.

Automatic USB mains switch for USB-controlled power strip


Almost all available USB-controlled power strips use 5 V from USB to drive the relay. This is no use with recent PCs, which hold 5 V during sleep mode. I've written alternative firmware for popular DCT Tech USB relay module available from AliExpress, which detects bus inactivity and shuts the relay down when PC is in sleep mode.

I wanted to buy a USB-controlled power strip which shuts the devices down when the PC is put to sleep. Life is not so easy: all available USB power strips are quite dumb - they simply use +5 V from USB port to drive the relay. The problem is that most motherboards don’t disable USB supply voltage in sleep mode; the PC has to be turned off completely to be that case.

I realized I had to build custom switch which detects USB bus inactivity. This great tutorial about USB physical layer specifies that host generates start-of-frame packet every millisecond, otherwise the device should enter suspend mode. Communication ceases when computer is suspended. This is ideal for my situation.

Let’s look at the screenshot visualising signal on USB D- line. Indeed, every millisecond there’s a short negative pulse. The idea is to make an extremely simple USB device with no communication capabilities (no endpoints defined), which just listens to the bus activity and shuts the relay down after some time of inactivity.

Negative pulses on D- line every 1 ms.

Hardware

In fact, there’s no need to develop one, because there already exists a popular Chinese USB relay module, sold on AliExpress and eBay under various names. I used the very same one in my eavesdropping USB communication tutorial. It identifies itself as vendor: www.dcttech.com, product: USBRelay2.

Onboard, there are few components, most importantly ATtiny45. To upload firmware through ISP, I soldered wires and secured them with hot glue. Then I performed a successful communication test with the microcontroller using avrdude:

avrdude -c usbasp -p t45

After I was done developing custom firmware, I added power cables and the power strip. The whole contraption was put into electrical box and fixed under my PC desk. For now, it just powers my power-hungry speakers, but I might later plug other devices, like printer or so.

Firmware

I found that someone had already recreated the functionality of original firmware of the relay board. This proves that it is indeed possible to use V-USB without quartz oscillator on this board. I took inspiration from aforementioned firmware code (kudos!), but nonetheless created a new project from scratch with CMake and up-to-date version of V-USB. As usual, it is published on GitHub.

Speaking of library, V-USB is a bit-bang implementation of USB 1.1 protocol for AVR microcontrollers, GPL licensed. It needs just two microcontroller’s pins, one of them has to support hardware interrupt. If no quartz onboard, it is possible to use calibrated internal oscillator. I used it previously in my old USB Geiger counter project.

I was wondering if I should leave manual driving of the relays functionality intact, but eventually decided against since it would need three positions: on, off and auto instead of usual two. In this case, I would need custom PC software anyway. I thought about using serial profile to emulate serial port (so no software on PC side), but serial profile is only available to full speed devices; we can’t use serial profile in low-speed mode.

Finally, I decided that absolutely no communication capabilities are needed. The device defines no endpoints. It identifies itself with following values, which are set in accordance with V-USB recommendations:

idVendor           0x16c0 Van Ooijen Technische Informatica
idProduct          0x05dc shared ID for use with libusb
iManufacturer           1 slomkowski.eu
iProduct                2 Mains switch

V-USB has support for start-of-frame packet detection builtin, but you have to enable it first. However, it works only if interrupt pin is connected to D- line. INT0 is connected to D+ line on the board, but we can also use PCINT interrupt, as shown in this forum thread.

The general idea of the program is to trigger timer interrupt every 10 ms and check whether usbSofCount value changed. If so, timeout counter is reset. After 30 seconds of inactivity, relay is shut down. When activity is resumed, relay is turned back on. You can use any reasonable timeout value.

Disabling selective suspend

The relay state is determined by USB bus activity. If USB selective suspend is enabled, there’s no start-of-frame packets even if PC is fully powered on. We need to disable selective suspend.

Linux

As root, find the device path:

grep 16c0 /sys/bus/usb/devices/*/idVendor

For example, my device is connected as 3-1. Then, write on to power setting:

echo on > /sys/bus/usb/devices/3-1/power/control

The other valid value is auto - it means, that USB suspend is enabled. If you write this value to control file, after 30 seconds relay should go down. More details in this Stack Overflow answer.

To make it persistent, create udev rules file /etc/udev/rules.d/101-usb-power-strip.rules with following content:

ACTION=="add", SUBSYSTEM=="usb", ATTR{idVendor}=="16c0", ATTR{idProduct}=="05dc", TEST=="power/control", ATTR{power/control}="on"

and reload the rules (as root):

udevadm control --reload-rules && udevadm trigger

Some other hobby devices (for example, USBasp AVR programmer) use the very same Vendor/Product ID pair, but I guess they won’t be harmed if they have USB suspend disabled too.

Windows

There’s no way to disable selective suspend for single device, you have to disable it completely as described here. However, even with this done, my relay got shut down after 30 seconds (tested under Windows 10 and ThinkPad T480). Any ideas?