honeywell-config/honeywell-config.c
s3lph 447e1c29ab
feat: argument parsing with getopt
feat: enumerate usb interfaces and endpoints rather than hardcoding them
2024-11-27 02:44:39 +01:00

298 lines
7.8 KiB
C

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <libusb-1.0/libusb.h>
uint16_t vendor_id = 0;
uint16_t product_id = 0;
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_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
//
rc = libusb_init(&ctx);
if (!ctx) {
USBERR("failed to initialize libusb", rc);
return 1;
}
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);
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) {
dprintf(2, "default devices not found, please provide vendor and product id\n");
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
//
rc = libusb_set_auto_detach_kernel_driver(devh, 1);
if (rc) {
USBERR("auto-attaching kernel driver not possible; please replug USB when done", rc);
for (uint8_t i = 0; i < inti; ++i) {
rc = libusb_kernel_driver_active(devh, ints[i]);
if (rc < 0) {
USBERR("failed to query kernel driver state", rc);
return 1;
}
if (rc > 0) {
rc = libusb_detach_kernel_driver(devh, ints[i]);
if (rc) {
USBERR("failed to detach kernel driver", rc);
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
//
for (int i = 0; i < argc; ++i) {
memset(cmd+5, 0, 59);
cmd[0] = 0xfd;
cmd[1] = strlen(args[i])+3;
cmd[2] = 0x16;
cmd[3] = 0x4d;
cmd[4] = 0x0d;
memcpy(cmd+5, args[i], strlen(args[i]));
rc = libusb_control_transfer(devh, 0x21, 0x09, 0x02fd, 1, cmd, 64, 0);
if (rc < 0) {
USBERR("failed to send control message", rc);
} else {
DEBUG("> %s\n", args[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) {
rc = libusb_interrupt_transfer(devh, endpoint, cmd, 64, &tx, 1000);
if (rc) {
USBERR("failed to receive response from device", rc);
break;
}
// strip 2-byte header and AIMID
start = (char *) cmd+5;
DEBUG("< %s\n", start);
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;
if (oneline) {
printf("%s;", tokbuf);
} else {
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
//
for (uint8_t i = 0; i < inti; ++i) {
rc = libusb_release_interface(devh, ints[i]);
if (rc) {
USBERR("failed to release interface", rc);
return 1;
}
}
if (reset) {
libusb_reset_device(devh);
}
libusb_close(devh);
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);
}