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?
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

View file

@ -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.");