Skip to content

Instantly share code, notes, and snippets.

@martinlroth
Last active February 4, 2025 18:26
Show Gist options
  • Save martinlroth/cf001af25d060fc2d193c725def26588 to your computer and use it in GitHub Desktop.
Save martinlroth/cf001af25d060fc2d193c725def26588 to your computer and use it in GitHub Desktop.
Simple relay control for the Denkovi USB Relay Module 4 Channels, for Home Automation - v2
#!/usr/bin/python
#
# Simple relay control for the Denkovi USB Relay Module 4 Channels, for Home Automation - v2
# http://denkovi.com/usb-relay-board-four-channels-for-home-automation-v2
#
# Better tools are offered on http://denkovi.com but they need an internet
# connection, and are not open source.
#
__author__ = "Martin Roth"
__copyright__ = "Copyright 2017 Google Inc."
__license__ = "GPL v2"
__version__ = "0.0.1"
__email__ = "gaumless@gmail.com"
__status__ = "Development"
import usb.core
import usb.util
import sys
# Check for pyusb 1.0.0 - Lots of incompatibilities with older versions.
# We're not exiting, because it *COULD* work.
if hasattr(usb, 'version_info'):
if usb.version_info[0] < 1:
print "You probably want a newer version of pyusb"
else:
print "You probably want a newer version of pyusb"
# Set some global variables
list_devices = False # True if just listing devices
serial = 0 # Serial number from command line
serial_num = 0 # Serial number read from device
NewGPIO = -1 # Relay configuration from command line
reattach = [0,0,0] # Interfaces detatched from the kernel
# Define the command structures that are being used
get_status_cmd = chr(0x80)+chr(0x00)+chr(0x00)+chr(0x00) + \
chr(0x00)+chr(0x00)+chr(0x00)+chr(0x00) + \
chr(0x00)+chr(0x00)+chr(0x00)+chr(0x00) + \
chr(0x00)+chr(0x00)+chr(0x00)+chr(0x00)
cfg_cmd = chr(0x10)+chr(0x00)+chr(0x00)+chr(0x00) + \
chr(0x00)+chr(0x00)+chr(0x00)+chr(0x00) + \
chr(0x04)+chr(0xe1)+chr(0x00)+chr(0x00) + \
chr(0x00)+chr(0x00)+chr(0x00)+chr(0x00)
# Check command line arguments
if (len(sys.argv) == 1) or (sys.argv[1] == "--help"):
print "Usage: %s <list|serial No> <Hex GPIOs Enabled>" % sys.argv[0]
print "Examples:"
print " relay.py list - Shows all attached relay boards"
print " relay.py 0001652501 - Shows current relay status"
print " relay.py 0001652501 0x0f - Enable all relays"
print " relay.py 0001652501 0 - Disable all relays"
print " relay.py 0001652501 8 - Enable just relay 4"
exit(0)
if sys.argv[1] == 'list':
list_devices = True
else:
serial = sys.argv[1]
if (list_devices == False) and (len(sys.argv) > 2):
NewGPIO = int(sys.argv[2], 16)
# Find all the MCP2200 devices
usbdevs = usb.core.find(find_all=True, idVendor=0x04d8, idProduct=0x00df)
# Loop through and print devices found or select requested device
for dev in usbdevs:
serial_num = usb.util.get_string(dev,dev.iSerialNumber)
if list_devices == True:
print "Serial Number: ", serial_num
elif serial_num == serial:
break
# Make sure we found specified device, or exit if we were just listing
if serial_num == 0:
print "No devices found"
exit(1)
elif list_devices == True:
exit(0)
elif serial_num != serial:
print "Error: Serial Number %s not found." % serial
exit(1)
# Get the configuration, and detach every interface from the kernel
# We get a 'usb.core.USBError: [Errno 16] Resource busy' error otherwise
# Save the interfaces we detached for re-attachment later
cfg = dev.get_active_configuration()
for i in range(cfg.bNumInterfaces):
if dev.is_kernel_driver_active(i):
reattach[i]=1
dev.detach_kernel_driver(i)
# Because we know the device, just set the endpoints directly
epin = dev[0][(2,0)][0]
epout = dev[0][(2,0)][1]
# No idea why this would happen, but hey, checking is good.
if (epin is None) or (epout is None):
print "endpoint not found."
exit(1)
# Get the current status of the relays
dev.write(epout.bEndpointAddress, get_status_cmd)
status = dev.read(epin.bEndpointAddress,16,1000)
# Set the relays if requested
if NewGPIO != -1:
dev.write(epout.bEndpointAddress, cfg_cmd[:6] + chr(status[6]) + cfg_cmd[7:])
dev.write(epout.bEndpointAddress, cfg_cmd[:6] + chr(NewGPIO) + cfg_cmd[7:])
dev.write(epout.bEndpointAddress, get_status_cmd)
status = dev.read(epin.bEndpointAddress,16,1000)
# Display the current value of the GPIOs / Relays
print "0x%02x"%status[6]
# Clean up and release the device
usb.util.dispose_resources(dev)
for i in range(cfg.bNumInterfaces):
if reattach[i] == 1:
dev.attach_kernel_driver(i)
exit(0)
@RufusVS
Copy link

RufusVS commented Mar 21, 2022

Was this for a linux or mac system? I tried running it on a Windows 10 system and pyusb is unable to enumerate the USB devices.
Working on solving that. I may try it on a linux system too.
(Edit: problem solved. See later comment.)

@martinlroth
Copy link
Author

martinlroth commented Mar 21, 2022 via email

@RufusVS
Copy link

RufusVS commented Mar 21, 2022

I've been muddling about with the python hid module and hidapi. After muddling around with cmake and building hidapi (I think it was my efforts) I was able to import hid module. So far I have been able to open and read the status, now I am about to write the relays. I'll summarize and send you test code later, though I don't know if I have the energy to go back and figure out the hidapi install I got through, though if I install on another windows machine, I'll pay more attention.

Edit: Stupid me, I didn't have my proper libusb.dll installed. I was able to just download the binary from sourceforge thanks to an Adafruit learn guide...

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment