| 1 | // Command line tool for low-cost USB/HID relays |
| 2 | // |
| 3 | // pa02 20-Nov-2014 supports 1,2,4,8 - relay devices |
| 4 | // |
| 5 | //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
| 6 | |
| 7 | /* Prototype: V-USB example: vusb-20121206/examples/hid-data/commandline/hidtool.c |
| 8 | * Author: Christian Starkjohann |
| 9 | * Creation Date: 2008-04-11 |
| 10 | * Copyright: (c) 2008 by OBJECTIVE DEVELOPMENT Software GmbH |
| 11 | */ |
| 12 | |
| 13 | #define A_VER_STR "r1.4" |
| 14 | #define A_URL "http://vusb.wikidot.com/project:driver-less-usb-relays-hid-interface" |
| 15 | |
| 16 | #include <stdio.h> |
| 17 | #include <string.h> |
| 18 | #include <stdlib.h> |
| 19 | #include "hidusb-tool.h" |
| 20 | |
| 21 | #define USB_RELAY_VENDOR_NAME "www.dcttech.com" |
| 22 | #define USB_RELAY_NAME_PREF "USBRelay" // + number |
| 23 | |
| 24 | #define USB_RELAY_ID_STR_LEN 5 /* length of "unique serial number" in the devices */ |
| 25 | |
| 26 | static int rel_read_status_raw(USBDEVHANDLE dev, void *raw_data); |
| 27 | |
| 28 | #define printerr(fmt, ...) fprintf(stderr, fmt, ## __VA_ARGS__); |
| 29 | |
| 30 | /* ------------------------------------------------------------------------- */ |
| 31 | |
| 32 | static void usage(char *myName) |
| 33 | { |
| 34 | char *p = strrchr(myName, '\\'); /* windows */ |
| 35 | if (p) myName = p + 1; |
| 36 | else p = strrchr(myName, '/'); /* whatever */ |
| 37 | if (p) myName = p + 1; |
| 38 | |
| 39 | printf("HID USB relay utility, " A_VER_STR " " |
| 40 | "For info: " A_URL "\n" |
| 41 | "Usage:\n"); |
| 42 | printf(" %s on <num> - turn relay <num> ON\n", myName); |
| 43 | printf(" %s off <num> - turn relay <num> OFF\n", myName); |
| 44 | printf(" %s state - print states of the relays\n", myName); |
| 45 | printf(" %s enum - print state of all found relay devices\n", myName); |
| 46 | printf("\nParameter ID=XXXXX selects one device if several are connected.\n"); |
| 47 | printf("Example: %s ID=ABCDE ON 2\n\n", myName); |
| 48 | } |
| 49 | |
| 50 | |
| 51 | static const char *usbErrorMessage(int errCode) |
| 52 | { |
| 53 | static char buffer[80]; |
| 54 | buffer[0] = 0; |
| 55 | if ( errCode != USBHID_ERR_UNKNOWN ) { |
| 56 | usbhidStrerror_r(errCode, buffer, sizeof(buffer)); |
| 57 | } |
| 58 | if ( 0 == buffer[0] ) { |
| 59 | snprintf(buffer, sizeof(buffer), "Unknown error (%d)", errCode); |
| 60 | } |
| 61 | return buffer; |
| 62 | } |
| 63 | |
| 64 | // Data for enumeration func: |
| 65 | static struct |
| 66 | { |
| 67 | USBDEVHANDLE mydev; |
| 68 | char id[USB_RELAY_ID_STR_LEN * 2]; |
| 69 | } g_enumCtx; |
| 70 | |
| 71 | |
| 72 | static int g_max_relay_num = 0; // number of relays in the active device |
| 73 | |
| 74 | |
| 75 | static int enumFunc(USBDEVHANDLE dev, void *context) |
| 76 | { |
| 77 | static const char vendorName[] = USB_RELAY_VENDOR_NAME; |
| 78 | static const char productName[] = USB_RELAY_NAME_PREF; |
| 79 | int err; |
| 80 | char buffer[128*sizeof(short)]; // max USB string is 128 UTF-16 chars |
| 81 | int num = 0; |
| 82 | int i; |
| 83 | |
| 84 | err = usbhidGetVendorString(dev, buffer, sizeof(buffer)); |
| 85 | if ( err || 0 != strcmp( buffer, vendorName) ) |
| 86 | { |
| 87 | goto next; |
| 88 | } |
| 89 | |
| 90 | err = usbhidGetProductString(dev, buffer, sizeof(buffer)); |
| 91 | if (err) |
| 92 | { |
| 93 | goto next; |
| 94 | } |
| 95 | |
| 96 | i = (int)strlen(buffer); |
| 97 | if ( i != (int)strlen(productName) + 1 ) |
| 98 | { |
| 99 | goto next; |
| 100 | } |
| 101 | |
| 102 | /* the last char of ProductString is number of relays */ |
| 103 | num = (int)(buffer[i - 1]) - (int)'0'; |
| 104 | buffer[i - 1] = 0; |
| 105 | |
| 106 | if ( 0 != strcmp( buffer, productName) ) |
| 107 | { |
| 108 | goto next; |
| 109 | } |
| 110 | |
| 111 | if ( num <= 0 || num > 8 ) |
| 112 | { |
| 113 | printerr("Unknown relay device? num relays=%d\n", num); |
| 114 | goto next; |
| 115 | } |
| 116 | |
| 117 | /* Check the unique ID: USB_RELAY_ID_STR_LEN bytes at offset 1 (just after the report id) */ |
| 118 | err = rel_read_status_raw(dev, buffer); |
| 119 | if( err < 0 ) |
| 120 | { |
| 121 | printerr("Error reading report 0: %s\n", usbErrorMessage(err)); |
| 122 | goto next; |
| 123 | } |
| 124 | |
| 125 | for (i = 1; i <= USB_RELAY_ID_STR_LEN; i++) |
| 126 | { |
| 127 | unsigned char x = (unsigned char)buffer[i]; |
| 128 | if (x <= 0x20 || x >= 0x7F) |
| 129 | { |
| 130 | fprintf(stderr, "Bad device ID!\n"); |
| 131 | goto next; |
| 132 | } |
| 133 | } |
| 134 | |
| 135 | if( buffer[USB_RELAY_ID_STR_LEN + 1] != 0 ) |
| 136 | { |
| 137 | printerr("Bad device ID!\n"); |
| 138 | goto next; |
| 139 | } |
| 140 | |
| 141 | DEBUG_PRINT(("Device %s%d found: ID=[%5s]\n", productName, num, &buffer[1])); |
| 142 | g_max_relay_num = num; |
| 143 | |
| 144 | if ( g_enumCtx.id[0] != 0 ) |
| 145 | { |
| 146 | if ( 0 != memcmp(g_enumCtx.id, &buffer[1], USB_RELAY_ID_STR_LEN) ) |
| 147 | goto next; |
| 148 | } |
| 149 | #if 0 |
| 150 | if ( g_enumCtx.mydev ) |
| 151 | { |
| 152 | printerr("ERROR: More than one relay device found. ID must be specified\n"); |
| 153 | usbhidCloseDevice(dev); |
| 154 | usbhidCloseDevice(g_enumCtx.mydev); |
| 155 | return 0; |
| 156 | } |
| 157 | #endif |
| 158 | g_enumCtx.mydev = dev; |
| 159 | return 0; /* stop */ |
| 160 | |
| 161 | next: |
| 162 | /* Continue search */ |
| 163 | usbhidCloseDevice(dev); |
| 164 | return 1; |
| 165 | } |
| 166 | |
| 167 | static USBDEVHANDLE openDevice(void) |
| 168 | { |
| 169 | int err; |
| 170 | err = usbhidEnumDevices(USB_CFG_VENDOR_ID, USB_CFG_DEVICE_ID, &g_enumCtx, enumFunc); |
| 171 | |
| 172 | if ( err || !g_enumCtx.mydev ) |
| 173 | { |
| 174 | printerr("error finding USB relay: %s\n", usbErrorMessage(err)); |
| 175 | return NULL; |
| 176 | } |
| 177 | |
| 178 | return g_enumCtx.mydev; |
| 179 | } |
| 180 | |
| 181 | |
| 182 | // Read state of all relays |
| 183 | // @return bit mask of all relays (R1->bit 0, R2->bit 1 ...) or -1 on error |
| 184 | static int rel_read_status_raw(USBDEVHANDLE dev, void *raw_data) |
| 185 | { |
| 186 | char buffer[10]; |
| 187 | int err; |
| 188 | int reportnum = 0; |
| 189 | int len = 8 + 1; /* report id 1 byte + 8 bytes data */ |
| 190 | memset(buffer, 0, sizeof(buffer)); |
| 191 | |
| 192 | err = usbhidGetReport(dev, reportnum, buffer, &len); |
| 193 | if ( err ) { |
| 194 | printerr("error reading status: %s\n", usbErrorMessage(err)); |
| 195 | return -1; |
| 196 | } |
| 197 | |
| 198 | if ( len != 9 || buffer[0] != reportnum ) { |
| 199 | printerr("ERROR: wrong HID report returned! %d\n", len); |
| 200 | return -2; |
| 201 | } |
| 202 | |
| 203 | if (raw_data) { |
| 204 | /* copy raw report data */ |
| 205 | memcpy( raw_data, buffer, len ); |
| 206 | } |
| 207 | |
| 208 | return (unsigned char)buffer[8]; /* byte of relay states */ |
| 209 | } |
| 210 | |
| 211 | |
| 212 | static int rel_onoff( USBDEVHANDLE dev, int is_on, char const *numstr ) |
| 213 | { |
| 214 | unsigned char buffer[10]; |
| 215 | int err = -1; |
| 216 | int relaynum = numstr ? atoi(numstr) : 0; |
| 217 | |
| 218 | if ( numstr && (0 == strcasecmp(numstr,"all")) ) { |
| 219 | char x[2] = {'1', 0}; |
| 220 | int i; |
| 221 | for (i = 1; i <= g_max_relay_num; i++) { |
| 222 | x[0] = (char)('0' + i); |
| 223 | err = rel_onoff(dev, is_on, x); |
| 224 | if (err) break; |
| 225 | } |
| 226 | return err; |
| 227 | } |
| 228 | |
| 229 | if ( relaynum <= 0 || relaynum > g_max_relay_num ) { |
| 230 | printerr("Invalid relay number. Must be 1-%d or ALL)\n", g_max_relay_num); |
| 231 | return 1; |
| 232 | } |
| 233 | |
| 234 | memset(buffer, 0, sizeof(buffer)); |
| 235 | buffer[0] = 0; /* report # */ |
| 236 | buffer[1] = is_on ? 0xFF : 0xFD; |
| 237 | // ALL ON=0xFE, ALL OFF=0xFC |
| 238 | buffer[2] = (unsigned char)relaynum; |
| 239 | if((err = usbhidSetReport(dev, (void*)buffer, 9)) != 0) { |
| 240 | printerr("Error writing data: %s\n", usbErrorMessage(err)); |
| 241 | return 1; |
| 242 | } |
| 243 | |
| 244 | // Read back & verify |
| 245 | err = rel_read_status_raw(dev, NULL); |
| 246 | if ( err >= 0 ) { |
| 247 | err = (err >> (unsigned)(relaynum -1)) & 1; |
| 248 | err ^= !!is_on; |
| 249 | } |
| 250 | |
| 251 | if ( err ) { |
| 252 | printerr("Error: failed to set relay %u %s\n", relaynum, is_on ? "ON":"OFF"); |
| 253 | return 1; |
| 254 | } |
| 255 | |
| 256 | return 0; |
| 257 | } |
| 258 | |
| 259 | |
| 260 | static int show_status(USBDEVHANDLE dev) |
| 261 | { |
| 262 | int err; |
| 263 | char buffer[10]; |
| 264 | static const char* on_off[] = {"OFF","ON"}; |
| 265 | |
| 266 | #define onoff(n) on_off[!!(err & (1U << n))] |
| 267 | |
| 268 | err = rel_read_status_raw(dev, buffer); |
| 269 | if ( err < 0 ) { |
| 270 | printerr("Error reading data: %s\n", usbErrorMessage(err)); |
| 271 | err = 1; |
| 272 | } else { |
| 273 | switch (g_max_relay_num) { |
| 274 | case 1: |
| 275 | printf("Board ID=[%5.5s] State: R1=%s\n", &buffer[1], onoff(0) ); |
| 276 | break; |
| 277 | case 2: |
| 278 | printf("Board ID=[%5.5s] State: R1=%s R2=%s\n", |
| 279 | &buffer[1], onoff(0), onoff(1) ); |
| 280 | break; |
| 281 | case 4: |
| 282 | printf("Board ID=[%5.5s] State: R1=%s R3=%s R1=%s R4=%s\n", |
| 283 | &buffer[1], onoff(0), onoff(1), onoff(2), onoff(3) ); |
| 284 | break; |
| 285 | default: /* print as bit mask */ |
| 286 | printf("Board ID=[%5.5s] State: %2.2X (hex)\n", &buffer[1], (unsigned char)err ); |
| 287 | break; |
| 288 | } |
| 289 | err = 0; |
| 290 | } |
| 291 | return err; |
| 292 | #undef onoff |
| 293 | } |
| 294 | |
| 295 | // Enumerate available relay devices |
| 296 | |
| 297 | static int showFunc(USBDEVHANDLE dev, void *context) |
| 298 | { |
| 299 | int err = enumFunc( dev, context ); |
| 300 | if (err != 0 || g_enumCtx.mydev == 0) // not my device, continue |
| 301 | return err; |
| 302 | |
| 303 | show_status(g_enumCtx.mydev); |
| 304 | usbhidCloseDevice(g_enumCtx.mydev); |
| 305 | g_enumCtx.mydev = 0; |
| 306 | |
| 307 | return 1; // continue |
| 308 | } |
| 309 | |
| 310 | static int show_relays(void) |
| 311 | { |
| 312 | int err; |
| 313 | g_enumCtx.mydev = 0; |
| 314 | |
| 315 | err = usbhidEnumDevices(USB_CFG_VENDOR_ID, USB_CFG_DEVICE_ID, &g_enumCtx, showFunc); |
| 316 | if ( err ) |
| 317 | { |
| 318 | printerr("Error finding USB relays: %s\n", usbErrorMessage(err)); |
| 319 | return 1; |
| 320 | } |
| 321 | |
| 322 | return 0; |
| 323 | } |
| 324 | |
| 325 | |
| 326 | int main(int argc, char **argv) |
| 327 | { |
| 328 | USBDEVHANDLE dev = 0; |
| 329 | int err; |
| 330 | char const *arg1 = (argc >= 2) ? argv[1] : NULL; |
| 331 | char const *arg2 = (argc >= 3) ? argv[2] : NULL; |
| 332 | |
| 333 | if ( !arg1 ) { |
| 334 | usage(argv[0]); |
| 335 | return 1; |
| 336 | } |
| 337 | |
| 338 | if ( strcasecmp(arg1, "enum") == 0 ) { |
| 339 | err = show_relays(); |
| 340 | return err; |
| 341 | } |
| 342 | |
| 343 | if ( strncasecmp(arg1, "id=", 3) == 0 ) { |
| 344 | /* Set the ID for following commands. else use 1st found device.*/ |
| 345 | if (strlen(&arg1[3]) != USB_RELAY_ID_STR_LEN) { |
| 346 | printerr("ERROR: ID must be %d characters (%s)\n", USB_RELAY_ID_STR_LEN, arg1); |
| 347 | return 1; |
| 348 | } |
| 349 | |
| 350 | strcpy(g_enumCtx.id, &arg1[3]); |
| 351 | |
| 352 | // shift following params |
| 353 | arg1 = arg2; |
| 354 | arg2 = (argc >= 4) ? argv[3] : NULL; |
| 355 | } |
| 356 | |
| 357 | dev = openDevice(); |
| 358 | if ( !dev ) |
| 359 | return 1; |
| 360 | |
| 361 | if ( strncasecmp(arg1, "stat", 4) == 0 ) { // stat|state|status |
| 362 | err = show_status(dev); |
| 363 | }else if( strcasecmp(arg1, "on" ) == 0) { |
| 364 | err = rel_onoff(dev, 1, arg2); |
| 365 | }else if( strcasecmp(arg1, "off" ) == 0) { |
| 366 | err = rel_onoff(dev, 0, arg2); |
| 367 | }else { |
| 368 | usage(argv[0]); |
| 369 | err = 2; |
| 370 | } |
| 371 | |
| 372 | if ( dev ) { |
| 373 | usbhidCloseDevice(dev); |
| 374 | } |
| 375 | |
| 376 | return err; |
| 377 | } |
| 378 | |