feat: argument parsing with getopt

feat: enumerate usb interfaces and endpoints rather than hardcoding them
This commit is contained in:
s3lph 2024-11-27 02:44:39 +01:00
parent 18f2d75d91
commit 447e1c29ab
Signed by: s3lph
GPG key ID: 0AA29A52FB33CFB5
4 changed files with 241 additions and 89 deletions

View file

@ -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}=="0b01", MODE="0666"
SUBSYSTEM=="usb", ATTRS{idVendor}=="0c2e", ATTRS{idProduct}=="0b02", MODE="0666"
SUBSYSTEM=="usb", ATTRS{idVendor}=="0c2e", ATTRS{idProduct}=="0b07", MODE="0666"

View file

@ -2,7 +2,7 @@
.PHONY: honeywell-config clean .PHONY: honeywell-config clean
honeywell-config: 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: clean:
rm -f honeywell-config rm -f honeywell-config

View file

@ -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: The configuration strings need to be provided as command line arguments. You can provide multiple:
``` ```
./honeywell-config <string1> [... <stringN>] 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 ### Factory Reset
``` ```
$ ./honeywell-config DEFOVR. DEFALT. $ ./honeywell-config DEFOVR. DEFALT.
> DEFOVR. DEFOVR.
< DEFOVR. DEFALT.
> DEFALT.
< DEFALT.
```
### Factory Reset - In one command
```
$ ./honeywell-config "DEFOVR;DEFALT."
> DEFOVR;DEFALT.
< DEFOVR.
< DEFALT.
``` ```
### Switch Operation Mode ### Switch Operation Mode
@ -53,38 +51,46 @@ USBHID:
``` ```
$ ./honeywell-config PAP131. $ ./honeywell-config PAP131.
> PAP131. PAP131.
< PAP131.
``` ```
Keyboard: Keyboard (PC):
``` ```
$ ./honeywell-config PAP124. $ ./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 ### 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: The following adds the string `FCKAFD ` (ASCII hex `46 43 4B 41 46 44 20`) in front of every scanned barcode:
``` ```
$ ./honeywell-config PREBK246434B41464420. $ ./honeywell-config PREBK29946434B41464420.
> PREBK246434B41464420. PREBK29946434B41464420.
< PREBK246434B41464420.
``` ```
Note how this behaves differently from if you were to configure the scanner in-band via scanning config barcodes. 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: There you would scan a lot of individual codes from the manual:
1. `PREBK2.` 1. `PREBK2.` (command)
1. `K4K.` 1. `K9K.` (argument "99" = all code types)
1. `K9K.`
1. `K4K.` (ASCII hex 46)
1. `K6K.` 1. `K6K.`
1. ... 1. ...
1. `K2K.` 1. `K2K.` (ASCII hex 20)
1. `K0K.` 1. `K0K.`
1. `MNUSAV.` 1. `MNUSAV.` (finish and save)
On the USB config interface, all of that goes into a single string instead. 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?.' $ ./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 ### List all Settings and Their Possible Values
``` ```
$ ./honeywell-config '*.' $ ./honeywell-config '*.'
> *. BEPFQ1100-5000.
< BEPFQ1100-5000. BEPFQ2100-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 ## 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. 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 ## License
MIT License MIT License

View file

