usb_relay_lib: added usb_relay_device_get_status_bitmap
[usb-relay-hid.git] / lib / usb_relay_lib.c
1 /**
2 * USB HID relays API library
3 * http://git.io/bGcxrQ
4 *
5 * This is reconstruction of the original Windows DLL,
6 * as supplied by the USB relay vendors.
7 * It is binary compatible and works with their example programs.
8 * The original usb_relay_device.h file has been slightly hacked up.
9 *
10 * 12-jan-2015 pa01 Win32 version
11 */
12
13 #define MY_VERSION 0x02
14
15 #if defined (WIN32) || defined (_WIN32)
16
17 // Windows 32 or 64 bit
18 #include "targetver.h"
19 #define WIN32_EXTRALEAN
20 #include <windows.h>
21
22 #ifdef _MSC_VER
23 /* The original DLL has cdecl calling convention */
24 #define USBRL_CALL __cdecl
25 #define USBRL_API __declspec(dllexport) USBRL_CALL
26
27 #define snprintf _snprintf
28 #endif // _MSC_VER
29 #endif //WIN32
30
31 #include "usb_relay_device.h"
32
33 #if USBRELAY_LIB_VER != MY_VERSION
34 #error "Oops. Wrong version of usb_relay_device.h"
35 #endif
36
37 #include "usb_relay_hw.h"
38 #include "hiddata.h"
39 #include <stdio.h>
40 #include <string.h>
41 #include <stdlib.h>
42
43 //#define dbgprintf(fmt, ...) fprintf(stderr, fmt, __VA_ARGS__)
44 //#define dbgprintf(fmt, ...) printf(fmt, __VA_ARGS__)
45 #define dbgprintf(fmt, ...) __noop(fmt, __VA_ARGS__)
46 //#define printerr(fmt, ...) fprintf(stderr, fmt, __VA_ARGS__)
47 //#define printerr(fmt, ...) printf(fmt, __VA_ARGS__)
48 #define printerr(fmt, ...) __noop(fmt, __VA_ARGS__)
49
50 #ifdef __cplusplus
51 extern "C" {
52 #endif
53
54 struct usbrelay_internal_s {
55 struct usb_relay_device_info urdi; //public part
56 // Private part:
57 USBDEVHANDLE usbh; // handle
58 char idstr[8];
59 };
60
61 // struct for enum context
62 struct enumctx_s {
63 struct usbrelay_internal_s *head, *tail;
64 int numdevs;
65 int status;
66 };
67
68 // Globals
69
70 const char *g_dummyPath = "NOTHING"; // passing dev.path to client not implemented, I return this as path.
71
72 static const char *usbErrorMessage(int errCode)
73 {
74 static char buffer[80];
75 buffer[0] = 0;
76 if ( errCode != USBHID_ERR_UNKNOWN ) {
77 usbhidStrerror_r(errCode, buffer, sizeof(buffer));
78 }
79 if ( 0 == buffer[0] ) {
80 snprintf(buffer, sizeof(buffer), "Unknown error (%d)", errCode);
81 }
82 return buffer;
83 }
84
85 // Read state of all relays
86 // @return bit mask of all relays (R1->bit 0, R2->bit 1 ...) or -1 on error
87 static int rel_read_status_raw(USBDEVHANDLE dev, void *raw_data)
88 {
89 char buffer[10];
90 int err;
91 int reportnum = 0;
92 int len = 8 + 1; /* report id 1 byte + 8 bytes data */
93 memset(buffer, 0, sizeof(buffer));
94
95 err = usbhidGetReport(dev, reportnum, buffer, &len);
96 if ( err ) {
97 printerr("error reading status: %s\n", usbErrorMessage(err));
98 return -1;
99 }
100
101 if ( len != 9 || buffer[0] != reportnum ) {
102 printerr("ERROR: wrong HID report returned! %d\n", len);
103 return -2;
104 }
105
106 if (raw_data) {
107 /* copy raw report data */
108 memcpy( raw_data, buffer, len );
109 }
110
111 return (unsigned char)buffer[8]; /* byte of relay states */
112 }
113
114 // Turn relay on/off.
115 // @param relaynum: positive (1-N): one relay index, negative: all, -num = number of relays
116 // @returns 0 ok, else error
117 static int rel_onoff( USBDEVHANDLE dev, int is_on, int relaynum )
118 {
119 unsigned char buffer[10];
120 int err = -1;
121 unsigned char cmd1, cmd2, mask, maskval;
122
123 if ( relaynum < 0 && (-relaynum) <= 8 ) {
124 mask = 0xFF;
125 cmd2 = 0;
126 if (is_on) {
127 cmd1 = 0xFE;
128 maskval = (unsigned char)( (1U << (-relaynum)) - 1 );
129 } else {
130 cmd1 = 0xFC;
131 maskval = 0;
132 }
133 } else {
134 if ( relaynum <= 0 || relaynum > 8 ) {
135 printerr("Relay number must be 1-8\n");
136 return 1;
137 }
138 mask = (unsigned char)(1U << relaynum);
139 cmd2 = (unsigned char)relaynum;
140 if (is_on) {
141 cmd1 = 0xFF;
142 maskval = mask;
143 } else {
144 cmd1 = 0xFD;
145 maskval = 0;
146 }
147 }
148
149 memset(buffer, 0, sizeof(buffer));
150 buffer[0] = 0; /* report # */
151 buffer[1] = cmd1;
152 buffer[2] = cmd2;
153 if((err = usbhidSetReport(dev, (void*)buffer, 9)) != 0) {
154 printerr("Error writing data: %s\n", usbErrorMessage(err));
155 return 1;
156 }
157
158 // Read back & verify
159 err = rel_read_status_raw(dev, NULL);
160 if ( err < 0 ) {
161 printerr("Error read back: %s\n", usbErrorMessage(err));
162 return 1;
163 }
164
165 err = err & mask;
166 if (err != maskval) {
167 printerr("Error: failed to set relay %u %s\n", relaynum, is_on ? "ON":"OFF");
168 return 1;
169 }
170
171 return 0;
172 }
173
174
175 // Public functions:
176
177 /** Initialize the USB Relay Library
178 @returns: This function returns 0 on success and -1 on error.
179 */
180 int USBRL_API usb_relay_init(void)
181 {
182 return 0;
183 }
184
185 /** Finalize the USB Relay Library.
186 This function frees all of the static data associated with USB Relay Library.
187 It should be called at the end of execution to avoid memory leaks.
188 @returns: This function returns 0 on success and -1 on error.
189 */
190 int USBRL_API usb_relay_exit(void)
191 {
192 return 0;
193 }
194
195 // Enum function for building list of devices
196 static
197 int enumfunc(USBDEVHANDLE usbh, void *context)
198 {
199 // static const char vendorName[] = USB_RELAY_VENDOR_NAME;
200 static const char productName[] = USB_RELAY_NAME_PREF;
201 int err;
202 char buffer[128*sizeof(short)]; // max USB string is 128 UTF-16 chars
203 int num = 0;
204 int i;
205 struct usbrelay_internal_s *q;
206 struct enumctx_s *ectx = (struct enumctx_s *)context;
207
208 //NOTE: Ignore vendor string. This is against ObjDev rules, restore the check if needed!
209
210 err = usbhidGetProductString(usbh, buffer, sizeof(buffer));
211 if (err)
212 {
213 goto next;
214 }
215
216 i = (int)strlen(buffer);
217 if ( i != strlen(productName) + 1 )
218 {
219 goto next;
220 }
221
222 /* the last char of ProductString is number of relays */
223 num = (int)(buffer[i - 1]) - (int)'0';
224 buffer[i - 1] = 0;
225
226 if ( 0 != strcmp( buffer, productName) )
227 {
228 goto next;
229 }
230
231 if ( num <= 0 || num > 8 )
232 {
233 dbgprintf("Unknown relay device? num relays=%d\n", num);
234 goto next;
235 }
236
237 /* Check the unique ID: USB_RELAY_ID_STR_LEN bytes at offset 1 (just after the report id) */
238 err = rel_read_status_raw(usbh, buffer);
239 if( err < 0 )
240 {
241 dbgprintf("Error reading report 0: %s\n", usbErrorMessage(err));
242 goto next;
243 }
244
245 for (i = 1; i <= USB_RELAY_ID_STR_LEN; i++)
246 {
247 unsigned char x = (unsigned char)buffer[i];
248 if (x <= 0x20 || x >= 0x7F)
249 {
250 dbgprintf("Bad usbrelay ID string!\n");
251 goto next;
252 }
253 }
254
255 if( buffer[USB_RELAY_ID_STR_LEN + 1] != 0 )
256 {
257 dbgprintf("Bad usbrelay ID string!\n");
258 goto next;
259 }
260
261 dbgprintf("Device %s%d found: ID=[%5s]\n", productName, num, &buffer[1]);
262
263 // allocate & save info
264 q = (struct usbrelay_internal_s *)calloc(1, sizeof(struct usbrelay_internal_s));
265 if (!q) {
266 dbgprintf("Malloc err\n");
267 goto next; //$$$ revise
268 }
269 /* keep this device, continue */
270 q->usbh = usbh;
271 memcpy(q->idstr, &buffer[1], USB_RELAY_ID_STR_LEN);
272 q->urdi.type = num; // enum = number of relays
273 q->urdi.serial_number = &q->idstr[0];
274 q->urdi.device_path = (char*)g_dummyPath;
275
276 if (!ectx->head) {
277 ectx->head = q;
278 ectx->tail =q;
279 } else {
280 ectx->tail->urdi.next = (pusb_relay_device_info_t)q;
281 }
282
283 ++ectx->numdevs;
284 return 1;
285
286 next:
287 /* Continue search */
288 usbhidCloseDevice(usbh);
289 return 1;
290 }
291
292 // Enum function for open one device by ID
293 static
294 int enumOpenfunc(USBDEVHANDLE usbh, void *context)
295 {
296 // static const char vendorName[] = USB_RELAY_VENDOR_NAME;
297 static const char productName[] = USB_RELAY_NAME_PREF;
298 int err;
299 char buffer[128*sizeof(short)]; // max USB string is 128 UTF-16 chars
300 int num = 0;
301 int i;
302 struct enumctx_s *ectx = (struct enumctx_s *)context;
303 struct usbrelay_internal_s *q = ectx->head;
304
305 //NOTE: Ignore vendor string. This is against ObjDev rules, restore the check if needed!
306
307 err = usbhidGetProductString(usbh, buffer, sizeof(buffer));
308 if (err)
309 {
310 goto next;
311 }
312
313 i = (int)strlen(buffer);
314 if ( i != strlen(productName) + 1 )
315 {
316 goto next;
317 }
318
319 /* the last char of ProductString is number of relays */
320 num = (int)(buffer[i - 1]) - (int)'0';
321 buffer[i - 1] = 0;
322
323 if ( 0 != strcmp( buffer, productName) )
324 {
325 goto next;
326 }
327
328 if ( num <= 0 || num > 8 )
329 {
330 dbgprintf("Unknown relay device? num relays=%d\n", num);
331 goto next;
332 }
333
334 /* Check the unique ID: USB_RELAY_ID_STR_LEN bytes at offset 1 (just after the report id) */
335 err = rel_read_status_raw(usbh, buffer);
336 if( err < 0 )
337 {
338 dbgprintf("Error reading report 0: %s\n", usbErrorMessage(err));
339 goto next;
340 }
341
342 for (i = 1; i <= USB_RELAY_ID_STR_LEN; i++)
343 {
344 unsigned char x = (unsigned char)buffer[i];
345 if (x <= 0x20 || x >= 0x7F)
346 {
347 dbgprintf("Bad usbrelay ID string!\n");
348 goto next;
349 }
350 }
351
352 if( buffer[USB_RELAY_ID_STR_LEN + 1] != 0 )
353 {
354 dbgprintf("Bad usbrelay ID string!\n");
355 goto next;
356 }
357
358 dbgprintf("Device %s%d found: ID=[%5s]\n", productName, num, &buffer[1]);
359
360 if ( 0 == memcmp( q->idstr, &buffer[1], USB_RELAY_ID_STR_LEN) ) {
361 q->usbh = usbh;
362 q->urdi.type = num; // enum = number of relays
363 q->urdi.serial_number = &q->idstr[0];
364 q->urdi.device_path = (char*)g_dummyPath;
365 ++ectx->numdevs;
366 return 0;
367 }
368
369 next:
370 /* Continue search */
371 usbhidCloseDevice(usbh);
372 return 1;
373 }
374
375 /** Enumerate the USB Relay Devices.*/
376 pusb_relay_device_info_t USBRL_API usb_relay_device_enumerate(void)
377 {
378 struct enumctx_s ectx;
379 int ret;
380 memset(&ectx, 0, sizeof(ectx));
381 ret = usbhidEnumDevices(USB_CFG_VENDOR_ID, USB_CFG_DEVICE_ID,
382 (void*)&ectx,
383 enumfunc);
384
385 return (pusb_relay_device_info_t)ectx.head;
386 }
387
388
389 /** Free an enumeration Linked List*/
390 void USBRL_API usb_relay_device_free_enumerate(struct usb_relay_device_info *dilist)
391 {
392 struct usbrelay_internal_s *p = (struct usbrelay_internal_s *)dilist;
393
394 while (p) {
395 struct usbrelay_internal_s *q = (struct usbrelay_internal_s *)((pusb_relay_device_info_t)p)->next;
396 if (p->usbh && ((USBDEVHANDLE)(-1)) != p->usbh) {
397 usbhidCloseDevice(p->usbh);
398 p->usbh = 0;
399 }
400 free(p);
401 p = q;
402 }
403
404 return;
405 }
406
407 /** Open device by serial number
408 serial_number == NULL is valid and means any one device.
409 @return: This function returns a valid handle to the device on success or NULL on failure.
410 Example: usb_relay_device_open_with_serial_number("abcde", 5) */
411 intptr_t USBRL_API usb_relay_device_open_with_serial_number(const char *serial_number, unsigned len)
412 {
413 struct enumctx_s ectx;
414 int ret;
415 struct usbrelay_internal_s *q;
416 memset(&ectx, 0, sizeof(ectx));
417
418 if (serial_number && len != USB_RELAY_ID_STR_LEN) {
419 printerr("Specified invalid str id length: %u", len);
420 return (intptr_t)0;
421 }
422
423 q = ectx.head = calloc(1, sizeof(*ectx.head));
424 if (!q)
425 return (intptr_t)0;
426
427 memcpy(q->idstr, serial_number, len);
428
429 ret = usbhidEnumDevices(USB_CFG_VENDOR_ID, USB_CFG_DEVICE_ID,
430 (void*)&ectx,
431 enumfunc);
432 if (ret != 0)
433 goto ret_err; // error during enum
434
435 if (ectx.numdevs == 0 || q->usbh == 0) {
436 goto ret_err; // not found
437 }
438
439 q->urdi.next = (void*)q; // mark this element as standalone
440 return (intptr_t)q;
441
442 ret_err:
443 free(q);
444 return (intptr_t)0;
445 }
446
447 /** Open a USB relay device
448 @return: This function returns a valid handle to the device on success or NULL on failure.
449 */
450 intptr_t USBRL_API usb_relay_device_open(struct usb_relay_device_info *device_info)
451 {
452 struct usbrelay_internal_s *p = (struct usbrelay_internal_s *)device_info;
453 if (!device_info)
454 return 0;
455 if ( (uintptr_t)p->usbh == 0 || (uintptr_t)p->usbh == (uintptr_t)-1 )
456 return 0;
457 //$$$ validate more
458 return (uintptr_t)device_info;
459 }
460
461 /** Close a USB relay device*/
462 void USBRL_API usb_relay_device_close(intptr_t hHandle)
463 {
464 struct usbrelay_internal_s *p = (struct usbrelay_internal_s *)hHandle;
465 if ( 0 == hHandle || ((intptr_t)-1) == hHandle )
466 return;
467
468 if ( (void*)(p->urdi.next) == (void*)p ) {
469 // This was made by usb_relay_device_open_with_serial_number() so free it now:
470 if ( p->usbh && ((intptr_t)-1) != (intptr_t)(p->usbh)) {
471 usbhidCloseDevice(p->usbh);
472 p->usbh = 0;
473 }
474 p->urdi.next = NULL;
475 free( (void*)p );
476 }
477 // Else this can be in the list, don't do anything.
478 }
479
480 /** Turn ON a relay channel on the USB-Relay-Device
481 @param index -- which channel your want to open
482 @param hHandle -- which usb relay device your want to operate
483 @returns: 0 -- success; 1 -- error; 2 -- index is outnumber the number of the usb relay device
484 */
485 int USBRL_API usb_relay_device_open_one_relay_channel(intptr_t hHandle, int index)
486 {
487 struct usbrelay_internal_s *p = (struct usbrelay_internal_s *)hHandle;
488 if (!p)
489 return 1;
490 if ( index <= 0 || index > (int)p->urdi.type )
491 return 2;
492 return rel_onoff( p->usbh, 1, index);
493 }
494
495 /** Turn ON all relay channels on the USB-Relay-Device
496 @param hHandle -- which usb relay device your want to operate
497 @returns: 0 -- success; 1 -- error
498 */
499 int USBRL_API usb_relay_device_open_all_relay_channel(intptr_t hHandle)
500 {
501 struct usbrelay_internal_s *p = (struct usbrelay_internal_s *)hHandle;
502 if (!p)
503 return 1;
504 return rel_onoff( p->usbh, 1, -(int)p->urdi.type );
505 }
506
507 /** Turn OFF a relay channel on the USB-Relay-Device
508 @param index -- which channel your want to close
509 @param hHandle -- which usb relay device your want to operate
510 @returns: 0 -- success; 1 -- error; 2 -- index is outnumber the number of the usb relay device
511 */
512 int USBRL_API usb_relay_device_close_one_relay_channel(intptr_t hHandle, int index)
513 {
514 struct usbrelay_internal_s *p = (struct usbrelay_internal_s *)hHandle;
515 if (!p)
516 return 1;
517 if ( index <= 0 || index > (int)p->urdi.type )
518 return 2;
519 return rel_onoff( p->usbh, 0, index);
520 }
521
522 /** Turn OFF all relay channels on the USB-Relay-Device
523 @param hHandle -- which usb relay device your want to operate
524 @returns 0 -- success; 1 -- error
525 */
526 int USBRL_API usb_relay_device_close_all_relay_channel(intptr_t hHandle)
527 {
528 struct usbrelay_internal_s *p = (struct usbrelay_internal_s *)hHandle;
529 if (!p)
530 return 1;
531 return rel_onoff( p->usbh, 0, -(int)p->urdi.type );
532 }
533
534 /** Get status of all relays on the device
535 Status bits: one bit indicate a relay status.
536 bit 0/1/2/3/4/5/6/7/8 indicate channel 1/2/3/4/5/6/7/8 status
537 1 -- means ON, 0 -- means OFF.
538 @returns: 0 -- success; 1 -- error
539 */
540 int USBRL_API usb_relay_device_get_status(intptr_t hHandle, unsigned int *status)
541 {
542 struct usbrelay_internal_s *p = (struct usbrelay_internal_s *)hHandle;
543 int err;
544 if (!p)
545 return 1;
546 err = rel_read_status_raw(p->usbh, NULL);
547 if ( err < 0 ) {
548 printerr("Error reading data: %s\n", usbErrorMessage(err));
549 return -err;
550 }
551
552 *status = (unsigned char)err;
553 return 0;
554 }
555
556 /************ Added ****************/
557
558 /** Get the library (dll) version
559 @return Lower 16 bits: the library version. Higher bits: undefined, ignore.
560 @note The original DLL does not have this function!
561 */
562 int USBRL_API usb_relay_device_lib_version(void)
563 {
564 return (int)(MY_VERSION);
565 }
566
567 /**
568 The following functions are for non-native callers, to avoid fumbling with C structs.
569 Native C/C++ callers do not need to use these.
570 The ptr_usb_relay_device_info arg is pointer to struct usb_relay_device_info, cast to intptr_t, void*, etc.
571 */
572
573 /* Return next info struct pointer in the list returned by usb_relay_device_enumerate() */
574 intptr_t USBRL_API usb_relay_device_next_dev(intptr_t ptr_usb_relay_device_info)
575 {
576 if (!ptr_usb_relay_device_info)
577 return 0;
578 return (intptr_t)(void*)((pusb_relay_device_info_t)ptr_usb_relay_device_info)->next;
579 }
580
581 /* Get number of relay channels on the device */
582 int USBRL_API usb_relay_device_get_num_relays(intptr_t ptr_usb_relay_device_info)
583 {
584 if (!ptr_usb_relay_device_info)
585 return 0;
586 return (int)((pusb_relay_device_info_t)ptr_usb_relay_device_info)->type;
587 }
588
589 /* Get the ID string of the device. Returns pointer to const C string (1-byte, 0-terminated) */
590 intptr_t USBRL_API usb_relay_device_get_id_string(intptr_t ptr_usb_relay_device_info)
591 {
592 if (!ptr_usb_relay_device_info)
593 return 0;
594 return (intptr_t)(void const *)((pusb_relay_device_info_t)ptr_usb_relay_device_info)->serial_number;
595 }
596
597 /* Get status of all relays on the device.
598 * @return Bitmask of all relay channels state, if the value > 0. Negative values mean error.
599 bit 0/1/2/3/4/5/6/7/8 indicate channel 1/2/3/4/5/6/7/8 status
600 Each bit value 1 means ON, 0 means OFF.
601 * @note This is same as usb_relay_device_get_status, but without dereferencing pointers.
602 */
603 int USBRL_API usb_relay_device_get_status_bitmap(intptr_t hHandle)
604 {
605 unsigned int st;
606 int err = usb_relay_device_get_status(hHandle, &st);
607 if (0 == err)
608 return (int)st;
609 return (err > 0) ? (-err) : err;
610 }
611
612 #ifdef __cplusplus
613 }
614 #endif