85fe04b8 |
1 | // Command line tool for low-cost 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 | |
645340be |
96 | i = (int)strlen(buffer); |
56c8dcf9 |
97 | if ( i != (int)strlen(productName) + 1 ) |
f6b405fc |
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; |
b60fbf7e |
170 | err = usbhidEnumDevices(USB_CFG_VENDOR_ID, USB_CFG_DEVICE_ID, &g_enumCtx, enumFunc); |
171 | |
f6b405fc |
172 | if ( err || !g_enumCtx.mydev ) |
173 | { |
fe63975d |
174 | printerr("error finding USB relay: %s\n", usbErrorMessage(err)); |
f6b405fc |
175 | return NULL; |
176 | } |
b60fbf7e |
177 | |
178 | return g_enumCtx.mydev; |
6629800a |
179 | } |
180 | |
6629800a |
181 | |
6629800a |
182 | // Read state of all relays |
183 | // @return bit mask of all relays (R1->bit 0, R2->bit 1 ...) or -1 on error |
1c892392 |
184 | static int rel_read_status_raw(USBDEVHANDLE dev, void *raw_data) |
6629800a |
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 ) { |
fe63975d |
194 | printerr("error reading status: %s\n", usbErrorMessage(err)); |
6629800a |
195 | return -1; |
196 | } |
197 | |
198 | if ( len != 9 || buffer[0] != reportnum ) { |
fe63975d |
199 | printerr("ERROR: wrong HID report returned! %d\n", len); |
6629800a |
200 | return -2; |
201 | } |
202 | |
203 | if (raw_data) { |
1c892392 |
204 | /* copy raw report data */ |
fe63975d |
205 | memcpy( raw_data, buffer, len ); |
6629800a |
206 | } |
207 | |
b60fbf7e |
208 | return (unsigned char)buffer[8]; /* byte of relay states */ |
6629800a |
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 | |
28f22397 |
218 | if ( numstr && (0 == strcasecmp(numstr,"all")) ) { |
6629800a |
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 ) { |
fe63975d |
230 | printerr("Invalid relay number. Must be 1-%d or ALL)\n", g_max_relay_num); |
6629800a |
231 | return 1; |
232 | } |
233 | |
234 | memset(buffer, 0, sizeof(buffer)); |
235 | buffer[0] = 0; /* report # */ |
236 | buffer[1] = is_on ? 0xFF : 0xFD; |
fe63975d |
237 | // ALL ON=0xFE, ALL OFF=0xFC |
6629800a |
238 | buffer[2] = (unsigned char)relaynum; |
28f22397 |
239 | if((err = usbhidSetReport(dev, (void*)buffer, 9)) != 0) { |
fe63975d |
240 | printerr("Error writing data: %s\n", usbErrorMessage(err)); |
6629800a |
241 | return 1; |
242 | } |
243 | |
244 | // Read back & verify |
1c892392 |
245 | err = rel_read_status_raw(dev, NULL); |
6629800a |
246 | if ( err >= 0 ) { |
247 | err = (err >> (unsigned)(relaynum -1)) & 1; |
248 | err ^= !!is_on; |
249 | } |
250 | |
251 | if ( err ) { |
fe63975d |
252 | printerr("Error: failed to set relay %u %s\n", relaynum, is_on ? "ON":"OFF"); |
6629800a |
253 | return 1; |
254 | } |
255 | |
256 | return 0; |
257 | } |
258 | |
259 | |
1c892392 |
260 | static int show_status(USBDEVHANDLE dev) |
261 | { |
262 | int err; |
263 | char buffer[10]; |
f6b405fc |
264 | static const char* on_off[] = {"OFF","ON"}; |
1c892392 |
265 | |
266 | #define onoff(n) on_off[!!(err & (1U << n))] |
267 | |
268 | err = rel_read_status_raw(dev, buffer); |
fe63975d |
269 | if ( err < 0 ) { |
270 | printerr("Error reading data: %s\n", usbErrorMessage(err)); |
1c892392 |
271 | err = 1; |
272 | } else { |
1c892392 |
273 | switch (g_max_relay_num) { |
274 | case 1: |
275 | printf("Board ID=[%5.5s] State: R1=%s\n", &buffer[1], onoff(0) ); |
f6b405fc |
276 | break; |
1c892392 |
277 | case 2: |
278 | printf("Board ID=[%5.5s] State: R1=%s R2=%s\n", |
f6b405fc |
279 | &buffer[1], onoff(0), onoff(1) ); |
280 | break; |
1c892392 |
281 | case 4: |
282 | printf("Board ID=[%5.5s] State: R1=%s R3=%s R1=%s R4=%s\n", |
f6b405fc |
283 | &buffer[1], onoff(0), onoff(1), onoff(2), onoff(3) ); |
284 | break; |
fe63975d |
285 | default: /* print as bit mask */ |
f6b405fc |
286 | printf("Board ID=[%5.5s] State: %2.2X (hex)\n", &buffer[1], (unsigned char)err ); |
287 | break; |
1c892392 |
288 | } |
289 | err = 0; |
290 | } |
291 | return err; |
292 | #undef onoff |
293 | } |
294 | |
b60fbf7e |
295 | // Enumerate available relay devices |
296 | |
297 | static int showFunc(USBDEVHANDLE dev, void *context) |
298 | { |
299 | int err = enumFunc( dev, context ); |
f6b405fc |
300 | if (err != 0 || g_enumCtx.mydev == 0) // not my device, continue |
301 | return err; |
b60fbf7e |
302 | |
303 | show_status(g_enumCtx.mydev); |
f6b405fc |
304 | usbhidCloseDevice(g_enumCtx.mydev); |
305 | g_enumCtx.mydev = 0; |
b60fbf7e |
306 | |
f6b405fc |
307 | return 1; // continue |
b60fbf7e |
308 | } |
309 | |
310 | static int show_relays(void) |
311 | { |
312 | int err; |
f6b405fc |
313 | g_enumCtx.mydev = 0; |
b60fbf7e |
314 | |
f6b405fc |
315 | err = usbhidEnumDevices(USB_CFG_VENDOR_ID, USB_CFG_DEVICE_ID, &g_enumCtx, showFunc); |
316 | if ( err ) |
317 | { |
fe63975d |
318 | printerr("Error finding USB relays: %s\n", usbErrorMessage(err)); |
f6b405fc |
319 | return 1; |
320 | } |
b60fbf7e |
321 | |
322 | return 0; |
323 | } |
324 | |
325 | |
6629800a |
326 | int main(int argc, char **argv) |
327 | { |
1c892392 |
328 | USBDEVHANDLE dev = 0; |
6629800a |
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]); |
b60fbf7e |
335 | return 1; |
6629800a |
336 | } |
337 | |
b60fbf7e |
338 | if ( strcasecmp(arg1, "enum") == 0 ) { |
f6b405fc |
339 | err = show_relays(); |
340 | return err; |
341 | } |
342 | |
343 | if ( strncasecmp(arg1, "id=", 3) == 0 ) { |
645340be |
344 | /* Set the ID for following commands. else use 1st found device.*/ |
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 | |
645340be |
350 | strcpy(g_enumCtx.id, &arg1[3]); |
f6b405fc |
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); |
1c892392 |
363 | }else if( strcasecmp(arg1, "on" ) == 0) { |
6629800a |
364 | err = rel_onoff(dev, 1, arg2); |
1c892392 |
365 | }else if( strcasecmp(arg1, "off" ) == 0) { |
6629800a |
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 | |