From 447e1c29ab352280f9f178031c8f0d80dd6ba581 Mon Sep 17 00:00:00 2001 From: s3lph Date: Wed, 27 Nov 2024 02:44:39 +0100 Subject: [PATCH] feat: argument parsing with getopt feat: enumerate usb interfaces and endpoints rather than hardcoding them --- 99-honeywell.rules | 3 +- Makefile | 2 +- README.md | 87 ++++++++--------- honeywell-config.c | 238 +++++++++++++++++++++++++++++++++++++-------- 4 files changed, 241 insertions(+), 89 deletions(-) diff --git a/99-honeywell.rules b/99-honeywell.rules index 796d737..fb69c2f 100644 --- a/99-honeywell.rules +++ b/99-honeywell.rules @@ -1,2 +1,3 @@ -SUBSYSTEM=="usb", ATTRS{idVendor}=="0c2e", ATTRS{idProduct}=="0b07", MODE="0666" SUBSYSTEM=="usb", ATTRS{idVendor}=="0c2e", ATTRS{idProduct}=="0b01", MODE="0666" +SUBSYSTEM=="usb", ATTRS{idVendor}=="0c2e", ATTRS{idProduct}=="0b02", MODE="0666" +SUBSYSTEM=="usb", ATTRS{idVendor}=="0c2e", ATTRS{idProduct}=="0b07", MODE="0666" diff --git a/Makefile b/Makefile index 5e895af..e427b42 100644 --- a/Makefile +++ b/Makefile @@ -2,7 +2,7 @@ .PHONY: honeywell-config clean honeywell-config: - gcc -lusb-1.0 -o honeywell-config honeywell-config.c + gcc -Wall -Werror -lusb-1.0 -o honeywell-config honeywell-config.c clean: rm -f honeywell-config diff --git a/README.md b/README.md index fa2ce5f..3a54b05 100644 --- a/README.md +++ b/README.md @@ -19,32 +19,30 @@ First of all, compile the program using `make`. It depends only on `libusb-1.0` The configuration strings need to be provided as command line arguments. You can provide multiple: ``` -./honeywell-config [... ] +usage: ./honeywell-config [-v vendor] [-p product] [-o] [-r] [-d] command1 [command2 [... commandN]] + -v vendor USB vendor ID of the barcode scanner + -p product USB product ID of the barcode scanner + -o Print response on one line, rather than one token per line + -r Reset device after configuration + -d Debug mode (more verbose output) ``` -The commands sent to the scanner are prefixed with `>`, the (tokenized) responses from the scanner with `<`. +If no vendor ID or product ID is provided, the following set of IDs is tried: -In general, please refer to your scanner's manual for configuration strings. +- `0c2e:0b01` (Honeywell 1300G, USB Kkeyboard mode PC) +- `0c2e:0b02` (Honeywell 1300G, USB Keyboard Apple) +- `0c2e:0b07` (Honeywell 1300G, USB HID POS mode) -Here are some examples. +USB serial mode (`0c2e:0b0a`) is skipped because USB configuration does not seem to work for it (and it isn't found by EZconfig either). + +For the actual configuration commands, please refer to your scanner's manual. Here are some examples. ### Factory Reset ``` $ ./honeywell-config DEFOVR. DEFALT. -> DEFOVR. -< DEFOVR. -> DEFALT. -< DEFALT. -``` - -### Factory Reset - In one command - -``` -$ ./honeywell-config "DEFOVR;DEFALT." -> DEFOVR;DEFALT. -< DEFOVR. -< DEFALT. +DEFOVR. +DEFALT. ``` ### Switch Operation Mode @@ -53,38 +51,46 @@ USBHID: ``` $ ./honeywell-config PAP131. -> PAP131. -< PAP131. +PAP131. ``` -Keyboard: +Keyboard (PC): ``` $ ./honeywell-config PAP124. -> PAP124. -< PAP124. +PAP124. ``` +Keyboard (Apple): + +``` +$ ./honeywell-config PAP125. +PAP124. +``` + +Please note that when switching modes, the scanner restarts on its own, so some error messages are to be expected. + ### Add Prefix to Output The following adds the string `FCKAFD ` (ASCII hex `46 43 4B 41 46 44 20`) in front of every scanned barcode: ``` -$ ./honeywell-config PREBK246434B41464420. -> PREBK246434B41464420. -< PREBK246434B41464420. +$ ./honeywell-config PREBK29946434B41464420. +PREBK29946434B41464420. ``` Note how this behaves differently from if you were to configure the scanner in-band via scanning config barcodes. There you would scan a lot of individual codes from the manual: -1. `PREBK2.` -1. `K4K.` +1. `PREBK2.` (command) +1. `K9K.` (argument "99" = all code types) +1. `K9K.` +1. `K4K.` (ASCII hex 46) 1. `K6K.` 1. ... -1. `K2K.` +1. `K2K.` (ASCII hex 20) 1. `K0K.` -1. `MNUSAV.` +1. `MNUSAV.` (finish and save) On the USB config interface, all of that goes into a single string instead. @@ -92,19 +98,17 @@ On the USB config interface, all of that goes into a single string instead. ``` $ ./honeywell-config 'BEP?.' -> BEP?. -< BEPFQ12550,FQ2100,RPT1,ERR1,BEP1,BIP0,LVL0,EXZ,GRX,EXE,DFT,LED1. +BEPFQ12550,FQ2100,RPT1,ERR1,BEP1,BIP0,LVL0,EXZ,GRX,EXE,DFT,LED1. ``` ### List all Settings and Their Possible Values ``` $ ./honeywell-config '*.' -> *. -< BEPFQ1100-5000. -< BEPFQ2100-5000. +BEPFQ1100-5000. +BEPFQ2100-5000. ... -< AXXMOD0|1|2|3|4|5|6|7|8|9|10. +AXXMOD0|1|2|3|4|5|6|7|8|9|10. ``` ## USB Device Access @@ -116,19 +120,6 @@ Put this file into `/etc/udev/rules.d`, and run `sudo udevadm control --reload` Once you replug the scanner, you should have access to it as a regular user. -## Adapting for Other Scanners - -The tool currently has hardcoded USB vendor/product IDs for a Honeywell Hyperion 1300G in Keyboard or USBHID modes. - -If you have a different Honeywell scanner, and the tool doesn't find it, you can try making this tool work for it: -1. Obtain the USB vendor and product IDs for your scanner, e.g. through `lsusb`. -1. Replace the VID/PID in the following line of code in `honeywell-config.c`: - - ```c - libusb_device_handle *devh = libusb_open_device_with_vid_pid(ctx, 0x0c2e, 0x0b07); - ``` -1. Recompile the tool with `make` - ## License MIT License diff --git a/honeywell-config.c b/honeywell-config.c index 25f675c..e177b51 100644 --- a/honeywell-config.c +++ b/honeywell-config.c @@ -1,75 +1,169 @@ #include #include +#include +#include #include -int main(int argc, char **argv) { - uint8_t cmd[64]; - char tokbuf[1024]; +uint16_t vendor_id = 0; +uint16_t product_id = 0; +uint8_t reset = 0; +uint8_t oneline = 0; +uint8_t debug = 0; + +#define USBERR(msg, rc) { dprintf(2, "%s: %s\n", msg, libusb_strerror(rc)); } +#define DEBUG(...) { if (debug) { dprintf(2, __VA_ARGS__); } } + +uint32_t vidpid[] = { + 0x0c2e0b01, + 0x0c2e0b02, + 0x0c2e0b07, +}; + + +int config(int argc, char **args) { + uint8_t cmd[64] = { 0 }; + char tokbuf[1024] = { 0 }; libusb_context *ctx = NULL; + libusb_device *dev = NULL; + libusb_device_handle *devh = NULL; + struct libusb_device_descriptor devd = { 0 }; + struct libusb_config_descriptor *devc = NULL; + const struct libusb_interface *devi = NULL; + const struct libusb_interface_descriptor *devid = NULL; + const struct libusb_endpoint_descriptor *deved = NULL; + char devdesc[256] = { 0 }; + int rc = 0; + + uint8_t ints[32] = { 0 }; + uint8_t inti = 0; + uint8_t endpoint = 0; // // Find and open the device // - libusb_init(&ctx); + rc = libusb_init(&ctx); if (!ctx) { - perror("failed to initialize libusb"); + USBERR("failed to initialize libusb", rc); return 1; } - libusb_device_handle *devh = libusb_open_device_with_vid_pid(ctx, 0x0c2e, 0x0b07); - if (!devh) { - devh = libusb_open_device_with_vid_pid(ctx, 0x0c2e, 0x0b01); + if (vendor_id || product_id) { + devh = libusb_open_device_with_vid_pid(ctx, vendor_id, product_id); if (!devh) { - perror("failed to open device"); + dprintf(2, "device %04x:%04x not found\n", vendor_id, product_id); + return 1; + } + } else { + devh = NULL; + for (uint8_t i = 0; i < sizeof(vidpid)/sizeof(vidpid[0]) && !devh; ++i) { + devh = libusb_open_device_with_vid_pid(ctx, vidpid[i] >> 16, vidpid[i] & 0xffff); + } + if (!devh) { + dprintf(2, "default devices not found, please provide vendor and product id\n"); return 1; } } + // Get endpoint infos + dev = libusb_get_device(devh); + rc = libusb_get_device_descriptor(dev, &devd); + if (rc) { + USBERR("failed to get device descriptor", rc); + return 1; + } + for (uint8_t c = 0; c < devd.bNumConfigurations; ++c) { + libusb_free_config_descriptor(devc); + rc = libusb_get_config_descriptor(dev, c, &devc); + if (rc) { + USBERR("failed to get config descriptor", rc); + continue; + } + rc = libusb_get_string_descriptor_ascii(devh, devc->iConfiguration, (uint8_t *) devdesc, 256); + if (rc < 0) { + USBERR("failed to get config string descriptor", rc); + continue; + } + DEBUG("- configuration %d: %s\n", c, devdesc); + for (uint8_t i = 0; i < devc->bNumInterfaces; ++i) { + DEBUG(" - interface %d\n", i); + devi = &(devc->interface[i]); + for (uint8_t a = 0; a < devi->num_altsetting; ++a) { + devid = &(devi->altsetting[a]); + rc = libusb_get_string_descriptor_ascii(devh, devid->iInterface, (uint8_t *) devdesc, 256); + if (rc < 0) { + USBERR("failed to get interface string descriptor", rc); + continue; + } + DEBUG(" - altsetting %d: interface=%d, desc=%s class=%d subclass=%d protocol=%d\n", + devid->bAlternateSetting, devid->bInterfaceNumber, devdesc, + devid->bInterfaceClass, devid->bInterfaceSubClass, devid->bInterfaceProtocol); + for (uint8_t e = 0; e < devid->bNumEndpoints; ++e) { + deved = &(devid->endpoint[e]); + char type = "csbi"[deved->bmAttributes & 0b00000011]; + DEBUG(" - endpoint %x: t=%c\n", deved->bEndpointAddress, type); + // build a list of interfaces so that we can claim all of them + ints[inti++] = devid->bInterfaceNumber; + // endpoint selection: + if ((deved->bmAttributes & 0b11) == LIBUSB_ENDPOINT_TRANSFER_TYPE_INTERRUPT // must be an interrupt endpoint + && ((deved->bEndpointAddress & 0x80) == LIBUSB_ENDPOINT_IN) // must be a device to host endpoint + && !strncmp("REM", devdesc, 3) // human readable description of the interface must be "REM" (Honeywell "Remote MasterMind" control interface) + ) { + endpoint = deved->bEndpointAddress; + } + } + } + } + } + + DEBUG("chose endpoint %x\n", endpoint); + // // Disconnect usbhid driver from both device interfaces // - if (libusb_set_auto_detach_kernel_driver(devh, 1)) { - perror("auto-attaching kernel driver not possible; please replug USB when done"); - for (uint8_t i = 0; i < 2; ++i) { - int ret = libusb_kernel_driver_active(devh, i); - if (ret < 0) { - perror("failed to query kernel driver state"); + rc = libusb_set_auto_detach_kernel_driver(devh, 1); + if (rc) { + USBERR("auto-attaching kernel driver not possible; please replug USB when done", rc); + for (uint8_t i = 0; i < inti; ++i) { + rc = libusb_kernel_driver_active(devh, ints[i]); + if (rc < 0) { + USBERR("failed to query kernel driver state", rc); return 1; } - if (ret > 0) { - ret = libusb_detach_kernel_driver(devh, i); - if (ret) { - perror("failed to detach kernel driver"); + if (rc > 0) { + rc = libusb_detach_kernel_driver(devh, ints[i]); + if (rc) { + USBERR("failed to detach kernel driver", rc); return 1; } } } } - for (uint8_t i = 0; i < 2; ++i) { - if (libusb_claim_interface(devh, i)) { - perror("failed to claim interface"); + + for (uint8_t i = 0; i < inti; ++i) { + rc = libusb_claim_interface(devh, ints[i]); + if (rc) { + USBERR("failed to claim interface", rc); return 1; } } - // // Prepare and send a message on the control channel for each config argument // - for (int i = 1; i < argc; ++i) { + for (int i = 0; i < argc; ++i) { memset(cmd+5, 0, 59); cmd[0] = 0xfd; - cmd[1] = strlen(argv[i])+3; + cmd[1] = strlen(args[i])+3; cmd[2] = 0x16; cmd[3] = 0x4d; cmd[4] = 0x0d; - memcpy(cmd+5, argv[i], strlen(argv[i])); - int ret = libusb_control_transfer(devh, 0x21, 0x09, 0x02fd, 1, cmd, 64, 0); - if (ret < 0) { - perror("failed to send control message"); + memcpy(cmd+5, args[i], strlen(args[i])); + rc = libusb_control_transfer(devh, 0x21, 0x09, 0x02fd, 1, cmd, 64, 0); + if (rc < 0) { + USBERR("failed to send control message", rc); } else { - printf("> %s\n", argv[i]); + DEBUG("> %s\n", args[i]); } int tx; tokbuf[64] = 0; @@ -77,13 +171,14 @@ int main(int argc, char **argv) { uint8_t eot = 0; // Repeat until a "." (EOT) is encountered in the response. while (!eot) { - ret = libusb_interrupt_transfer(devh, 0x83, cmd, 64, &tx, 0); - if (ret) { - perror("failed to receive response"); + rc = libusb_interrupt_transfer(devh, endpoint, cmd, 64, &tx, 1000); + if (rc) { + USBERR("failed to receive response from device", rc); break; } // strip 2-byte header and AIMID - start = cmd+5; + start = (char *) cmd+5; + DEBUG("< %s\n", start); while (1) { // non-final tokens: terminated by ; end = strchr(start, ';'); @@ -93,7 +188,11 @@ int main(int argc, char **argv) { tok += end-start-1; } *tok = 0; - printf("< %s.\n", tokbuf); + if (oneline) { + printf("%s;", tokbuf); + } else { + printf("%s.\n", tokbuf); + } tok = tokbuf; *tok = 0; start = end + 1; @@ -108,7 +207,7 @@ int main(int argc, char **argv) { tok += end-start-1; } *tok = 0; - printf("< %s.\n", tokbuf); + printf("%s.\n", tokbuf); tok = tokbuf; *tok = 0; break; @@ -128,11 +227,72 @@ int main(int argc, char **argv) { // // Cleanup // - for (uint8_t i = 0; i < 2; ++i) { - if (libusb_release_interface(devh, i)) { - perror("failed to release interface"); + for (uint8_t i = 0; i < inti; ++i) { + rc = libusb_release_interface(devh, ints[i]); + if (rc) { + USBERR("failed to release interface", rc); + return 1; } } + if (reset) { + libusb_reset_device(devh); + } libusb_close(devh); libusb_exit(ctx); + return 0; +} + + +int main(int argc, char **argv) { + int opt; + char *end; + long parsed; + while ((opt = getopt(argc, argv, "v:p:ord")) != -1) { + switch (opt) { + case 'v': + end = optarg; + parsed = strtol(optarg, &end, 16); + if (*end != 0) { + perror("failed to parse vendor id"); + return 1; + } + if (parsed <= 0 || parsed > 0xffff) { + puts("invalid vendor id"); + return 1; + } + vendor_id = (uint16_t) parsed; + break; + case 'p': + end = optarg; + parsed = strtol(optarg, &end, 16); + if (*end != 0) { + perror("failed to parse product id"); + return 1; + } + if (parsed <= 0 || parsed > 0xffff) { + puts("invalid product id"); + return 1; + } + product_id = (uint16_t) parsed; + break; + case 'r': + reset = 1; + break; + case 'o': + oneline = 1; + break; + case 'd': + debug = 1; + break; + default: + printf("usage: %s [-v vendor] [-p product] [-o] [-r] [-d] command1 [command2 [... commandN]]\n", argv[0]); + puts(" -v vendor\tUSB vendor ID of the barcode scanner"); + puts(" -p product\tUSB product ID of the barcode scanner"); + puts(" -o\t\tPrint response on one line, rather than one token per line"); + puts(" -r\t\tReset device after configuration"); + puts(" -d\t\tDebug mode (more verbose output)"); + return 1; + } + } + return config(argc-optind, argv+optind); }