From 1445529d1c63edc3a498ec082329bb2f059614f2 Mon Sep 17 00:00:00 2001 From: Federico Fissore Date: Mon, 4 Feb 2013 15:09:01 +0100 Subject: [PATCH] working on #223: Auto-detection of serial ports. Windows version ready --- .../app/windows/ListComPortsParser.java | 40 ++++++++++++++++++ app/src/processing/app/windows/Platform.java | 37 ++++++++++++++-- .../app/windows/ListComPortsParserTest.java | 20 +++++++++ build/build.xml | 2 + build/windows/listComPorts.exe | Bin 0 -> 44032 bytes 5 files changed, 95 insertions(+), 4 deletions(-) create mode 100644 app/src/processing/app/windows/ListComPortsParser.java create mode 100644 app/test/processing/app/windows/ListComPortsParserTest.java create mode 100644 build/windows/listComPorts.exe diff --git a/app/src/processing/app/windows/ListComPortsParser.java b/app/src/processing/app/windows/ListComPortsParser.java new file mode 100644 index 000000000..14a679956 --- /dev/null +++ b/app/src/processing/app/windows/ListComPortsParser.java @@ -0,0 +1,40 @@ +package processing.app.windows; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.StringReader; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * Interprets the output of listComPorts.exe + *

+ * https://github.com/todbot/usbSearch/ + */ +public class ListComPortsParser { + + private final Pattern vidRegExp; + private final Pattern pidRegExp; + + public ListComPortsParser() { + vidRegExp = Pattern.compile("VID_(\\w\\w\\w\\w)"); + pidRegExp = Pattern.compile("PID_(\\w\\w\\w\\w)"); + } + + public String extractVIDAndPID(String output, String serial) throws IOException { + BufferedReader reader = new BufferedReader(new StringReader(output)); + String line; + while ((line = reader.readLine()) != null) { + if (line.startsWith(serial.toUpperCase())) { + Matcher vidMatcher = vidRegExp.matcher(line); + Matcher pidMatcher = pidRegExp.matcher(line); + if (vidMatcher.find() && pidMatcher.find()) { + return vidMatcher.group(1).toUpperCase() + "_" + pidMatcher.group(1).toUpperCase(); + } + } + } + + return null; + } + +} diff --git a/app/src/processing/app/windows/Platform.java b/app/src/processing/app/windows/Platform.java index f45db5a68..9cac00bda 100644 --- a/app/src/processing/app/windows/Platform.java +++ b/app/src/processing/app/windows/Platform.java @@ -22,18 +22,24 @@ package processing.app.windows; -import java.io.File; -import java.io.UnsupportedEncodingException; - import com.sun.jna.Library; import com.sun.jna.Native; - +import org.apache.commons.exec.CommandLine; +import org.apache.commons.exec.Executor; import processing.app.Base; import processing.app.Preferences; +import processing.app.debug.TargetPackage; +import processing.app.tools.ExternalProcessExecutor; import processing.app.windows.Registry.REGISTRY_ROOT_KEY; import processing.core.PApplet; import processing.core.PConstants; +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.IOException; +import java.io.UnsupportedEncodingException; +import java.util.Map; + // http://developer.apple.com/documentation/QuickTime/Conceptual/QT7Win_Update_Guide/Chapter03/chapter_3_section_1.html // HKEY_LOCAL_MACHINE\SOFTWARE\Apple Computer, Inc.\QuickTime\QTSysDir @@ -309,4 +315,27 @@ public class Platform extends processing.app.Platform { return PConstants.platformNames[PConstants.WINDOWS]; } + @Override + public String resolveDeviceAttachedTo(String serial, Map packages) { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + Executor executor = new ExternalProcessExecutor(baos); + + try { + String listComPorts = new File(System.getProperty("user.dir"), "hardware/tools/listComPorts.exe").getCanonicalPath(); + + CommandLine toDevicePath = CommandLine.parse(listComPorts); + executor.execute(toDevicePath); + String vidPid = new ListComPortsParser().extractVIDAndPID(new String(baos.toByteArray()), serial); + + if (vidPid == null) { + return super.resolveDeviceAttachedTo(serial, packages); + } + + return super.resolveDeviceByVendorIdProductId(packages, vidPid); + } catch (IOException e) { + return super.resolveDeviceAttachedTo(serial, packages); + } + } + + } diff --git a/app/test/processing/app/windows/ListComPortsParserTest.java b/app/test/processing/app/windows/ListComPortsParserTest.java new file mode 100644 index 000000000..89539e205 --- /dev/null +++ b/app/test/processing/app/windows/ListComPortsParserTest.java @@ -0,0 +1,20 @@ +package processing.app.windows; + +import org.junit.Test; + +import static org.junit.Assert.assertEquals; + +public class ListComPortsParserTest { + + @Test + public void shouldFindVIDPID() throws Exception { + String listComPortsOutput = "COM26 - FTDI - FTDIBUS\\VID_0403+PID_6001+A6004CCFA\\0000\nCOM24 - PJRC.COM, LLC. - USB\\VID_16C0&PID_0483\\12345"; + + ListComPortsParser parser = new ListComPortsParser(); + + assertEquals("0403_6001", parser.extractVIDAndPID(listComPortsOutput, "COM26")); + assertEquals("16C0_0483", parser.extractVIDAndPID(listComPortsOutput, "COM24")); + } + + +} diff --git a/build/build.xml b/build/build.xml index 22917e80f..a574c3fb6 100644 --- a/build/build.xml +++ b/build/build.xml @@ -669,9 +669,11 @@ + + diff --git a/build/windows/listComPorts.exe b/build/windows/listComPorts.exe new file mode 100644 index 0000000000000000000000000000000000000000..ddb78d7bbd07d540cd139ed9b12638ee4e6c50e4 GIT binary patch literal 44032 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