feat: iterate usb devices and find compatible ones
All checks were successful
/ build_debian (push) Successful in 30s

This commit is contained in:
s3lph 2024-12-05 03:02:28 +01:00
parent 4d9be10dd9
commit d7223710ea
Signed by: s3lph
GPG key ID: 0AA29A52FB33CFB5
2 changed files with 125 additions and 101 deletions

View file

@ -6,7 +6,7 @@ A Linux tool for configuring Honeywell barcode scanners via USB.
## Why? ## 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. 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. 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]] ./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 -v vendor USB vendor ID of the barcode scanner to configure.
-p product USB product ID of the barcode scanner -p product USB product ID of the barcode scanner to configure.
-1 Print response on one line, rather than one token per line -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. -c Print only received commands that contain an argument. Useful for config backups.
-r Reset device after configuration -r Reset device after configuration.
-d Debug mode (more verbose output) -d Debug mode (more verbose output). Can be provided repeatedly to increase verbosity.
-i infile Read input from infile instead of stdin -i infile Read input from infile instead of stdin.
-o outfile Write output to outfile instead of stdout -o outfile Write output to outfile instead of stdout.
-h Show this help -h Show this help and exit.
-V Show the version number -V Show the version number and exit.
``` ```
If no vendor ID or product ID is provided, the following set of IDs is tried: 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.
- `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).
For the actual configuration commands, please refer to your scanner's manual. Here are some examples. 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 $ 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 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:
- `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:
```shell-session ```shell-session
$ honeywell-config -i scanner.conf $ honeywell-config -i scanner.conf

View file

