feat: use libusb rather than usbdevfs to unclaim kernel driver

feat: read and print responses from barcode scanner
feat: udev rule
docs: add more usage examples
This commit is contained in:
s3lph 2024-11-26 19:56:30 +01:00
parent 21603523de
commit 18f2d75d91
Signed by: s3lph
GPG key ID: 0AA29A52FB33CFB5
4 changed files with 152 additions and 42 deletions

2
99-honeywell.rules Normal file
View file

@ -0,0 +1,2 @@
SUBSYSTEM=="usb", ATTRS{idVendor}=="0c2e", ATTRS{idProduct}=="0b07", MODE="0666"
SUBSYSTEM=="usb", ATTRS{idVendor}=="0c2e", ATTRS{idProduct}=="0b01", MODE="0666"

View file

@ -1,8 +1,8 @@
.PHONY: clean .PHONY: honeywell-config clean
honeywell-config: honeywell-config:
gcc -lusb-1.0 -o honeywell-config honeywell-config.c gcc -lusb-1.0 -o honeywell-config honeywell-config.c
clean: clean:
rm honeywell-config rm -f honeywell-config

View file

@ -7,7 +7,9 @@ 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 Hyperion 1300G 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.
To not rely on a Windows VM for configuring the scanner, I sniffed the USB traffic using Wireshark and extracted the relevant messages, and wrapped them in this tool. To not rely on a Windows VM for configuring the scanner, I sniffed the USB traffic using Wireshark and extracted the relevant messages, and wrapped them in this tool.
## Usage ## Usage
@ -20,14 +22,29 @@ The configuration strings need to be provided as command line arguments. You ca
./honeywell-config <string1> [... <stringN>] ./honeywell-config <string1> [... <stringN>]
``` ```
The commands sent to the scanner are prefixed with `>`, the (tokenized) responses from the scanner with `<`.
In general, please refer to your scanner's manual for configuration strings. In general, please refer to your scanner's manual for configuration strings.
Here are some examples. Please not that this tool most likely needs to run as root. Here are some examples.
### Factory Reset ### Factory Reset
``` ```
./honeywell-config DEFOVR. DEFALT. $ ./honeywell-config DEFOVR. DEFALT.
> DEFOVR.
< DEFOVR.
> DEFALT.
< DEFALT.
```
### Factory Reset - In one command
```
$ ./honeywell-config "DEFOVR;DEFALT."
> DEFOVR;DEFALT.
< DEFOVR.
< DEFALT.
``` ```
### Switch Operation Mode ### Switch Operation Mode
@ -35,13 +52,17 @@ Here are some examples. Please not that this tool most likely needs to run as r
USBHID: USBHID:
``` ```
./honeywell-config PAP131. $ ./honeywell-config PAP131.
> PAP131.
< PAP131.
``` ```
Keyboard: Keyboard:
``` ```
./honeywell-config PAP124. $ ./honeywell-config PAP124.
> PAP124.
< PAP124.
``` ```
### Add Prefix to Output ### Add Prefix to Output
@ -49,7 +70,9 @@ Keyboard:
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 PREBK246434B41464420.
> PREBK246434B41464420.
< 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.
@ -65,9 +88,38 @@ There you would scan a lot of individual codes from the manual:
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.
### Query Current Beeper Settings
```
$ ./honeywell-config 'BEP?.'
> BEP?.
< 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.
...
< AXXMOD0|1|2|3|4|5|6|7|8|9|10.
```
## USB Device Access
Please note that this tool normally needs to run as root. Alternatively, you can set up a udev rule to grant access to the USB device to non-root users.
An example for this can be found in the file `99-honeywell.rules`.
Put this file into `/etc/udev/rules.d`, and run `sudo udevadm control --reload` to load the new ruleset.
Once you replug the scanner, you should have access to it as a regular user.
## Adapting for Other Scanners ## Adapting for Other Scanners
The tool currently has hardcoded USB vendor/product IDs for a Honeywell Hyperion 1300G in Keyboard or USBHID modes. 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: 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. 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`: 1. Replace the VID/PID in the following line of code in `honeywell-config.c`:
@ -75,7 +127,7 @@ If you have a different Honeywell scanner, and the tool doesn't find it, you can
```c ```c
libusb_device_handle *devh = libusb_open_device_with_vid_pid(ctx, 0x0c2e, 0x0b07); libusb_device_handle *devh = libusb_open_device_with_vid_pid(ctx, 0x0c2e, 0x0b07);
``` ```
1. Recompile the tool with `make clean; make` 1. Recompile the tool with `make`
## License ## License

View file

@ -1,21 +1,11 @@
#include <stdio.h> #include <stdio.h>
#include <string.h> #include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/ioctl.h>
#include <fcntl.h>
#include <linux/ioctl.h>
#include <linux/usbdevice_fs.h>
#include <libusb-1.0/libusb.h> #include <libusb-1.0/libusb.h>
// Template for control messages, obtained through packet capture
unsigned char *CMD = "\xfd\x00\x16M\r\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00";
int main(int argc, char **argv) { int main(int argc, char **argv) {
uint8_t cmd[64]; uint8_t cmd[64];
memcpy(cmd, CMD, 64); char tokbuf[1024];
struct usbdevfs_ioctl command;
libusb_context *ctx = NULL; libusb_context *ctx = NULL;
// //
@ -35,48 +25,114 @@ int main(int argc, char **argv) {
return 1; return 1;
} }
} }
libusb_device *dev = libusb_get_device(devh);
if (!dev) {
perror("failed to get device info");
return 1;
}
// //
// Disconnect usbhid driver from both device interfaces // Disconnect usbhid driver from both device interfaces
// //
char usbdevfs[32]; if (libusb_set_auto_detach_kernel_driver(devh, 1)) {
snprintf(usbdevfs, 32, "/dev/bus/usb/%03d/%03d", libusb_get_bus_number(dev), libusb_get_device_address(dev)); perror("auto-attaching kernel driver not possible; please replug USB when done");
// usbdevfs/ioctl inspired from for (uint8_t i = 0; i < 2; ++i) {
// https://www.linuxquestions.org/questions/linux-hardware-18/how-to-unclaim-usb-device-558138/#post3406986 int ret = libusb_kernel_driver_active(devh, i);
int fd = open(usbdevfs,O_RDWR); if (ret < 0) {
if (fd < 1) { perror("failed to query kernel driver state");
perror("failed to open usbdevfs file"); return 1;
return 1; }
} if (ret > 0) {
for (uint8_t i = 0; i < 2; ++i) { ret = libusb_detach_kernel_driver(devh, i);
command.ifno = i; if (ret) {
command.ioctl_code = USBDEVFS_DISCONNECT; perror("failed to detach kernel driver");
command.data = NULL; return 1;
int ret = ioctl(fd, USBDEVFS_IOCTL, &command); }
if (ret < 0) { }
perror("failed to unclaim USB interface from usbhid");
} }
} }
for (uint8_t i = 0; i < 2; ++i) {
if (libusb_claim_interface(devh, i)) {
perror("failed to claim interface");
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 = 1; i < argc; ++i) {
memset(cmd+5, 0, 59); memset(cmd+5, 0, 59);
cmd[0] = 0xfd;
cmd[1] = strlen(argv[i])+3; cmd[1] = strlen(argv[i])+3;
cmd[2] = 0x16;
cmd[3] = 0x4d;
cmd[4] = 0x0d;
memcpy(cmd+5, argv[i], strlen(argv[i])); memcpy(cmd+5, argv[i], strlen(argv[i]));
libusb_control_transfer(devh, 0x21, 0x09, 0x02fd, 1, cmd, 64, 0); int ret = libusb_control_transfer(devh, 0x21, 0x09, 0x02fd, 1, cmd, 64, 0);
if (ret < 0) {
perror("failed to send control message");
} else {
printf("> %s\n", argv[i]);
}
int tx;
tokbuf[64] = 0;
char *start, *end, *tok = tokbuf;
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");
break;
}
// strip 2-byte header and AIMID
start = cmd+5;
while (1) {
// non-final tokens: terminated by ;
end = strchr(start, ';');
if (end) {
if (end > start) {
memcpy(tok, start, end-start-1);
tok += end-start-1;
}
*tok = 0;
printf("< %s.\n", tokbuf);
tok = tokbuf;
*tok = 0;
start = end + 1;
continue;
}
// last token: terminated by .
end = strchr(start, '.');
if (end) {
eot = 1;
if (end > start) {
memcpy(tok, start, end-start-1);
tok += end-start-1;
}
*tok = 0;
printf("< %s.\n", tokbuf);
tok = tokbuf;
*tok = 0;
break;
}
// incomplete tokens continued in the next message
end = strchr(start, '?');
if (end) {
*end = 0;
}
strcpy(tok, start);
tok += strlen(start);
break;
}
}
} }
// //
// Cleanup // Cleanup
// //
libusb_release_interface(devh, 1); for (uint8_t i = 0; i < 2; ++i) {
if (libusb_release_interface(devh, i)) {
perror("failed to release interface");
}
}
libusb_close(devh); libusb_close(devh);
libusb_exit(ctx); libusb_exit(ctx);
} }