]> git.zerfleddert.de Git - fs20pcs/blame - fs20pcs.c
add license
[fs20pcs] / fs20pcs.c
CommitLineData
f54ebf89
MG
1/* FS20PCS libusb-driver
2 *
3 * Copyright (c) 2013 Michael Gernoth <michael@gernoth.net>
4 *
5 * Permission is hereby granted, free of charge, to any person obtaining a copy
6 * of this software and associated documentation files (the "Software"), to
7 * deal in the Software without restriction, including without limitation the
8 * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
9 * sell copies of the Software, and to permit persons to whom the Software is
10 * furnished to do so, subject to the following conditions:
11 *
12 * The above copyright notice and this permission notice shall be included in
13 * all copies or substantial portions of the Software.
14 *
15 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
20 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
21 * IN THE SOFTWARE.
22 */
23
a06d3b72
MG
24#include <string.h>
25#include <stdio.h>
26#include <stdint.h>
27#include <unistd.h>
28#include <stdlib.h>
f6d105d6 29#include <math.h>
a06d3b72
MG
30#include <libusb-1.0/libusb.h>
31
32#define USB_TIMEOUT 10000
33
34#define ID_VENDOR 0x18ef
35#define ID_PRODUCT 0xe015
36
f6d105d6
MG
37#define EXT_TO_SEC(ext) ((1 << ((ext & 0xf0) >> 4)) * (ext & 0x0f) * 0.25)
38
23892620
MG
39extern char *optarg;
40
a06d3b72
MG
41/* Not in all libusb-1.0 versions, so we have to roll our own :-( */
42static char * usb_strerror(int e)
43{
44 static char unknerr[256];
45
46 switch (e) {
47 case LIBUSB_SUCCESS:
48 return "Success";
49 case LIBUSB_ERROR_IO:
50 return "Input/output error";
51 case LIBUSB_ERROR_INVALID_PARAM:
52 return "Invalid parameter";
53 case LIBUSB_ERROR_ACCESS:
54 return "Access denied (insufficient permissions)";
55 case LIBUSB_ERROR_NO_DEVICE:
56 return "No such device (it may have been disconnected)";
57 case LIBUSB_ERROR_NOT_FOUND:
58 return "Entity not found";
59 case LIBUSB_ERROR_BUSY:
60 return "Resource busy";
61 case LIBUSB_ERROR_TIMEOUT:
62 return "Operation timed out";
63 case LIBUSB_ERROR_OVERFLOW:
64 return "Overflow";
65 case LIBUSB_ERROR_PIPE:
66 return "Pipe error";
67 case LIBUSB_ERROR_INTERRUPTED:
68 return "System call interrupted (perhaps due to signal)";
69 case LIBUSB_ERROR_NO_MEM:
70 return "Insufficient memory";
71 case LIBUSB_ERROR_NOT_SUPPORTED:
72 return "Operation not supported or unimplemented on this platform";
73 case LIBUSB_ERROR_OTHER:
74 return "Other error";
75 };
76 snprintf(unknerr, sizeof(unknerr), "Unknown error code %d / 0x%02x", e, e);
77 return unknerr;
78}
79
80libusb_device_handle *fs20pcs_find() {
81 libusb_device_handle *devh = NULL;
82 libusb_device **list;
83 ssize_t cnt;
84 ssize_t i;
85 int err;
86
87 cnt = libusb_get_device_list(NULL, &list);
88 if (cnt < 0) {
89 fprintf(stderr, "Can't get USB device list: %d\n", (int)cnt);
90 return NULL;
91 }
92
93 for (i = 0; i < cnt; i++){
94 struct libusb_device_descriptor desc;
95
96 err = libusb_get_device_descriptor(list[i], &desc);
97 if (err)
98 continue;
99
100 if ((desc.idVendor == ID_VENDOR) && (desc.idProduct == ID_PRODUCT)) {
101 libusb_device *dev = list[i];
102
103 err = libusb_open(dev, &devh);
104 if (err) {
105 fprintf(stderr, "Can't open device: %s\n", usb_strerror(err));
106 return NULL;
107 }
108
109 err = libusb_detach_kernel_driver(devh, 0);
110 if ((err != 0) && (err != LIBUSB_ERROR_NOT_FOUND)) {
111 fprintf(stderr, "Can't detach kernel driver: %s\n", usb_strerror(err));
112 return NULL;
113 }
114
115 err = libusb_claim_interface(devh, 0);
116 if ((err != 0)) {
117 fprintf(stderr, "Can't claim interface: %s\n", usb_strerror(err));
118 return NULL;
119 }
120
121 return devh;
122 }
123
124 }
125
126 return NULL;
127}
128
129int fs20pcs_send(libusb_device_handle *devh, unsigned char* send_data)
130{
131 unsigned char recv_data[5] = {0x00, 0x00, 0x00, 0x00, 0x00};
132 int err;
133 int cnt;
134 int i;
2b257333 135 int ret;
a06d3b72 136
f6d105d6 137 err = libusb_interrupt_transfer(devh, 0x01, send_data, 11, &cnt, USB_TIMEOUT);
a06d3b72
MG
138 if (err) {
139 fprintf(stderr, "Can't send data: %s\n", usb_strerror(err));
140 return 0;
141 }
142
f6d105d6 143 err = libusb_interrupt_transfer(devh, 0x81, recv_data, sizeof(recv_data), &cnt, USB_TIMEOUT);
a06d3b72
MG
144 if (err) {
145 fprintf(stderr, "Can't receive data: %s\n", usb_strerror(err));
146 return 0;
147 }
148
2b257333
MG
149 if ((recv_data[0] != 0x02) ||
150 (recv_data[1] != 0x03) ||
151 (recv_data[2] != 0xa0)) {
152 fprintf(stderr, "Unexpected response: ");
153 for (i = 0; i < cnt; i++) {
154 fprintf(stderr, "0x%02x ", recv_data[i]);
155 }
156 fprintf(stderr, "\n");
157
158 return 0;
159 }
160
161 ret = 1;
162
163 switch(recv_data[3]) {
164 case 0x00:
165 printf("Success");
166 break;
167 case 0x01:
23892620 168 printf("Firmware: V%d.%d", ((recv_data[4] & 0xf0) >> 4) & 0x0f, recv_data[4] & 0x0f);
2b257333
MG
169 break;
170 case 0x02:
171 printf("Unknown command");
172 ret = 0;
173 break;
174 case 0x03:
175 printf("Wrong length");
176 ret = 0;
177 break;
178 case 0x04:
179 printf("Aborted sending long press");
180 break;
181 case 0x05:
182 printf("Nothing to stop");
183 break;
184 default:
185 printf("Unknown response: 0x%02x 0x%02x", recv_data[3], recv_data[4]);
186 ret = 0;
187 break;
a06d3b72 188 }
2b257333 189
a06d3b72
MG
190 printf("\n");
191
2b257333 192 return ret;
a06d3b72
MG
193}
194
23892620
MG
195unsigned char *parse_housecode(char *hc)
196{
197 static unsigned char housecode[2];
198 char hc_fixed[9];
199 char *ep;
200 unsigned long val;
201 int i;
202
203 if (hc == NULL)
204 return NULL;
205
206 memset(housecode, 0, sizeof(housecode));
207
208 switch(strlen(hc)) {
209 case 6:
210 if (strncmp(hc, "0x", 2)) {
211 fprintf(stderr, "Not a 2 byte hexstring: %s\n", hc);
212 return NULL;
213 }
214 case 4:
215 val = strtoul(hc, &ep, 16);
216 if (*ep != '\0') {
217 fprintf(stderr, "Not a 2 byte hexstring: %s\n", hc);
218 return NULL;
219 }
220 break;
221 case 8:
222 memset(hc_fixed, 0, sizeof(hc_fixed));
223 for (i = 0; i < 8; i++) {
224 if ((hc[i] < '1') || (hc[i] > '4')) {
225 fprintf(stderr, "Not a valid ELV housecode: %s\n", hc);
226 return NULL;
227 }
228 hc_fixed[i] = hc[i] - 1;
229 val = strtoul(hc_fixed, &ep, 4);
230
231 if (*ep != '\0') {
232 fprintf(stderr, "Can't parse fixed ELV housecode: %s\n", hc_fixed);
233 return NULL;
234 }
235 }
236 break;
237 default:
6c7fff3b 238 fprintf(stderr, "Housecode has to be in hex (1234, 0x1234) or ELV (12341234) format!\n");
23892620
MG
239 return NULL;
240 break;
241 }
242
243 housecode[0] = (val & 0xff00) >> 8;
244 housecode[1] = (val & 0xff);
245
246 return housecode;
247}
248
6c7fff3b
MG
249int parse_addr(char *ad)
250{
251 int addr = -1;
252 char ad_fixed[5];
253 char *ep;
254 unsigned long val;
255 int i;
256
257 if (ad == NULL)
258 return -1;
259
260 switch(strlen(ad)) {
261 case 4:
262 if (strncmp(ad, "0x", 2)) {
263 memset(ad_fixed, 0, sizeof(ad_fixed));
264 for (i = 0; i < 4; i++) {
265 if ((ad[i] < '1') || (ad[i] > '4')) {
266 fprintf(stderr, "Not a valid ELV address: %s\n", ad);
267 return -1;
268 }
269 ad_fixed[i] = ad[i] - 1;
270 val = strtoul(ad_fixed, &ep, 4);
271
272 if (*ep != '\0') {
273 fprintf(stderr, "Can't parse fixed ELV housecode: %s\n", ad_fixed);
274 return -1;
275 }
276 }
277
278 break;
279 }
280 case 2:
281 val = strtoul(ad, &ep, 16);
282 if (*ep != '\0') {
283 fprintf(stderr, "Not a 1 byte hexstring: %s\n", ad);
284 return -1;
285 }
286 break;
287 default:
288 fprintf(stderr, "Address has to be in hex (01, 0x01) or ELV (1112) format!\n");
289 return -1;
290 break;
291 }
292
293 addr = val & 0xff;
294
295 return addr;
296}
297
4636e355
MG
298int parse_command(char *cmd)
299{
300 int command = -1;
301 char *ep;
302 unsigned long val;
303
304 if (cmd == NULL)
305 return -1;
306
307 if (!strcasecmp(cmd, "off")) {
308 command = 0x00;
309 } else if (!strcasecmp(cmd, "on")) {
310 command = 0x11;
311 } else if (!strcasecmp(cmd, "toggle")) {
312 command = 0x12;
313 } else if (!strcasecmp(cmd, "dimup")) {
314 command = 0x13;
315 } else if (!strcasecmp(cmd, "dimdown")) {
316 command = 0x14;
317 } else if (!strcasecmp(cmd, "on-for-timer")) {
318 command = 0x39;
319 } else {
320 val = strtoul(cmd, &ep, 16);
321 if (*ep != '\0') {
322 fprintf(stderr, "Not a 1 byte hexstring or alias: %s\n", cmd);
323 return -1;
324 }
325
326 command = val & 0xff;
327 }
328
329 return command;
330}
331
f6d105d6
MG
332int parse_extension(char *ext)
333{
334 int extension = -1;
335 char *ep;
336
337 if (ext == NULL)
338 return -1;
339
340 if ((ext[strlen(ext)-1] == 's') ||
341 (ext[strlen(ext)-1] == 'S')) {
342 double t;
343 double diff = 1;
344 uint8_t high = 0;
345 uint8_t low = 0;
346 uint8_t i;
347
3ca85394 348 t = strtod(ext, &ep);
f6d105d6
MG
349 if ((*ep != 's') && (*ep != 'S')) {
350 fprintf(stderr, "Not a valid time in seconds: %s\n", ext);
351 return -1;
352 }
353
354 /* time = 2^(high nibble) * (low nibble) * 0.25 , high may only be 12 */
355 t /= 0.25;
356#ifdef DEBUG
357 printf("t: %f\n", t);
358#endif
359
360 for(i = 1; i <= 0xf; i++) {
361 double h;
362 double l;
363 uint8_t i_l;
364 double d;
365
366#ifdef DEBUG
367 printf("i: 0x%01x\n", i);
368#endif
369
370 h = t / i;
371#ifdef DEBUG
372 printf("h: %f\n", h);
373#endif
374
375 l = log2(h);
376#ifdef DEBUG
377 printf("l: %f\n", l);
378#endif
379
380 i_l = l;
381 if (i_l > 12)
382 i_l = 12;
383
3ca85394 384 d = fabs(l - i_l);
f6d105d6
MG
385
386#ifdef DEBUG
387 printf("d: %f\n", d);
388#endif
389
390 if (d < diff) {
391#ifdef DEBUG
392 printf("Current best match!\n");
393#endif
394 diff = d;
cbd3f0ba 395 high = i_l;
f6d105d6
MG
396 low = i;
397 }
398 }
399
400#ifdef DEBUG
401 printf("Got: %f, high: %01x, low: %01x\n", t, high, low);
402#endif
403
404 if ((high == 0) && (low == 0)) {
405 fprintf(stderr, "Can't find extension byte for %s!\n", ext);
406 return -1;
407 }
408
409 extension = ((high & 0xf) << 4) | (low & 0xf);
410 } else {
411 unsigned long val;
412
413 val = strtoul(ext, &ep, 16);
414 if (*ep != '\0') {
415 fprintf(stderr, "Not a 1 byte hexstring or time: %s\n", ext);
416 return -1;
417 }
418
419 extension = val & 0xff;
420 }
421
422 return extension;
423}
424
23892620
MG
425void syntax(char *prog)
426{
427 fprintf(stderr, "Syntax: %s options\n\n", prog);
428 fprintf(stderr, "Possible options:\n");
f4f53d0f 429 fprintf(stderr, "\t-V\t\trequest firmware-version\n");
23892620
MG
430 fprintf(stderr, "\t-x\t\tabort sending long press\n");
431 fprintf(stderr, "\t-h [ELV|hex]\thousecode in ELV- or hex-notation\n");
11007e95 432 fprintf(stderr, "The following options need an housecode:\n");
4636e355
MG
433 fprintf(stderr, "\t-a [ELV|hex]\tdestination address\n");
434 fprintf(stderr, "\t-s [hex|alias]\tsend command byte\n");
f6d105d6 435 fprintf(stderr, "\t-e [hex|time]\textension byte for command or time in seconds (e.g. 10s)\n");
23892620
MG
436 fprintf(stderr, "\t-r n\t\trepeat sending n times\n");
437 fprintf(stderr, "\nCommand bytes (without extension byte):\n");
4636e355
MG
438 fprintf(stderr, "hex\t\talias\tdescription\n");
439 fprintf(stderr, "0x00\t\toff\tswitch off\n");
440 fprintf(stderr, "0x01-0x10\t\tswitch on dimmed (0x01: 6.25%%, 0x02: 12.5%%, ..., 0x10: 100%%)\n");
441 fprintf(stderr, "0x11\t\ton\tswitch on\n");
442 fprintf(stderr, "0x12\t\ttoggle\ttoggle\n");
443 fprintf(stderr, "0x13\t\tdimup\tdimup\n");
444 fprintf(stderr, "0x14\t\tdimdown\tdimdown\n");
445 fprintf(stderr, "0x15\t\t\tdimup, pause, dimdown, pause, ...\n");
446 fprintf(stderr, "0x16\t\t\tstart/stop programming internal timer\n");
447 fprintf(stderr, "0x17\t\t\tlearn housecode/address\n");
448 fprintf(stderr, "0x18\t\t\toff for internal timer, then back on with current level\n");
449 fprintf(stderr, "0x19\t\t\ton (100%%) for internal timer, then off\n");
450 fprintf(stderr, "0x1a\t\t\ton (old level) for internal timer, then off\n");
451 fprintf(stderr, "0x1b\t\t\tfactory reset\n");
452 fprintf(stderr, "0x1e\t\t\ton (100%%) for internal timer, then old level\n");
453 fprintf(stderr, "0x1f\t\t\ton (old level) for internal timer, then old state\n");
23892620 454 fprintf(stderr, "\nCommand bytes (with timer as extension byte):\n");
4636e355
MG
455 fprintf(stderr, "hex\t\talias\tdescription\n");
456 fprintf(stderr, "0x20\t\t\tdim down to off while timer is running\n");
457 fprintf(stderr, "0x21-0x30\t\tdim to (0x01: 6.25%%, 0x02: 12.5%%, ..., 0x10: 100%%) while timer is running\n");
458 fprintf(stderr, "0x31\t\t\tdim to last level while timer is running\n");
459 fprintf(stderr, "0x32\t\t\tdim to off, then after timer dim to off\n");
460 fprintf(stderr, "0x33\t\t\tdimup now and switch off after timer\n");
461 fprintf(stderr, "0x34\t\t\tdimdown now and switch off after timer\n");
462 fprintf(stderr, "0x35\t\t\tdimup or dimdown (toggle) and switch off after timer\n");
463 fprintf(stderr, "0x36\t\t\tprogram internal timer\n");
464 fprintf(stderr, "0x38\t\t\toff for timer, then back on with current level\n");
465 fprintf(stderr, "0x39\ton-for-timer\ton (100%%) for timer, then off\n");
466 fprintf(stderr, "0x3a\t\t\ton (old level) for timer, then off\n");
467 fprintf(stderr, "0x3c\t\t\tprogram internal ramp-up-time\n");
468 fprintf(stderr, "0x3d\t\t\tprogram internal ramp-down-time\n");
469 fprintf(stderr, "0x3e\t\t\ton (100%%) for timer, then old level\n");
470 fprintf(stderr, "0x3f\t\t\ton (old level) for timer, then old state\n");
23892620
MG
471};
472
473enum {
474 NO_ACTION,
475 REQUEST_FIRMWARE,
476 SEND_COMMAND,
477 ABORT_LONG_PRESS
478};
479
480#define DUPLICATE_ACTION if (action != NO_ACTION) { \
481 fprintf(stderr, "duplicate action specified!\n"); \
482 exit(EXIT_FAILURE); }
483
a06d3b72
MG
484int main(int argc, char **argv)
485{
486 unsigned char send_data[11];
23892620 487 unsigned char *housecode = NULL;
a06d3b72 488 libusb_device_handle *devh = NULL;
23892620 489 char *ep;
a06d3b72 490 int err;
23892620
MG
491 int opt;
492 int action = NO_ACTION;
4636e355 493 int command = -1;
f6d105d6 494 int extension = 0;
23892620
MG
495 uint8_t repeat = 0;
496 int addr = -1;
a06d3b72 497
23892620
MG
498 memset(send_data, 0, sizeof(send_data));
499
500 send_data[0] = 0x01;
501
502 while ((opt = getopt(argc, argv, "Vxs:h:a:r:e:")) != -1) {
503 switch(opt) {
504 case 'V':
505 DUPLICATE_ACTION;
506 action = REQUEST_FIRMWARE;
507 break;
508 case 'x':
509 DUPLICATE_ACTION;
510 action = ABORT_LONG_PRESS;
511 break;
512 case 's':
513 DUPLICATE_ACTION;
514 action = SEND_COMMAND;
515
4636e355
MG
516 command = parse_command(optarg);
517 if (command == -1) {
23892620
MG
518 fprintf(stderr, "Can't parse command!\n");
519 exit(EXIT_FAILURE);
520 }
521#ifdef DEBUG
522 printf("Got command: %d\n", command);
523#endif
524 break;
525 case 'h':
526 housecode = parse_housecode(optarg);
527 if (!housecode) {
528 fprintf(stderr, "Can't parse housecode!\n");
529 exit(EXIT_FAILURE);
530 }
531#ifdef DEBUG
532 printf("Got housecode: 0x%02x%02x\n", housecode[0], housecode[1]);
533#endif
534 break;
535 case 'a':
6c7fff3b
MG
536 addr = parse_addr(optarg);
537 if (addr == -1) {
23892620
MG
538 fprintf(stderr, "Can't parse address!\n");
539 exit(EXIT_FAILURE);
540 }
541#ifdef DEBUG
542 printf("Got address: 0x%02x\n", addr);
543#endif
544 break;
545 case 'r':
546 repeat = strtoul(optarg, &ep, 10);
547 if (*ep != '\0') {
548 fprintf(stderr, "Can't parse repeat!\n");
549 exit(EXIT_FAILURE);
550 }
551#ifdef DEBUG
552 printf("Got repeat: %d\n", repeat);
553#endif
554 break;
555 case 'e':
f6d105d6
MG
556 extension = parse_extension(optarg);
557 if (extension == -1) {
23892620
MG
558 fprintf(stderr, "Can't parse extension!\n");
559 exit(EXIT_FAILURE);
560 }
561#ifdef DEBUG
562 printf("Got extension: %d\n", extension);
563#endif
564 break;
565 case ':':
566 case '?':
567 default:
568 syntax(argv[0]);
569 exit(EXIT_FAILURE);
570 break;
571 }
a06d3b72
MG
572 }
573
23892620
MG
574 switch(action) {
575 case REQUEST_FIRMWARE:
576 send_data[1] = 0x01;
577 send_data[2] = 0xf0;
578 printf("Requesting firmware version...\n");
579
580 break;
581 case ABORT_LONG_PRESS:
582 send_data[1] = 0x01;
583 send_data[2] = 0xf3;
584 printf("Aborting long press...\n");
585
586 break;
587 case SEND_COMMAND:
588 if (housecode == NULL) {
589 fprintf(stderr, "housecode needed!\n");
590 exit(EXIT_FAILURE);
591 }
592 if (addr == -1) {
593 fprintf(stderr, "address needed!\n");
594 exit(EXIT_FAILURE);
595 }
596
597 if (repeat) {
598 send_data[1] = 0x07;
599 send_data[2] = 0xf2;
600 send_data[8] = repeat;
f6d105d6
MG
601 printf("Sending 0x%02x 0x%02x (%.2fs) to address %d (hc: 0x%02x%02x) (repeated %d times)\n",
602 command, extension, EXT_TO_SEC(extension), addr,
23892620
MG
603 housecode[0], housecode[1],
604 repeat);
605 } else {
606 send_data[1] = 0x06;
607 send_data[2] = 0xf1;
f6d105d6
MG
608 printf("Sending 0x%02x 0x%02x (%.2fs) to address %d (hc: 0x%02x%02x)\n",
609 command, extension, EXT_TO_SEC(extension), addr,
23892620
MG
610 housecode[0], housecode[1]);
611 }
612 send_data[3] = housecode[0];
613 send_data[4] = housecode[1];
614 send_data[5] = addr;
615 send_data[6] = command;
616 send_data[7] = extension;
617
618 break;
619 case NO_ACTION:
620 default:
621 fprintf(stderr, "No action specified!\n\n");
622 syntax(argv[0]);
623 exit(EXIT_FAILURE);
624 break;
a06d3b72
MG
625 }
626
627 err = libusb_init(NULL);
628 if (err != 0) {
629 fprintf(stderr, "Can't initialize libusb: %s\n", usb_strerror(err));
630 return EXIT_FAILURE;
631 }
632
633 devh = fs20pcs_find();
634 if (!devh) {
635 fprintf(stderr, "Can't find/open fs20pcs!\n");
636 return EXIT_FAILURE;
637 }
638
639 if (fs20pcs_send(devh, send_data) == 0) {
640 fprintf(stderr, "Can't communicate with fs20pcs!\n");
641 return EXIT_FAILURE;
642 }
643
644 libusb_close(NULL);
645
646 return EXIT_SUCCESS;
647}
Impressum, Datenschutz