@ -17,6 +17,7 @@
#define ISSTATUS(c) (*c == S_ENQ || *c == S_ACK || *c == S_NAK) #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 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 DEBUG(...) { if (debug >= 1) { dprintf(2, __VA_ARGS__); } }
#define DEBUG2(...) { if (debug >= 2) { dprintf(2, __VA_ARGS__); } } #define DEBUG2(...) { if (debug >= 2) { dprintf(2, __VA_ARGS__); } }
#define DEBUG3(...) { if (debug >= 3) { dprintf(2, __VA_ARGS__); } } #define DEBUG3(...) { if (debug >= 3) { dprintf(2, __VA_ARGS__); } }
@ -25,6 +26,7 @@
uint16_t vendor_id = 0; uint16_t vendor_id = 0;
uint16_t product_id = 0; uint16_t product_id = 0;
char *serial_number = NULL;
uint8_t reset = 0; uint8_t reset = 0;
uint8_t oneline = 0; uint8_t oneline = 0;
uint8_t onlyargs = 0; uint8_t onlyargs = 0;
@ -35,14 +37,8 @@ uint8_t explicit_in = 0;
libusb_device_handle *devh = NULL; libusb_device_handle *devh = NULL;
uint8_t endpoint = 0; uint8_t endpoint = 0;
uint8_t ints[32] = { 0 };
uint8_t inti = 0;
uint32_t vidpid[] = {
0x0c2e0b01,
0x0c2e0b02,
0x0c2e0b07,
};
struct tokenizer { struct tokenizer {
char tokbuf[1024]; char tokbuf[1024];
@ -225,95 +221,61 @@ int config(int argc, char **args) {
} }
int usb(int argc, char **args) { int check_device(libusb_device *dev) {
libusb_context *ctx = NULL;
libusb_device *dev = NULL;
struct libusb_device_descriptor devd = { 0 }; struct libusb_device_descriptor devd = { 0 };
struct libusb_config_descriptor *devc = NULL; struct libusb_config_descriptor *devc = NULL;
const struct libusb_interface *devi = NULL; const struct libusb_interface *devi = NULL;
const struct libusb_interface_descriptor *devid = NULL; const struct libusb_interface_descriptor *devid = NULL;
const struct libusb_endpoint_descriptor *deved = NULL; const struct libusb_endpoint_descriptor *deved = NULL;
char devdesc[256] = { 0 }; char devdesc[256] = { 0 };
int rc = 0, fail = 0;
uint8_t ints[32] = { 0 }; // Top-level checks against VID, PID and Serial
uint8_t inti = 0; int rc = libusb_get_device_descriptor(dev, &devd);
//
// 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);
if (rc) { if (rc) {
USBERR("failed to get device descriptor", rc); USBDEBUG2("failed to get device descriptor", rc);
fail = 1; return rc;
goto fail;
} }
if (vendor_id && vendor_id != devd.idVendor) {
// Print device info return 1;
rc = libusb_get_string_descriptor_ascii(devh, devd.iManufacturer, (uint8_t *) devdesc, 256); }
if (product_id && product_id != devd.idProduct) {
return 1;
}
rc = libusb_open(dev, &devh);
if (rc < 0) { if (rc < 0) {
USBERR("failed to get manufacturer string descriptor", rc); USBDEBUG2("failed to open device", rc);
fail = 1; return rc;
goto fail;
} }
DEBUG("Manufacturer: %s\n", devdesc); if (serial_number) {
rc = libusb_get_string_descriptor_ascii(devh, devd.iProduct, (uint8_t *) devdesc, 256); rc = libusb_get_string_descriptor_ascii(devh, devd.iSerialNumber, (uint8_t *) devdesc, 256);
if (rc < 0) { if (rc < 0) {
USBERR("failed to get product string descriptor", rc); libusb_close(devh);
fail = 1; devh = NULL;
goto fail; 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 // Iterate all configurations, interfaces and endpoints to find the REM/EZConfig endpoint
for (uint8_t c = 0; c < devd.bNumConfigurations; ++c) { for (uint8_t c = 0; c < devd.bNumConfigurations; ++c) {
libusb_free_config_descriptor(devc); libusb_free_config_descriptor(devc);
rc = libusb_get_config_descriptor(dev, c, &devc); rc = libusb_get_config_descriptor(dev, c, &devc);
if (rc) { if (rc) {
USBERR("failed to get config descriptor", rc); USBDEBUG2("failed to get config descriptor", rc);
continue; continue;
} }
rc = libusb_get_string_descriptor_ascii(devh, devc->iConfiguration, (uint8_t *) devdesc, 256); rc = libusb_get_string_descriptor_ascii(devh, devc->iConfiguration, (uint8_t *) devdesc, 256);
if (rc < 0) { if (rc < 0) {
USBERR("failed to get config string descriptor", rc); USBDEBUG2("failed to get config string descriptor", rc);
continue; continue;
} }
DEBUG2("- configuration %d: %s\n", c, devdesc); DEBUG2("- configuration %d: %s\n", c, devdesc);
inti = 0;
for (uint8_t i = 0; i < devc->bNumInterfaces; ++i) { for (uint8_t i = 0; i < devc->bNumInterfaces; ++i) {
DEBUG2(" - interface %d\n", i); DEBUG2(" - interface %d\n", i);
devi = &(devc->interface[i]); devi = &(devc->interface[i]);
@ -321,7 +283,7 @@ int usb(int argc, char **args) {
devid = &(devi->altsetting[a]); devid = &(devi->altsetting[a]);
rc = libusb_get_string_descriptor_ascii(devh, devid->iInterface, (uint8_t *) devdesc, 256); rc = libusb_get_string_descriptor_ascii(devh, devid->iInterface, (uint8_t *) devdesc, 256);
if (rc < 0) { if (rc < 0) {
USBERR("failed to get interface string descriptor", rc); USBDEBUG2("failed to get interface string descriptor", rc);
continue; continue;
} }
DEBUG2(" - altsetting %d: interface=%d, desc=%s class=%d subclass=%d protocol=%d\n", 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) && !strncmp("REM", devdesc, 3) // human readable description of the interface must be "REM" (Honeywell "Remote MasterMind" control interface)
) { ) {
endpoint = deved->bEndpointAddress; 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 // Disconnect usbhid driver from both device interfaces
@ -405,6 +433,9 @@ int usb(int argc, char **args) {
} }
libusb_close(devh); libusb_close(devh);
} }
if (list) {
libusb_free_device_list(list, 1);
}
if (ctx) { if (ctx) {
libusb_exit(ctx); libusb_exit(ctx);
} }
@ -416,7 +447,7 @@ int main(int argc, char **argv) {
int opt; int opt;
char *end; char *end;
long parsed; 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) { switch (opt) {
case 'v': case 'v':
end = optarg; end = optarg;
@ -444,6 +475,9 @@ int main(int argc, char **argv) {
} }
product_id = (uint16_t) parsed; product_id = (uint16_t) parsed;
break; break;
case 's':
serial_number = strdup(optarg);
break;
case 'r': case 'r':
reset = 1; reset = 1;
break; 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]); 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(" -v vendor\tUSB vendor ID of the barcode scanner to configure.");
puts(" -p product\tUSB product 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(" -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(" -c\t\tPrint only received commands that contain an argument. Useful for config backups.");
puts(" -r\t\tReset device after configuration."); puts(" -r\t\tReset device after configuration.");