From d7223710eaad391e16af4548ad89aa2b85929340 Mon Sep 17 00:00:00 2001 From: s3lph Date: Thu, 5 Dec 2024 03:02:28 +0100 Subject: [PATCH] feat: iterate usb devices and find compatible ones --- README.md | 41 ++++------ honeywell-config.c | 185 +++++++++++++++++++++++++++------------------ 2 files changed, 125 insertions(+), 101 deletions(-) diff --git a/README.md b/README.md index 72abc2d..c404a84 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ A Linux tool for configuring Honeywell barcode scanners via USB. ## Why? -I had obtained a used Honeywell Hyperion 1300G scanner, however it was locked down and secured with a password, and the password was unknown. +I had obtained a used Honeywell barcode scanner, however it was locked down and secured with a password, and the password was unknown. There is a Windows-only configuration tool from Honeywell called "EZconfig" with which the barcode scanners can be configured via USB, even when locked down. @@ -19,26 +19,22 @@ First of all, compile the program using `make`. It depends only on `libusb-1.0` The configuration commands can be provided either as CLI arguments or via stdin; if CLI commands are present, stdin is ignrored. ``` -honeywell-config [-v vendor] [-p product] [-1] [-c] [-r] [-d] [-i infile] [-o outfile] [command1 [... commandN]] - -v vendor USB vendor ID of the barcode scanner - -p product USB product ID of the barcode scanner - -1 Print response on one line, rather than one token per line +./honeywell-config [-v vendor] [-p product] [-1] [-c] [-r] [-d] [-i infile] [-o outfile] [command1 [... commandN]] + -v vendor USB vendor ID of the barcode scanner to configure. + -p product USB product ID of the barcode scanner to configure. + -s serial USB serial number of the barcode scanner to configure. + -1 Print response on one line, rather than one token per line. -c Print only received commands that contain an argument. Useful for config backups. - -r Reset device after configuration - -d Debug mode (more verbose output) - -i infile Read input from infile instead of stdin - -o outfile Write output to outfile instead of stdout - -h Show this help - -V Show the version number + -r Reset device after configuration. + -d Debug mode (more verbose output). Can be provided repeatedly to increase verbosity. + -i infile Read input from infile instead of stdin. + -o outfile Write output to outfile instead of stdout. + -h Show this help and exit. + -V Show the version number and exit. ``` -If no vendor ID or product ID is provided, the following set of IDs is tried: - -- `0c2e:0b01` (Honeywell 1300G, USB Kkeyboard mode PC) -- `0c2e:0b02` (Honeywell 1300G, USB Keyboard Apple) -- `0c2e:0b07` (Honeywell 1300G, USB HID POS mode) - -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). +All USB devices on the host are iterated, and the first one with an EZConfig configuration endpoint is chosen. +If there are multiple compatible devices on the host, you can control which device is selected by providing a vendor ID, product ID or serial number. For the actual configuration commands, please refer to your scanner's manual. Here are some examples. @@ -52,14 +48,7 @@ To only retrieve the config commands that have a value associated with them, use $ honeywell-config -c -o scanner.conf ``` -This configuration can be restored the the scanner by providing it as an input file using `-i`. Each sent config command should be acknowledged -- `0c2e:0b01` (Honeywell 1300G, USB Kkeyboard mode PC) -- `0c2e:0b02` (Honeywell 1300G, USB Keyboard Apple) -- `0c2e:0b07` (Honeywell 1300G, USB HID POS mode) - -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). - by the scanner and printed to stdout: - +This configuration can be restored the the scanner by providing it as an input file using `-i`. Each sent config command should be acknowledged by the scanner and printed to stdout: ```shell-session $ honeywell-config -i scanner.conf diff --git a/honeywell-config.c b/honeywell-config.c index 7b75c7b..9f56258 100644 --- a/honeywell-config.c +++ b/honeywell-config.c @@ -17,6 +17,7 @@ #define ISSTATUS(c) (*c == S_ENQ || *c == S_ACK || *c == S_NAK) #define USBERR(msg, rc) { dprintf(2, "%s: %s\n", msg, libusb_strerror(rc)); } +#define USBDEBUG2(msg, rc) { if (debug >= 3) { dprintf(2, "%s: %s\n", msg, libusb_strerror(rc)); } } #define DEBUG(...) { if (debug >= 1) { dprintf(2, __VA_ARGS__); } } #define DEBUG2(...) { if (debug >= 2) { dprintf(2, __VA_ARGS__); } } #define DEBUG3(...) { if (debug >= 3) { dprintf(2, __VA_ARGS__); } } @@ -25,6 +26,7 @@ uint16_t vendor_id = 0; uint16_t product_id = 0; +char *serial_number = NULL; uint8_t reset = 0; uint8_t oneline = 0; uint8_t onlyargs = 0; @@ -35,14 +37,8 @@ uint8_t explicit_in = 0; libusb_device_handle *devh = NULL; uint8_t endpoint = 0; - - -uint32_t vidpid[] = { - 0x0c2e0b01, - 0x0c2e0b02, - 0x0c2e0b07, -}; - +uint8_t ints[32] = { 0 }; +uint8_t inti = 0; struct tokenizer { char tokbuf[1024]; @@ -225,95 +221,61 @@ int config(int argc, char **args) { } -int usb(int argc, char **args) { - libusb_context *ctx = NULL; - libusb_device *dev = NULL; +int check_device(libusb_device *dev) { 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, fail = 0; - uint8_t ints[32] = { 0 }; - uint8_t inti = 0; - - // - // Find and open the device - // - rc = libusb_init(&ctx); - if (!ctx) { - USBERR("failed to initialize libusb", rc); - fail = 1; - goto fail; - } - - if (vendor_id || product_id) { - devh = libusb_open_device_with_vid_pid(ctx, vendor_id, product_id); - if (!devh) { - dprintf(2, "device %04x:%04x not found\n", vendor_id, product_id); - fail = 1; - goto fail; - } - } 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"); - fail = 1; - goto fail; - } - } - - // Get endpoint infos - dev = libusb_get_device(devh); - rc = libusb_get_device_descriptor(dev, &devd); + // Top-level checks against VID, PID and Serial + int rc = libusb_get_device_descriptor(dev, &devd); if (rc) { - USBERR("failed to get device descriptor", rc); - fail = 1; - goto fail; + USBDEBUG2("failed to get device descriptor", rc); + return rc; } - - // Print device info - rc = libusb_get_string_descriptor_ascii(devh, devd.iManufacturer, (uint8_t *) devdesc, 256); + if (vendor_id && vendor_id != devd.idVendor) { + return 1; + } + if (product_id && product_id != devd.idProduct) { + return 1; + } + rc = libusb_open(dev, &devh); if (rc < 0) { - USBERR("failed to get manufacturer string descriptor", rc); - fail = 1; - goto fail; + USBDEBUG2("failed to open device", rc); + return rc; } - DEBUG("Manufacturer: %s\n", devdesc); - rc = libusb_get_string_descriptor_ascii(devh, devd.iProduct, (uint8_t *) devdesc, 256); - if (rc < 0) { - USBERR("failed to get product string descriptor", rc); - fail = 1; - goto fail; + if (serial_number) { + rc = libusb_get_string_descriptor_ascii(devh, devd.iSerialNumber, (uint8_t *) devdesc, 256); + if (rc < 0) { + libusb_close(devh); + devh = NULL; + USBDEBUG2("failed to get serial id string descriptor", rc); + return rc; + } + if (strncmp(serial_number, devdesc, strlen(serial_number))) { + libusb_close(devh); + devh = NULL; + return 1; + } } - DEBUG("Product: %s\n", devdesc); - rc = libusb_get_string_descriptor_ascii(devh, devd.iSerialNumber, (uint8_t *) devdesc, 256); - if (rc < 0) { - USBERR("failed to get serial number string descriptor", rc); - fail = 1; - goto fail; - } - DEBUG("Serial Number: %s\n", devdesc); // Iterate all configurations, interfaces and endpoints to find the REM/EZConfig endpoint 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); + USBDEBUG2("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); + USBDEBUG2("failed to get config string descriptor", rc); continue; } DEBUG2("- configuration %d: %s\n", c, devdesc); + inti = 0; for (uint8_t i = 0; i < devc->bNumInterfaces; ++i) { DEBUG2(" - interface %d\n", i); devi = &(devc->interface[i]); @@ -321,7 +283,7 @@ int usb(int argc, char **args) { 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); + USBDEBUG2("failed to get interface string descriptor", rc); continue; } DEBUG2(" - altsetting %d: interface=%d, desc=%s class=%d subclass=%d protocol=%d\n", @@ -339,13 +301,79 @@ int usb(int argc, char **args) { && !strncmp("REM", devdesc, 3) // human readable description of the interface must be "REM" (Honeywell "Remote MasterMind" control interface) ) { endpoint = deved->bEndpointAddress; + // Print device info + rc = libusb_get_string_descriptor_ascii(devh, devd.iManufacturer, (uint8_t *) devdesc, 256); + if (rc < 0) { + USBDEBUG2("failed to get manufacturer string descriptor", rc); + libusb_close(devh); + devh = NULL; + return 1; + } + DEBUG("Manufacturer: %s\n", devdesc); + rc = libusb_get_string_descriptor_ascii(devh, devd.iProduct, (uint8_t *) devdesc, 256); + if (rc < 0) { + USBDEBUG2("failed to get product string descriptor", rc); + libusb_close(devh); + devh = NULL; + return 1; + } + DEBUG("Product: %s\n", devdesc); + rc = libusb_get_string_descriptor_ascii(devh, devd.iSerialNumber, (uint8_t *) devdesc, 256); + if (rc < 0) { + USBDEBUG2("failed to get serial number string descriptor", rc); + libusb_close(devh); + devh = NULL; + return 1; + } + DEBUG("Serial Number: %s\n", devdesc); + DEBUG2("chose endpoint %x\n", endpoint); + return 0; } } } } } + libusb_close(devh); + devh = NULL; + return 1; +} - DEBUG2("chose endpoint %x\n", endpoint); + +int usb(int argc, char **args) { + libusb_context *ctx = NULL; + libusb_device **list = NULL; + int rc = 0, fail = 0; + + // + // Find and open the device + // + rc = libusb_init(&ctx); + if (!ctx) { + USBERR("failed to initialize libusb", rc); + fail = 1; + goto fail; + } + + // + // Iterate list of USB devices + // + ssize_t ndev = libusb_get_device_list(ctx, &list); + if (ndev < 0) { + USBERR("failed to list usb devices", ndev); + fail = 1; + goto fail; + } + for (size_t i = 0; i < ndev; ++i) { + rc = check_device(list[i]); + if (!rc) { + break; + } + } + if (!devh) { + dprintf(2, "failed to find compatible device\n"); + fail = 1; + goto fail; + } // // Disconnect usbhid driver from both device interfaces @@ -405,6 +433,9 @@ int usb(int argc, char **args) { } libusb_close(devh); } + if (list) { + libusb_free_device_list(list, 1); + } if (ctx) { libusb_exit(ctx); } @@ -416,7 +447,7 @@ int main(int argc, char **argv) { int opt; char *end; long parsed; - while ((opt = getopt(argc, argv, "v:p:1crdi:o:hV")) != -1) { + while ((opt = getopt(argc, argv, "v:p:s:1crdi:o:hV")) != -1) { switch (opt) { case 'v': end = optarg; @@ -444,6 +475,9 @@ int main(int argc, char **argv) { } product_id = (uint16_t) parsed; break; + case 's': + serial_number = strdup(optarg); + break; case 'r': reset = 1; break; @@ -487,6 +521,7 @@ int main(int argc, char **argv) { printf("%s [-v vendor] [-p product] [-1] [-c] [-r] [-d] [-i infile] [-o outfile] [command1 [... commandN]]\n", argv[0]); puts(" -v vendor\tUSB vendor ID of the barcode scanner to configure."); puts(" -p product\tUSB product ID of the barcode scanner to configure."); + puts(" -s serial\tUSB serial number of the barcode scanner to configure."); puts(" -1\t\tPrint response on one line, rather than one token per line."); puts(" -c\t\tPrint only received commands that contain an argument. Useful for config backups."); puts(" -r\t\tReset device after configuration.");