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