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