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