Command line utility for Chinese USB HID relays
(USB 1.1 HID devices, VEN=5824 (0x16c0) DEV=1503 (0x05DF), based on the V-USB project)
-Rev. 1.2 (20-Nov-2014)
+Rev. 1.2 (22-Nov-2014)
Usage:
usbrelay-cmd [ID=XXXXX] ON|OFF <num> -- turn the relay ON or OFF
- where <num> is the relay number: 1-2 or "*" for all
+ where <num> is the relay number: 1-2 or "ALL"
- usbrelay-cmd STATE -- print state of one relay device with its "unique ID"
+ usbrelay-cmd STATUS -- print state of one relay device with its "unique ID"
usbrelay-cmd ENUM -- print state of all devices and their "unique IDs"
-Example:
+Parameter ID=XXXXX specifies one relay device if several are connected.
+Without the ID= parameter, if several devices are connected, the program uses any one of them.
+The ID string is case sensitive.
+
+Examples:
+
usbrelay-cmd id=ABCDE on 1
+ usbrelay-cmd id=ABCDE OFF ALL
+ usbrelay-cmd id=ABCDE STATUS
+
+ usbrelay-cmd ON ALL -- uses first found relay device
+ usbrelay-cmd STATUS -- uses first found relay device
-Note: Parameter ID=XXXXX specifies one relay device if several are connected.
-The ID string is case sensitive.
-The Enum command lists IDs of all connected devices.
-Without the ID= parameter, if several devices are connected, the program uses any one of them.
+REMARKS
+The Enum command lists the IDs of all available relay devices.
+The ID strings are case sensitive and should be exactly as output by the "enum" command.
Each relay has two contact pairs: Normally Open and Normally Closed.
The OFF state is the "normal" state, when the red LED is off, and the Normally Open contacts are disconnected.
The ON state is when the red LED is on, and the Normally Open contacts are connected.
Note: In the original s/w readme, "Open" means ON, "Close" means OFF.
-
-Currently tested with 1 and 2-relay devices.
-SOURCE based on a V-USB HID example.
-LICENSE: TBD!
- Should be non-GPL; rewrite all prototype code!
+This program has been tested with 1- and 2-relay devices.
+On Linux, this program requires root access by default!
+To grant access to non-root users, define udev rule for the devices:
+Example:
+ SUBSYSTEMS=="usb", ATTRS{idVendor}=="16c0", ATTRS{idProduct}=="05df", MODE:="0666"
-For Windows XP and later:
- - using the in-box hid.dll and WDK 7.1.
- - build with VC++ 2008
-For Linux (PC, x86):
- - using the old libusb (v 0.1; usb.h)
- - tested on Centos 5, Ubuntu 12.04
- - requires root!
- To grant access to users, define udev rule for this device (or all HID). Example:
- SUBSYSTEMS=="usb", ATTRS{idVendor}=="16c0", ATTRS{idProduct}=="05df", MODE:="0666"
+BUILDING
+
+For Windows XP and later:
+ - Using the in-box hid.dll
+ - Build with VC++ 2008 and WDK 7.1, or newer VC++ with the suitable WDK
+For Linux (PC, x86):
+ - using the old libusb (v. 0.1; usb.h)
+ - tested on Centos 5, Ubuntu 12.04, Mint
+
+SOURCE based on a V-USB HID example.
+LICENSE: TBD!
+ Should be non-GPL; rewrite all prototype code!
+
>>> Target for 1st public release:
================================
1. Remove GPL encumberment
Other TODO's
============
-TODO: support multiple devices, support other existing variants
- usbrelay-cmd -ID=XXXXX ON|OFF <num>
- usbrelay-cmd -ID=XXXXX STATE
- or -s XXXXX or --SN
- -ID=* --ANY any one?
*** Check that these devices indeed have unique IDs!
The orig. h file mentions function: usb_relay_device_set_serial(int hHandle, char serial[5]);
#include "hiddata.h"
#include <stdio.h>
#include <string.h>
+#include <errno.h>
#if 0 //ifdef DEBUG
#define DEBUG_PRINT(arg) printf arg
}
-
void usbhidCloseDevice(USBDEVHANDLE usbh)
{
CloseHandle((HANDLE)usbh);
}
-
int usbhidSetReport(USBDEVHANDLE usbh, char *buffer, int len)
{
BOOLEAN rval;
#else /* defined WIN32 #################################################### */
/* ######################################################################## */
+// Using the old simple version of libusb (before 1.0)
#include <usb.h>
-#define usbDevice usb_dev_handle /* use libusb's device structure */
+// USBDEVHANDLE is same as struct usb_device *
+// Open handles are stored in struct usb_device (HACK! revise for new libusb)
+
+static inline struct usb_device * usbDevStruct(USBDEVHANDLE usbh) {
+ return (struct usb_device *)usbh;
+}
+
+static inline usb_dev_handle * usbDevHandle(USBDEVHANDLE usbh) {
+ return (usb_dev_handle *)(usbDevStruct(usbh)->children);
+}
+
+// TO DO: for devices that prepend report ID, implement this somehow
+static inline int usesReportIDs(USBDEVHANDLE usbh) {
+ /* return 1 if 1st byte of get/set report buffer is report ID */
+ return 0;
+}
/* ------------------------------------------------------------------------- */
#define USB_HID_REPORT_TYPE_FEATURE 3
-
-static int usesReportIDs = 0; /* 1 => 1st byte of get/set report buffer is report ID */
+#define A_REPORT_REQUEST_TIMEOUT 5000 /* in milliseconds */
/* ------------------------------------------------------------------------- */
-static int usbhidGetStringAscii(usb_dev_handle *dev, int index, char *buf, int buflen)
-{
-char buffer[256];
-int rval, i;
-
- if((rval = usb_get_string_simple(dev, index, buf, buflen)) >= 0) /* use libusb version if it works */
- return rval;
- if((rval = usb_control_msg(dev, USB_ENDPOINT_IN, USB_REQ_GET_DESCRIPTOR, (USB_DT_STRING << 8) + index, 0x0409, buffer, sizeof(buffer), 5000)) < 0)
- return rval;
- if(buffer[1] != USB_DT_STRING){
- *buf = 0;
- return 0;
- }
- if((unsigned char)buffer[0] < rval)
- rval = (unsigned char)buffer[0];
- rval /= 2;
- /* lossy conversion to ISO Latin1: */
- for(i=1;i<rval;i++){
- if(i > buflen) /* destination buffer overflow */
- break;
- buf[i-1] = buffer[2 * i];
- if(buffer[2 * i + 1] != 0) /* outside of ISO Latin1 range */
- buf[i-1] = '?';
- }
- buf[i-1] = 0;
- return i-1;
-}
+int usbhidEnumDevices(int vendor, int product,
+ void *context,
+ int (*usbhidEnumFunc)(USBDEVHANDLE usbh, void *ctx))
-int usbhidOpenDevice(usbDevice_t **device, int vendor, char *vendorName, int product, char *productName, int _usesReportIDs)
{
-struct usb_bus *bus;
-struct usb_device *dev;
-usb_dev_handle *handle = NULL;
-int errorCode = USBOPEN_ERR_NOTFOUND;
-static int didUsbInit = 0;
+ struct usb_bus *bus;
+ struct usb_device *dev;
+ usb_dev_handle *handle = NULL;
+ int errorCode = USBOPEN_ERR_NOTFOUND;
+ static int didUsbInit = 0;
if(!didUsbInit){
usb_init();
}
usb_find_busses();
usb_find_devices();
- for(bus=usb_get_busses(); bus; bus=bus->next){
- for(dev=bus->devices; dev; dev=dev->next){
- if(dev->descriptor.idVendor == vendor && dev->descriptor.idProduct == product){
- char string[256];
- int len;
+ for (bus=usb_get_busses(); bus; bus=bus->next) {
+ for (dev=bus->devices; dev; dev=dev->next) {
+ if (dev->descriptor.idVendor == vendor && dev->descriptor.idProduct == product) {
handle = usb_open(dev); /* we need to open the device in order to query strings */
- if(!handle){
+ if ( !handle ) {
errorCode = USBOPEN_ERR_ACCESS;
fprintf(stderr, "Warning: cannot open USB device: %s\n", usb_strerror());
continue;
}
- if(vendorName == NULL && productName == NULL){ /* name does not matter */
- break;
+
+ errorCode = 0;
+
+// printf("Probing: [%s] nc=%u %p\n", dev->filename, dev->num_children, dev->children);
+ // Assume our devices are leaf, so we can use dev->children to store the handle
+ if ( dev->children ) {
+ fprintf(stderr, "ERROR: assertion failed for usb dev %p\n", dev);
+ usb_close(handle);
+ handle = NULL;
+ continue;
}
- /* now check whether the names match: */
- len = usbhidGetStringAscii(handle, dev->descriptor.iManufacturer, string, sizeof(string));
- if(len < 0){
- errorCode = USBOPEN_ERR_IO;
- fprintf(stderr, "Warning: cannot query manufacturer for device: %s\n", usb_strerror());
- }else{
- errorCode = USBOPEN_ERR_NOTFOUND;
- /* fprintf(stderr, "seen device from vendor ->%s<-\n", string); */
- if(strcmp(string, vendorName) == 0){
- len = usbhidGetStringAscii(handle, dev->descriptor.iProduct, string, sizeof(string));
- if(len < 0){
- errorCode = USBOPEN_ERR_IO;
- fprintf(stderr, "Warning: cannot query product for device: %s\n", usb_strerror());
- }else{
- errorCode = USBOPEN_ERR_NOTFOUND;
- /* fprintf(stderr, "seen product ->%s<-\n", string); */
- if(strcmp(string, productName) == 0)
- break;
- }
- }
+
+ dev->children = (void*)handle;
+ if ( 0 == usbhidEnumFunc((void*)dev, context) )
+ {
+ break; /* stop enumeration */
}
- usb_close(handle);
- handle = NULL;
+
+ /* Now the handle is owned by the callback */
+ handle = 0;
}
}
- if(handle)
- break;
- }
- if(handle != NULL){
- errorCode = 0;
- *device = (void *)handle;
- usesReportIDs = _usesReportIDs;
}
+
return errorCode;
}
/* ------------------------------------------------------------------------- */
-void usbhidCloseDevice(usbDevice_t *device)
+void usbhidCloseDevice(USBDEVHANDLE usbh)
{
- if(device != NULL)
- usb_close((void *)device);
+ if (usbh != NULL) {
+ usb_close(usbDevHandle(usbh));
+ usbDevStruct(usbh)->children = NULL;
+ }
}
/* ------------------------------------------------------------------------- */
-int usbhidSetReport(usbDevice_t *device, char *buffer, int len)
+static int usbhidGetStringAscii(struct usb_dev_handle *dev, int index, char *buf, int buflen)
+{
+ int rval;
+ if((rval = usb_get_string_simple(dev, index, buf, buflen)) >= 0) /* use libusb version if it works */
+ return rval;
+ if (errno == EPERM)
+ fprintf(stderr, "usbhid: Access denied to USB device. Run as root or adjust device permissions.\n");
+ else
+ fprintf(stderr, "usbhid: %s error %s\n", __FUNCTION__, usb_strerror());
+ return -1;
+}
+
+int usbhidGetVendorString(USBDEVHANDLE usbh, char *buffer, int len)
+{
+ int len2 = usbhidGetStringAscii(usbDevHandle(usbh), usbDevStruct(usbh)->descriptor.iManufacturer, buffer, len);
+ if (len2 < 0) {
+ fprintf(stderr, "Warning: cannot query vendor for device\n");
+ return USBOPEN_ERR_IO;
+ }
+ return 0;
+}
+
+int usbhidGetProductString(USBDEVHANDLE usbh, char *buffer, int len)
+{
+ int len2 = usbhidGetStringAscii(usbDevHandle(usbh), usbDevStruct(usbh)->descriptor.iProduct, buffer, len);
+ if (len2 < 0) {
+ fprintf(stderr, "Warning: cannot query product for device\n");
+ return USBOPEN_ERR_IO;
+ }
+ return 0;
+}
+
+
+
+
+int usbhidSetReport(USBDEVHANDLE usbh, char *buffer, int len)
{
-int bytesSent, reportId = buffer[0];
+ int bytesSent, reportId = buffer[0];
- if(!usesReportIDs){
+ if ( !usesReportIDs(usbh) ) {
buffer++; /* skip dummy report ID */
len--;
}
- bytesSent = usb_control_msg((void *)device, USB_TYPE_CLASS | USB_RECIP_DEVICE | USB_ENDPOINT_OUT, USBRQ_HID_SET_REPORT, USB_HID_REPORT_TYPE_FEATURE << 8 | (reportId & 0xff), 0, buffer, len, 5000);
- if(bytesSent != len){
- if(bytesSent < 0)
+ bytesSent = usb_control_msg(usbDevHandle(usbh),
+ USB_TYPE_CLASS | USB_RECIP_DEVICE | USB_ENDPOINT_OUT, USBRQ_HID_SET_REPORT, USB_HID_REPORT_TYPE_FEATURE << 8 | (reportId & 0xff),
+ 0, buffer, len, A_REPORT_REQUEST_TIMEOUT);
+ if (bytesSent != len) {
+ if (bytesSent < 0)
fprintf(stderr, "Error sending message: %s\n", usb_strerror());
return USBOPEN_ERR_IO;
}
/* ------------------------------------------------------------------------- */
-int usbhidGetReport(usbDevice_t *device, int reportNumber, char *buffer, int *len)
+int usbhidGetReport(USBDEVHANDLE usbh, int reportNumber, char *buffer, int *len)
{
int bytesReceived, maxLen = *len;
- if(!usesReportIDs){
+ if (!usesReportIDs(usbh)) {
buffer++; /* make room for dummy report ID */
maxLen--;
}
- bytesReceived = usb_control_msg((void *)device, USB_TYPE_CLASS | USB_RECIP_DEVICE | USB_ENDPOINT_IN, USBRQ_HID_GET_REPORT, USB_HID_REPORT_TYPE_FEATURE << 8 | reportNumber, 0, buffer, maxLen, 5000);
+ bytesReceived = usb_control_msg(usbDevHandle(usbh),
+ USB_TYPE_CLASS | USB_RECIP_DEVICE | USB_ENDPOINT_IN, USBRQ_HID_GET_REPORT, USB_HID_REPORT_TYPE_FEATURE << 8 | reportNumber,
+ 0, buffer, maxLen, A_REPORT_REQUEST_TIMEOUT);
if(bytesReceived < 0){
fprintf(stderr, "Error sending message: %s\n", usb_strerror());
return USBOPEN_ERR_IO;
}
*len = bytesReceived;
- if(!usesReportIDs){
+ if (!usesReportIDs(usbh)) {
buffer[-1] = reportNumber; /* add dummy report ID */
(*len)++;
}
return 0;
}
+
+void usbhidSetUsesReportId(USBDEVHANDLE usbh)
+{
+ //TODO Implement if some devices prepend report IDs
+}
+
/* ######################################################################## */
#endif /* WIN32 */
/* ######################################################################## */