@ -1,75 +1,169 @@
#include <stdio.h> #include <stdio.h>
#include <string.h> #include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <libusb-1.0/libusb.h> #include <libusb-1.0/libusb.h>
int main(int argc, char **argv) { uint16_t vendor_id = 0;
uint8_t cmd[64]; uint16_t product_id = 0;
char tokbuf[1024]; 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_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 // Find and open the device
// //
libusb_init(&ctx); rc = libusb_init(&ctx);
if (!ctx) { if (!ctx) {
perror("failed to initialize libusb"); USBERR("failed to initialize libusb", rc);
return 1; return 1;
} }
libusb_device_handle *devh = libusb_open_device_with_vid_pid(ctx, 0x0c2e, 0x0b07); if (vendor_id || product_id) {
devh = libusb_open_device_with_vid_pid(ctx, vendor_id, product_id);
if (!devh) { if (!devh) {
devh = libusb_open_device_with_vid_pid(ctx, 0x0c2e, 0x0b01); 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) { if (!devh) {
perror("failed to open device"); dprintf(2, "default devices not found, please provide vendor and product id\n");
return 1; 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 // Disconnect usbhid driver from both device interfaces
// //
if (libusb_set_auto_detach_kernel_driver(devh, 1)) { rc = libusb_set_auto_detach_kernel_driver(devh, 1);
perror("auto-attaching kernel driver not possible; please replug USB when done"); if (rc) {
for (uint8_t i = 0; i < 2; ++i) { USBERR("auto-attaching kernel driver not possible; please replug USB when done", rc);
int ret = libusb_kernel_driver_active(devh, i); for (uint8_t i = 0; i < inti; ++i) {
if (ret < 0) { rc = libusb_kernel_driver_active(devh, ints[i]);
perror("failed to query kernel driver state"); if (rc < 0) {
USBERR("failed to query kernel driver state", rc);
return 1; return 1;
} }
if (ret > 0) { if (rc > 0) {
ret = libusb_detach_kernel_driver(devh, i); rc = libusb_detach_kernel_driver(devh, ints[i]);
if (ret) { if (rc) {
perror("failed to detach kernel driver"); USBERR("failed to detach kernel driver", rc);
return 1; return 1;
} }
} }
} }
} }
for (uint8_t i = 0; i < 2; ++i) {
if (libusb_claim_interface(devh, i)) {
perror("failed to claim interface");
return 1;
}
}
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 // 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); memset(cmd+5, 0, 59);
cmd[0] = 0xfd; cmd[0] = 0xfd;
cmd[1] = strlen(argv[i])+3; cmd[1] = strlen(args[i])+3;
cmd[2] = 0x16; cmd[2] = 0x16;
cmd[3] = 0x4d; cmd[3] = 0x4d;
cmd[4] = 0x0d; cmd[4] = 0x0d;
memcpy(cmd+5, argv[i], strlen(argv[i])); memcpy(cmd+5, args[i], strlen(args[i]));
int ret = libusb_control_transfer(devh, 0x21, 0x09, 0x02fd, 1, cmd, 64, 0); rc = libusb_control_transfer(devh, 0x21, 0x09, 0x02fd, 1, cmd, 64, 0);
if (ret < 0) { if (rc < 0) {
perror("failed to send control message"); USBERR("failed to send control message", rc);
} else { } else {
printf("> %s\n", argv[i]); DEBUG("> %s\n", args[i]);
} }
int tx; int tx;
tokbuf[64] = 0; tokbuf[64] = 0;
@ -77,13 +171,14 @@ int main(int argc, char **argv) {
uint8_t eot = 0; uint8_t eot = 0;
// Repeat until a "." (EOT) is encountered in the response. // Repeat until a "." (EOT) is encountered in the response.
while (!eot) { while (!eot) {
ret = libusb_interrupt_transfer(devh, 0x83, cmd, 64, &tx, 0); rc = libusb_interrupt_transfer(devh, endpoint, cmd, 64, &tx, 1000);
if (ret) { if (rc) {
perror("failed to receive response"); USBERR("failed to receive response from device", rc);
break; break;
} }
// strip 2-byte header and AIMID // strip 2-byte header and AIMID
start = cmd+5; start = (char *) cmd+5;
DEBUG("< %s\n", start);
while (1) { while (1) {
// non-final tokens: terminated by ; // non-final tokens: terminated by ;
end = strchr(start, ';'); end = strchr(start, ';');
@ -93,7 +188,11 @@ int main(int argc, char **argv) {
tok += end-start-1; tok += end-start-1;
} }
*tok = 0; *tok = 0;
printf("< %s.\n", tokbuf); if (oneline) {
printf("%s;", tokbuf);
} else {
printf("%s.\n", tokbuf);
}
tok = tokbuf; tok = tokbuf;
*tok = 0; *tok = 0;
start = end + 1; start = end + 1;
@ -108,7 +207,7 @@ int main(int argc, char **argv) {
tok += end-start-1; tok += end-start-1;
} }
*tok = 0; *tok = 0;
printf("< %s.\n", tokbuf); printf("%s.\n", tokbuf);
tok = tokbuf; tok = tokbuf;
*tok = 0; *tok = 0;
break; break;
@ -128,11 +227,72 @@ int main(int argc, char **argv) {
// //
// Cleanup // Cleanup
// //
for (uint8_t i = 0; i < 2; ++i) { for (uint8_t i = 0; i < inti; ++i) {
if (libusb_release_interface(devh, i)) { rc = libusb_release_interface(devh, ints[i]);
perror("failed to release interface"); if (rc) {
USBERR("failed to release interface", rc);
return 1;
} }
} }
if (reset) {
libusb_reset_device(devh);
}
libusb_close(devh); libusb_close(devh);
libusb_exit(ctx); 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);
} }