|
|
|
#!/usr/bin/env perl
|
|
|
|
|
|
|
|
# Based on listArduinos.pl from https://github.com/todbot/usbSearch (License: MIT)
|
|
|
|
# Original (C) 2012, Tod E. Kurt, http://todbot.com/blog/
|
|
|
|
# This version by Michael Richters <gedankenexperimenter@gmail.com> and Jesse Vincent <jesse@keyboard.io>
|
|
|
|
|
|
|
|
use warnings;
|
|
|
|
use strict;
|
|
|
|
|
|
|
|
my $vid = shift @ARGV;
|
|
|
|
my $pid = shift @ARGV;
|
|
|
|
|
|
|
|
if (!defined $vid || !defined $pid) {
|
|
|
|
die "$0 has two required parameters, VID and PID";
|
|
|
|
}
|
|
|
|
|
|
|
|
# ioreg might be more machine-readable than system_profiler, but I haven't been able to
|
|
|
|
# get it to produce useful output
|
|
|
|
my @output = qx(/usr/sbin/system_profiler SPUSBDataType 2> /dev/null);
|
|
|
|
|
|
|
|
my $serial = "";
|
|
|
|
my $location = "";
|
|
|
|
|
|
|
|
my $output = join('', @output);
|
|
|
|
|
|
|
|
my @stanzas = split(/\n\n/, $output);
|
|
|
|
foreach my $stanza (@stanzas) {
|
|
|
|
if ($stanza =~ /Product ID: ${pid}/ && $stanza =~ /Vendor ID: ${vid}/) {
|
|
|
|
if ($stanza =~ /Serial Number: (.*?)$/m) {
|
|
|
|
$serial = $1;
|
|
|
|
}
|
|
|
|
if ($stanza =~ /Location ID: (.*?)$/m) {
|
|
|
|
$location = $1;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ($serial) {
|
|
|
|
try_for_raw_serialnum($serial);
|
|
|
|
}
|
|
|
|
if ($location) {
|
|
|
|
try_for_location_id($location);
|
|
|
|
}
|
|
|
|
if ($serial) {
|
|
|
|
try_for_sn_prefix($serial);
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
sub try_for_raw_serialnum {
|
|
|
|
my $sn = shift;
|
|
|
|
|
|
|
|
my $serial_port_name = "/dev/cu.usbmodem" . $sn;
|
|
|
|
exit_with_port_if_exists($serial_port_name);
|
|
|
|
|
|
|
|
# High Sierra sometimes has a mismatch between the serial number and the device
|
|
|
|
# filename. I'm not sure why, but system_profiler has a serial number ending in "E",
|
|
|
|
# whereas the device filename ends in "1". In fact, when I change HID.getShortName()
|
|
|
|
# to return "kbio02", the final character is replaced with a "1".
|
|
|
|
|
|
|
|
if ($serial_port_name =~ /\d$/) {
|
|
|
|
chop $serial_port_name;
|
|
|
|
exit_with_port_if_exists($serial_port_name . "1");
|
|
|
|
} else {
|
|
|
|
# If the serial port name doesn't end with a digit, try -appending- rather than replacing
|
|
|
|
# the last character of the port name
|
|
|
|
exit_with_port_if_exists($serial_port_name . "1");
|
|
|
|
|
|
|
|
# and if that didn't work, try replacing the last character with a "1" anyway.
|
|
|
|
# Jason Koh reports that he saw this behavior as required on Catalina in May 2020.
|
|
|
|
|
|
|
|
chop $serial_port_name;
|
|
|
|
exit_with_port_if_exists($serial_port_name . "1");
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
sub try_for_location_id {
|
|
|
|
my $location_id = shift;
|
|
|
|
|
|
|
|
# macOS truncates the string of "0"s from the right of the location id.
|
|
|
|
# Here, also, the final character is an appended "1", so if macOS ever stops doing that,
|
|
|
|
# this will need an update, as well.
|
|
|
|
if ($location_id =~ /0x(\d+?)0*\b/) {
|
|
|
|
my $loc = $1;
|
|
|
|
exit_with_port_if_exists("/dev/cu.usbmodem" . $loc . "1");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
sub try_for_sn_prefix {
|
|
|
|
my $sn = shift;
|
|
|
|
# If macOS has appended 'E', take it off to maximise our chances of a match.
|
|
|
|
$sn =~ s/E$//;
|
|
|
|
|
|
|
|
# If none of the above tests succeeds, just list the directory and see if there are any
|
|
|
|
# files that have the device shortname that we expect:
|
|
|
|
foreach my $line (qx(ls /dev/cu.usbmodem*)) {
|
|
|
|
if ($line =~ /${sn}/) {
|
|
|
|
chomp $line;
|
|
|
|
print $line;
|
|
|
|
exit 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
sub exit_with_port_if_exists {
|
|
|
|
my $serial_port_name = shift;
|
|
|
|
|
|
|
|
if (-e $serial_port_name) {
|
|
|
|
print $serial_port_name;
|
|
|
|
exit 0;
|
|
|
|
}
|
|
|
|
}
|