My challenge then was to find some way of discovering the name of the character device under the "/dev" directory for a specific USB device. For instance, my RoboClaw gets a name of ttyUSBx, where "x" is some digit, if I plug the device into a USB hub (if it is discovered at all), but gets a similar name or sometimes "ttyACM0" if plugged directory into the Raspberry PI 2 without going though a hub.
With a lot of digging, I figured out how to traverse the "/sys/bus/usb/devices" tree to discover if there is a known USB device with the required vendor ID. Each of my devices, so far, has a unique vendor ID. If that is not sufficient, you could enhance my code to also test for the product ID.
If you have a USB device plugged in, you can do a "lsusb -v" command and filter through all the lines to discover your device's vendor and product IDs. But that's not the point of this little paper.
The function code is posted below. It returns a pointer to the device name (e.g., "/dev/ttyACM0") for the first USB device found that has the given vendor ID, or NULL if the device is not found. For example, to find the character device name of my RoboClaw USB device, here is some sample code:
You own the heap-allocated result returned by findUsbDeviceByVendorId, so be sure to free it if when you are done with the string. Here is the function. My next step will be to turn this into an executable service by itself so you can invoke it in scripts. There are printfs in there for debugging. You should probably remove them as well.
Oh, here is the gist of the code.
char* findUsbDeviceByVendorId(const char* vendorId) {
glob_t vendorGlob;
int vendorGlobErr = 0;
// Start by looking for all USB devices in the system that have an 'idVendor' file.
if ((vendorGlobErr = glob("/sys/bus/usb/devices/*/idVendor", 0, NULL, &vendorGlob)) == 0) {
// Found one or more idVendor files. Find the one wth a value matching the parameter.
for (unsigned int vendorGlobI = 0; vendorGlobI < vendorGlob.gl_pathc; vendorGlobI++) {
FILE* vendorFile = fopen(vendorGlob.gl_pathv[vendorGlobI], "r");
if (vendorFile != NULL) {
char *vendor = NULL;
size_t len = 0;
getline(&vendor, &len, vendorFile);
if (vendor[strlen(vendor) - 1] == '\n') vendor[strlen(vendor) - 1] = '\0'; // Remove any trailing space.
printf("idVendor path[%d]: '%s', value: %s\n", vendorGlobI, vendorGlob.gl_pathv[vendorGlobI], vendor);
if (strcmp(vendor, vendorId) != 0) {
// Not a match, go on to next one.
if (vendor) free(vendor);
continue;
} else {
printf("Found matching vendor value: '%s' at path: '%s'\n", vendor, vendorGlob.gl_pathv[vendorGlobI]);
}
if (vendor) free(vendor);
// Pull out the path of the directory containing the idVendor file.
regex_t parentRegex;
regmatch_t parentRegexMatch[2];
int regexResult;
if (regcomp(&parentRegex, "^(.*)idVendor[ \t\n]*$", REG_EXTENDED)) {
printf("!!! Unable to compile regex\n");
exit(EXIT_FAILURE);
}
if ((regexResult = regexec(&parentRegex, vendorGlob.gl_pathv[vendorGlobI], 2, parentRegexMatch, 0)) == 0) {
// Regex pulled out the parent path prefix from the idVendor full path. Form a path for just the parent directory.
char parentPath[strlen(vendorGlob.gl_pathv[vendorGlobI])];
strcpy(parentPath, vendorGlob.gl_pathv[vendorGlobI]);
parentPath[parentRegexMatch[1].rm_eo] = '\0';
printf("... idVendor parent path: '%s'\n", parentPath);
// Find subdirectories two levels down that have the name "tty" that are themselves a subdirectory
// of a directory that has "tty" as part of it's name prefix.
glob_t ttyGlob;
int ttyGlobErr = 0;
const char *TTY_GLOB_SUFFIX = "*/tty*/tty";
char ttyGlobPath[strlen(parentPath) + strlen(TTY_GLOB_SUFFIX) + 1]; // Reserve enough space for the glab expression.
strcpy(ttyGlobPath, parentPath); // Start to form the glob expression.
strcpy(&ttyGlobPath[strlen(ttyGlobPath)], TTY_GLOB_SUFFIX); // Finish the glob expresison.
printf("... ttyGlobPath: '%s'\n", ttyGlobPath);
if ((ttyGlobErr = glob(ttyGlobPath, 0, NULL, &ttyGlob)) == 0) {
// We have found the required directory. There will be a directory under here whose name is the
// device name under "/dev" for the USB device. E.g., if the directory is "ttyUSB0", then the
// corresponding device name is "/dev/ttyUSB0".
for (unsigned int ttyGlobI = 0; ttyGlobI < ttyGlob.gl_pathc; ttyGlobI++) {
printf("... ... tty path[%d]: '%s'\n", ttyGlobI, ttyGlob.gl_pathv[ttyGlobI]);
DIR *ttyDir;
if ((ttyDir = opendir(ttyGlob.gl_pathv[ttyGlobI])) != NULL) {
struct dirent *ttySubDir;
while ((ttySubDir = readdir(ttyDir)) != NULL) {
if (ttySubDir->d_name[0] == '.') continue;
printf("### device name: '%s'\n", ttySubDir->d_name);
const char *RESULT_PREFIX = "/dev/";
char *result = (char*) malloc(strlen(ttySubDir->d_name) + strlen(RESULT_PREFIX) + 1);
strcpy(result, RESULT_PREFIX);
strcpy(&result[strlen(RESULT_PREFIX)], ttySubDir->d_name);
return result;
}
// Didn't find any directories in the subdirectory.
return NULL;
} else {
// Could not read the subdirectory.
return NULL;
}
} // for
// No subdirectory found at all.
return NULL;
} else {
// Didn't find the exprected subdirectory.
return NULL;
}
} else {
// !!! Regex failure on idVendor glob.
return NULL;
}
} else {
// Unable to read the idVendor file.
return NULL;
}
} // for
// No match found for the parameter vendor ID.
return NULL;
} else {
// No vendor globs found.
return NULL;
}
}