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