Fix for #7 (by kevinchuangtw)
[usb-relay-hid.git] / lib / usb_relay_lib.c
CommitLineData
c099d658 1/**
2* USB HID relays API library
3* http://git.io/bGcxrQ
4*
5132ecf9 5* This is reconstruction of the original Windows DLL,
63e45858
P
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*
c099d658 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"
63e45858 19#define WIN32_EXTRALEAN
c099d658 20#include <windows.h>
21
5132ecf9 22#ifdef _MSC_VER
63e45858
P
23/* The original DLL has cdecl calling convention */
24#define USBRL_CALL __cdecl
25#define USBRL_API __declspec(dllexport) USBRL_CALL
26
4c51b553 27#if _MSC_VER < 1900 /* before VS2015 */
c099d658 28#define snprintf _snprintf
4c51b553 29#endif /* VS2015 */
5132ecf9 30#endif // _MSC_VER
c099d658 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
ac36c33b 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__)
c099d658 51
52#ifdef __cplusplus
53extern "C" {
54#endif
55
56struct usbrelay_internal_s {
57 struct usb_relay_device_info urdi; //public part
58 // Private part:
59 USBDEVHANDLE usbh; // handle
60 char idstr[8];
61};
5132ecf9 62
c099d658 63// struct for enum context
64struct enumctx_s {
65 struct usbrelay_internal_s *head, *tail;
66 int numdevs;
67 int status;
68};
5132ecf9 69
c099d658 70// Globals
71
aeb44a35 72static const char *g_dummyPath = "NOTHING"; // passing dev.path to client not implemented, I return this as path.
c099d658 73
74static 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
89static 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
119static 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;
5132ecf9 124
c099d658 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 }
904823f7 140 mask = (unsigned char)(1U << (relaynum-1));
c099d658 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 }
5132ecf9 159
c099d658 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;
5132ecf9 168 if (err != maskval) {
c099d658 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*/
182int USBRL_API usb_relay_init(void)
183{
184 return 0;
185}
186
187/** Finalize the USB Relay Library.
5132ecf9 188This function frees all of the static data associated with USB Relay Library.
c099d658 189It 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*/
192int USBRL_API usb_relay_exit(void)
193{
194 return 0;
195}
196
197// Enum function for building list of devices
5132ecf9 198static
c099d658 199int 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 }
5132ecf9 246
c099d658 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;
aeb44a35 283 ectx->tail = q;
c099d658 284 }
5132ecf9 285
c099d658 286 ++ectx->numdevs;
287 return 1;
288
289 next:
290 /* Continue search */
291 usbhidCloseDevice(usbh);
292 return 1;
293}
294
295// Enum function for open one device by ID
5132ecf9 296static
c099d658 297int enumOpenfunc(USBDEVHANDLE usbh, void *context)
298{
299// static const char vendorName[] = USB_RELAY_VENDOR_NAME;
300 static const char productName[] = USB_RELAY_NAME_PREF;
301 int err;
302 char buffer[128*sizeof(short)]; // max USB string is 128 UTF-16 chars
303 int num = 0;
304 int i;
305 struct enumctx_s *ectx = (struct enumctx_s *)context;
306 struct usbrelay_internal_s *q = ectx->head;
307
308 //NOTE: Ignore vendor string. This is against ObjDev rules, restore the check if needed!
309
310 err = usbhidGetProductString(usbh, buffer, sizeof(buffer));
311 if (err)
312 {
313 goto next;
314 }
315
316 i = (int)strlen(buffer);
317 if ( i != strlen(productName) + 1 )
318 {
319 goto next;
320 }
321
322 /* the last char of ProductString is number of relays */
323 num = (int)(buffer[i - 1]) - (int)'0';
324 buffer[i - 1] = 0;
325
326 if ( 0 != strcmp( buffer, productName) )
327 {
328 goto next;
329 }
330
331 if ( num <= 0 || num > 8 )
332 {
333 dbgprintf("Unknown relay device? num relays=%d\n", num);
334 goto next;
335 }
336
337 /* Check the unique ID: USB_RELAY_ID_STR_LEN bytes at offset 1 (just after the report id) */
338 err = rel_read_status_raw(usbh, buffer);
339 if( err < 0 )
340 {
341 dbgprintf("Error reading report 0: %s\n", usbErrorMessage(err));
342 goto next;
343 }
5132ecf9 344
c099d658 345 for (i = 1; i <= USB_RELAY_ID_STR_LEN; i++)
346 {
347 unsigned char x = (unsigned char)buffer[i];
348 if (x <= 0x20 || x >= 0x7F)
349 {
350 dbgprintf("Bad usbrelay ID string!\n");
351 goto next;
352 }
353 }
354
355 if( buffer[USB_RELAY_ID_STR_LEN + 1] != 0 )
356 {
357 dbgprintf("Bad usbrelay ID string!\n");
358 goto next;
359 }
360
361 dbgprintf("Device %s%d found: ID=[%5s]\n", productName, num, &buffer[1]);
362
363 if ( 0 == memcmp( q->idstr, &buffer[1], USB_RELAY_ID_STR_LEN) ) {
364 q->usbh = usbh;
365 q->urdi.type = num; // enum = number of relays
366 q->urdi.serial_number = &q->idstr[0];
367 q->urdi.device_path = (char*)g_dummyPath;
368 ++ectx->numdevs;
369 return 0;
370 }
371
372 next:
373 /* Continue search */
374 usbhidCloseDevice(usbh);
375 return 1;
376}
377
378/** Enumerate the USB Relay Devices.*/
379pusb_relay_device_info_t USBRL_API usb_relay_device_enumerate(void)
380{
381 struct enumctx_s ectx;
382 int ret;
383 memset(&ectx, 0, sizeof(ectx));
384 ret = usbhidEnumDevices(USB_CFG_VENDOR_ID, USB_CFG_DEVICE_ID,
385 (void*)&ectx,
386 enumfunc);
387
388 return (pusb_relay_device_info_t)ectx.head;
389}
390
391
392/** Free an enumeration Linked List*/
393void USBRL_API usb_relay_device_free_enumerate(struct usb_relay_device_info *dilist)
394{
395 struct usbrelay_internal_s *p = (struct usbrelay_internal_s *)dilist;
396
397 while (p) {
398 struct usbrelay_internal_s *q = (struct usbrelay_internal_s *)((pusb_relay_device_info_t)p)->next;
399 if (p->usbh && ((USBDEVHANDLE)(-1)) != p->usbh) {
400 usbhidCloseDevice(p->usbh);
401 p->usbh = 0;
402 }
403 free(p);
404 p = q;
405 }
5132ecf9 406
c099d658 407 return;
408}
409
410/** Open device by serial number
411serial_number == NULL is valid and means any one device.
412@return: This function returns a valid handle to the device on success or NULL on failure.
413Example: usb_relay_device_open_with_serial_number("abcde", 5) */
414intptr_t USBRL_API usb_relay_device_open_with_serial_number(const char *serial_number, unsigned len)
415{
416 struct enumctx_s ectx;
417 int ret;
418 struct usbrelay_internal_s *q;
419 memset(&ectx, 0, sizeof(ectx));
5132ecf9 420
c099d658 421 if (serial_number && len != USB_RELAY_ID_STR_LEN) {
422 printerr("Specified invalid str id length: %u", len);
423 return (intptr_t)0;
424 }
425
426 q = ectx.head = calloc(1, sizeof(*ectx.head));
427 if (!q)
428 return (intptr_t)0;
429
430 memcpy(q->idstr, serial_number, len);
431
432 ret = usbhidEnumDevices(USB_CFG_VENDOR_ID, USB_CFG_DEVICE_ID,
433 (void*)&ectx,
904823f7 434 enumOpenfunc);
c099d658 435 if (ret != 0)
436 goto ret_err; // error during enum
437
438 if (ectx.numdevs == 0 || q->usbh == 0) {
439 goto ret_err; // not found
440 }
441
442 q->urdi.next = (void*)q; // mark this element as standalone
443 return (intptr_t)q;
444
445 ret_err:
446 free(q);
447 return (intptr_t)0;
448}
449
450/** Open a USB relay device
451@return: This function returns a valid handle to the device on success or NULL on failure.
452*/
453intptr_t USBRL_API usb_relay_device_open(struct usb_relay_device_info *device_info)
454{
455 struct usbrelay_internal_s *p = (struct usbrelay_internal_s *)device_info;
456 if (!device_info)
457 return 0;
458 if ( (uintptr_t)p->usbh == 0 || (uintptr_t)p->usbh == (uintptr_t)-1 )
459 return 0;
460 //$$$ validate more
5132ecf9 461 return (uintptr_t)device_info;
c099d658 462}
463
464/** Close a USB relay device*/
465void USBRL_API usb_relay_device_close(intptr_t hHandle)
466{
467 struct usbrelay_internal_s *p = (struct usbrelay_internal_s *)hHandle;
468 if ( 0 == hHandle || ((intptr_t)-1) == hHandle )
469 return;
470
471 if ( (void*)(p->urdi.next) == (void*)p ) {
472 // This was made by usb_relay_device_open_with_serial_number() so free it now:
473 if ( p->usbh && ((intptr_t)-1) != (intptr_t)(p->usbh)) {
474 usbhidCloseDevice(p->usbh);
475 p->usbh = 0;
476 }
477 p->urdi.next = NULL;
478 free( (void*)p );
479 }
480 // Else this can be in the list, don't do anything.
481}
482
483/** Turn ON a relay channel on the USB-Relay-Device
484@param index -- which channel your want to open
485@param hHandle -- which usb relay device your want to operate
486@returns: 0 -- success; 1 -- error; 2 -- index is outnumber the number of the usb relay device
487*/
488int USBRL_API usb_relay_device_open_one_relay_channel(intptr_t hHandle, int index)
489{
490 struct usbrelay_internal_s *p = (struct usbrelay_internal_s *)hHandle;
491 if (!p)
492 return 1;
493 if ( index <= 0 || index > (int)p->urdi.type )
494 return 2;
495 return rel_onoff( p->usbh, 1, index);
496}
497
498/** Turn ON all relay channels on the USB-Relay-Device
499@param hHandle -- which usb relay device your want to operate
500@returns: 0 -- success; 1 -- error
501*/
502int USBRL_API usb_relay_device_open_all_relay_channel(intptr_t hHandle)
503{
504 struct usbrelay_internal_s *p = (struct usbrelay_internal_s *)hHandle;
505 if (!p)
506 return 1;
507 return rel_onoff( p->usbh, 1, -(int)p->urdi.type );
508}
509
510/** Turn OFF a relay channel on the USB-Relay-Device
511@param index -- which channel your want to close
512@param hHandle -- which usb relay device your want to operate
513@returns: 0 -- success; 1 -- error; 2 -- index is outnumber the number of the usb relay device
514*/
515int USBRL_API usb_relay_device_close_one_relay_channel(intptr_t hHandle, int index)
516{
517 struct usbrelay_internal_s *p = (struct usbrelay_internal_s *)hHandle;
518 if (!p)
519 return 1;
520 if ( index <= 0 || index > (int)p->urdi.type )
521 return 2;
522 return rel_onoff( p->usbh, 0, index);
523}
524
525/** Turn OFF all relay channels on the USB-Relay-Device
526@param hHandle -- which usb relay device your want to operate
527@returns 0 -- success; 1 -- error
528*/
529int USBRL_API usb_relay_device_close_all_relay_channel(intptr_t hHandle)
530{
531 struct usbrelay_internal_s *p = (struct usbrelay_internal_s *)hHandle;
532 if (!p)
533 return 1;
534 return rel_onoff( p->usbh, 0, -(int)p->urdi.type );
535}
536
537/** Get status of all relays on the device
538Status bits: one bit indicate a relay status.
539bit 0/1/2/3/4/5/6/7/8 indicate channel 1/2/3/4/5/6/7/8 status
540 1 -- means ON, 0 -- means OFF.
541@returns: 0 -- success; 1 -- error
542*/
543int USBRL_API usb_relay_device_get_status(intptr_t hHandle, unsigned int *status)
544{
545 struct usbrelay_internal_s *p = (struct usbrelay_internal_s *)hHandle;
546 int err;
547 if (!p)
548 return 1;
549 err = rel_read_status_raw(p->usbh, NULL);
550 if ( err < 0 ) {
551 printerr("Error reading data: %s\n", usbErrorMessage(err));
5132ecf9 552 return -err;
c099d658 553 }
554
5132ecf9 555 *status = (unsigned char)err;
c099d658 556 return 0;
557}
558
63e45858
P
559/************ Added ****************/
560
561/** Get the library (dll) version
562@return Lower 16 bits: the library version. Higher bits: undefined, ignore.
563@note The original DLL does not have this function!
c099d658 564*/
565int USBRL_API usb_relay_device_lib_version(void)
566{
567 return (int)(MY_VERSION);
568}
569
5132ecf9 570/**
63e45858
P
571The following functions are for non-native callers, to avoid fumbling with C structs.
572Native C/C++ callers do not need to use these.
573The ptr_usb_relay_device_info arg is pointer to struct usb_relay_device_info, cast to intptr_t, void*, etc.
574*/
575
576/* Return next info struct pointer in the list returned by usb_relay_device_enumerate() */
577intptr_t USBRL_API usb_relay_device_next_dev(intptr_t ptr_usb_relay_device_info)
578{
579 if (!ptr_usb_relay_device_info)
580 return 0;
581 return (intptr_t)(void*)((pusb_relay_device_info_t)ptr_usb_relay_device_info)->next;
582}
583
584/* Get number of relay channels on the device */
585int USBRL_API usb_relay_device_get_num_relays(intptr_t ptr_usb_relay_device_info)
586{
587 if (!ptr_usb_relay_device_info)
588 return 0;
589 return (int)((pusb_relay_device_info_t)ptr_usb_relay_device_info)->type;
590}
591
592/* Get the ID string of the device. Returns pointer to const C string (1-byte, 0-terminated) */
593intptr_t USBRL_API usb_relay_device_get_id_string(intptr_t ptr_usb_relay_device_info)
594{
595 if (!ptr_usb_relay_device_info)
596 return 0;
597 return (intptr_t)(void const *)((pusb_relay_device_info_t)ptr_usb_relay_device_info)->serial_number;
598}
599
5132ecf9
P
600/* Get status of all relays on the device.
601* @return Bitmask of all relay channels state, if the value > 0. Negative values mean error.
602 bit 0/1/2/3/4/5/6/7/8 indicate channel 1/2/3/4/5/6/7/8 status
603 Each bit value 1 means ON, 0 means OFF.
604* @note This is same as usb_relay_device_get_status, but without dereferencing pointers.
605*/
606int USBRL_API usb_relay_device_get_status_bitmap(intptr_t hHandle)
607{
608 unsigned int st;
609 int err = usb_relay_device_get_status(hHandle, &st);
610 if (0 == err)
611 return (int)st;
612 return (err > 0) ? (-err) : err;
c099d658 613}
c099d658 614
5132ecf9
P
615#ifdef __cplusplus
616}
617#endif