diff --git a/app/.classpath b/app/.classpath index f32ff129bf7..374fca9f467 100644 --- a/app/.classpath +++ b/app/.classpath @@ -38,7 +38,7 @@ - + diff --git a/app/lib/jmdns-3.4.1.jar b/app/lib/jmdns-3.4.1.jar deleted file mode 100644 index 4fcd002b4b5..00000000000 Binary files a/app/lib/jmdns-3.4.1.jar and /dev/null differ diff --git a/app/lib/jmdns-3.4.2.jar b/app/lib/jmdns-3.4.2.jar new file mode 100644 index 00000000000..3112053f297 Binary files /dev/null and b/app/lib/jmdns-3.4.2.jar differ diff --git a/app/src/cc/arduino/UpdatableBoardsLibsFakeURLsHandler.java b/app/src/cc/arduino/UpdatableBoardsLibsFakeURLsHandler.java index 112296b43b8..4cc377ea903 100644 --- a/app/src/cc/arduino/UpdatableBoardsLibsFakeURLsHandler.java +++ b/app/src/cc/arduino/UpdatableBoardsLibsFakeURLsHandler.java @@ -53,22 +53,28 @@ public void hyperlinkUpdate(HyperlinkEvent event) { } URL url = event.getURL(); + openBoardLibManager(url); + } + public void openBoardLibManager(URL url) { if (BOARDSMANAGER.equals(url.getHost())) { try { - base.openBoardsManager("", "DropdownUpdatableCoresItem"); + base.openBoardsManager(url.getRef() == null ? "": url.getRef() , url.getPath() == null ? "" : url.getPath().replace("/", "")); } catch (Exception e) { e.printStackTrace(); } return; } + System.out.println(url.getRef() + " " + url.getHost() + " " + url.getPath()); + if (LIBRARYMANAGER.equals(url.getHost())) { - base.openLibraryManager("DropdownUpdatableLibrariesItem"); + base.openLibraryManager(url.getRef() == null ? "": url.getRef() , url.getPath() == null ? "" : url.getPath().replace("/", "")); return; } throw new IllegalArgumentException(url.getHost() + " is invalid"); + } } diff --git a/app/src/cc/arduino/contributions/ContributionsSelfCheck.java b/app/src/cc/arduino/contributions/ContributionsSelfCheck.java index 1031effc3e5..636319d29b4 100644 --- a/app/src/cc/arduino/contributions/ContributionsSelfCheck.java +++ b/app/src/cc/arduino/contributions/ContributionsSelfCheck.java @@ -83,9 +83,9 @@ public void run() { String text; if (updatableLibraries > 0 && updatablePlatforms <= 0) { - text = I18n.format(tr("Updates available for some of your {0}libraries{1}"), "", ""); + text = I18n.format(tr("Updates available for some of your {0}libraries{1}"), "", ""); } else if (updatableLibraries <= 0 && updatablePlatforms > 0) { - text = I18n.format(tr("Updates available for some of your {0}boards{1}"), "", ""); + text = I18n.format(tr("Updates available for some of your {0}boards{1}"), "", ""); } else { text = I18n.format(tr("Updates available for some of your {0}boards{1} and {2}libraries{3}"), "", "", "", ""); } diff --git a/app/src/cc/arduino/contributions/libraries/filters/UpdatableLibraryPredicate.java b/app/src/cc/arduino/contributions/libraries/filters/UpdatableLibraryPredicate.java index 32165643b4a..821d14ce2ab 100644 --- a/app/src/cc/arduino/contributions/libraries/filters/UpdatableLibraryPredicate.java +++ b/app/src/cc/arduino/contributions/libraries/filters/UpdatableLibraryPredicate.java @@ -54,7 +54,8 @@ public boolean test(ContributedLibrary contributedLibrary) { } List libraries = BaseNoGui.librariesIndexer.getIndex().find(libraryName); return libraries.stream() - .filter(library -> versionComparator.greaterThan(library.getParsedVersion(), installed.getParsedVersion())) + .filter(library -> versionComparator.greaterThan(library.getParsedVersion(), installed.getParsedVersion()) + && library.getArchitectures().containsAll(installed.getArchitectures())) .count() > 0; } } diff --git a/app/src/cc/arduino/view/NotificationPopup.java b/app/src/cc/arduino/view/NotificationPopup.java index f5dfc717e9d..7ddbf2c3f3e 100644 --- a/app/src/cc/arduino/view/NotificationPopup.java +++ b/app/src/cc/arduino/view/NotificationPopup.java @@ -61,7 +61,7 @@ public NotificationPopup(Frame parent, HyperlinkListener hyperlinkListener, super(parent, false); setLayout(new FlowLayout()); setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE); - setAlwaysOnTop(true); + //setAlwaysOnTop(true); setUndecorated(true); setResizable(false); diff --git a/app/src/processing/app/Base.java b/app/src/processing/app/Base.java index 832973fe1ad..7f9906dad72 100644 --- a/app/src/processing/app/Base.java +++ b/app/src/processing/app/Base.java @@ -1135,7 +1135,7 @@ public void rebuildImportMenu(JMenu importMenu) { importMenu.removeAll(); JMenuItem menu = new JMenuItem(tr("Manage Libraries...")); - menu.addActionListener(e -> openLibraryManager("")); + menu.addActionListener(e -> openLibraryManager("", "")); importMenu.add(menu); importMenu.addSeparator(); @@ -1264,7 +1264,7 @@ public void onBoardOrPortChange() { } } - public void openLibraryManager(String dropdownItem) { + public void openLibraryManager(final String filterText, String dropdownItem) { if (contributionsSelfCheck != null) { contributionsSelfCheck.cancel(); } @@ -1280,6 +1280,9 @@ protected void onIndexesUpdated() throws Exception { if (StringUtils.isNotEmpty(dropdownItem)) { selectDropdownItemByClassName(dropdownItem); } + if (StringUtils.isNotEmpty(filterText)) { + setFilterText(filterText); + } } }; managerUI.setLocationRelativeTo(activeEditor); diff --git a/app/src/processing/app/Editor.java b/app/src/processing/app/Editor.java index 566cabf1c3f..d018b69b278 100644 --- a/app/src/processing/app/Editor.java +++ b/app/src/processing/app/Editor.java @@ -29,6 +29,7 @@ import cc.arduino.view.GoToLineNumber; import cc.arduino.view.StubMenuListener; import cc.arduino.view.findreplace.FindReplace; +import cc.arduino.UpdatableBoardsLibsFakeURLsHandler; import com.jcraft.jsch.JSchException; import jssc.SerialPortException; import org.fife.ui.rsyntaxtextarea.RSyntaxDocument; @@ -145,6 +146,8 @@ public boolean test(Sketch sketch) { private int numTools = 0; + public boolean avoidMultipleOperations = false; + private final EditorToolbar toolbar; // these menus are shared so that they needn't be rebuilt for all windows // each time a sketch is created, renamed, or moved. @@ -197,7 +200,7 @@ public boolean test(Sketch sketch) { private Runnable stopHandler; Runnable exportHandler; private Runnable exportAppHandler; - + private Runnable timeoutUploadHandler; public Editor(Base ibase, File file, int[] storedLocation, int[] defaultLocation, Platform platform) throws Exception { super("Arduino"); @@ -811,6 +814,9 @@ public void actionPerformed(ActionEvent e) { portMenu = new JMenu(tr("Port")); populatePortMenu(); toolsMenu.add(portMenu); + item = new JMenuItem(tr("Get Board Info")); + item.addActionListener(e -> handleBoardInfo()); + toolsMenu.add(item); toolsMenu.addSeparator(); base.rebuildProgrammerMenu(); @@ -1015,9 +1021,15 @@ private SketchTextArea createTextArea() throws IOException { textArea.setTabSize(PreferencesData.getInteger("editor.tabs.size")); textArea.addHyperlinkListener(evt -> { try { - platform.openURL(sketch.getFolder(), evt.getURL().toExternalForm()); - } catch (Exception e) { - Base.showWarning(e.getMessage(), e.getMessage(), e); + UpdatableBoardsLibsFakeURLsHandler boardLibHandler = new UpdatableBoardsLibsFakeURLsHandler(base); + boardLibHandler.openBoardLibManager(evt.getURL()); + } + catch (Exception e) { + try { + platform.openURL(sketch.getFolder(), evt.getURL().toExternalForm()); + } catch (Exception f) { + Base.showWarning(f.getMessage(), f.getMessage(), f); + } } }); textArea.addCaretListener(e -> { @@ -1641,6 +1653,7 @@ private void resetHandlers() { stopHandler = new DefaultStopHandler(); exportHandler = new DefaultExportHandler(); exportAppHandler = new DefaultExportAppHandler(); + timeoutUploadHandler = new TimeoutUploadHandler(); } @@ -1972,6 +1985,7 @@ public void run() { status.unprogress(); toolbar.deactivateRun(); + avoidMultipleOperations = false; } } @@ -2360,6 +2374,7 @@ synchronized public void handleExport(final boolean usingProgrammer) { console.clear(); status.progress(tr("Uploading to I/O Board...")); + new Thread(timeoutUploadHandler).start(); new Thread(usingProgrammer ? exportAppHandler : exportHandler).start(); } @@ -2399,6 +2414,7 @@ public void run() { e.printStackTrace(); } finally { populatePortMenu(); + avoidMultipleOperations = false; } status.unprogress(); uploading = false; @@ -2426,13 +2442,14 @@ private void resumeOrCloseSerialMonitor() { } } try { - if (serialMonitor != null) - serialMonitor.resume(boardPort); - if (boardPort == null) { - serialMonitor.close(); - handleSerial(); - } else { + if (serialMonitor != null) { serialMonitor.resume(boardPort); + if (boardPort == null) { + serialMonitor.close(); + handleSerial(); + } else { + serialMonitor.resume(boardPort); + } } } catch (Exception e) { statusError(e); @@ -2493,6 +2510,7 @@ public void run() { } catch (Exception e) { e.printStackTrace(); } finally { + avoidMultipleOperations = false; populatePortMenu(); } status.unprogress(); @@ -2507,6 +2525,20 @@ public void run() { } } + class TimeoutUploadHandler implements Runnable { + + public void run() { + try { + //10 seconds, than reactivate upload functionality and let the programmer pid being killed + Thread.sleep(1000 * 10); + if (uploading) { + avoidMultipleOperations = false; + } + } catch (InterruptedException e) { + // noop + } + } + } public void handleSerial() { if(serialPlotter != null) { @@ -2547,11 +2579,11 @@ public void handleSerial() { } serialMonitor = new MonitorFactory().newMonitor(port); - serialMonitor.setIconImage(getIconImage()); + base.setIcon(serialMonitor); // If currently uploading, disable the monitor (it will be later // enabled when done uploading) - if (uploading) { + if (uploading || avoidMultipleOperations) { try { serialMonitor.suspend(); } catch (Exception e) { @@ -2575,8 +2607,10 @@ public void handleSerial() { } try { - serialMonitor.open(); serialMonitor.setVisible(true); + if (!avoidMultipleOperations) { + serialMonitor.open(); + } success = true; } catch (ConnectException e) { statusError(tr("Unable to connect: is the sketch using the bridge?")); @@ -2643,7 +2677,7 @@ public void handlePlotter() { } serialPlotter = new SerialPlotter(port); - serialPlotter.setIconImage(getIconImage()); + base.setIcon(serialPlotter); // If currently uploading, disable the plotter (it will be later // enabled when done uploading) @@ -2723,6 +2757,59 @@ private void handleBurnBootloader() { }).start(); } + private void handleBoardInfo() { + console.clear(); + + String selectedPort = PreferencesData.get("serial.port"); + List ports = Base.getDiscoveryManager().discovery(); + + String label = ""; + String vid = ""; + String pid = ""; + String iserial = ""; + String protocol = ""; + boolean found = false; + + for (BoardPort port : ports) { + if (port.getAddress().equals(selectedPort)) { + label = port.getBoardName(); + vid = port.getVID(); + pid = port.getPID(); + iserial = port.getISerial(); + protocol = port.getProtocol(); + found = true; + break; + } + } + + if (!found) { + statusNotice(tr("Please select a port to obtain board info")); + return; + } + + if (protocol.equals("network")) { + statusNotice(tr("Network port, can't obtain info")); + return; + } + + if (vid == null || vid.equals("") || vid.equals("0000")) { + statusNotice(tr("Native serial port, can't obtain info")); + return; + } + + if (iserial == null || iserial.equals("")) { + iserial = tr("Upload any sketch to obtain it"); + } + + if (label == null) { + label = tr("Unknown board"); + } + + String infos = I18n.format("BN: {0}\nVID: {1}\nPID: {2}\nSN: {3}", label, vid, pid, iserial); + JTextArea textArea = new JTextArea(infos); + + JOptionPane.showMessageDialog(this, textArea, tr("Board Info"), JOptionPane.PLAIN_MESSAGE); + } /** * Handler for File → Page Setup. diff --git a/app/src/processing/app/EditorLineStatus.java b/app/src/processing/app/EditorLineStatus.java index b68b74b4674..506e8f56d71 100644 --- a/app/src/processing/app/EditorLineStatus.java +++ b/app/src/processing/app/EditorLineStatus.java @@ -52,6 +52,7 @@ public class EditorLineStatus extends JComponent { String text = ""; String name = ""; String serialport = ""; + String serialnumber = ""; public EditorLineStatus() { background = Theme.getColor("linestatus.bgcolor"); @@ -129,6 +130,10 @@ public void setSerialPort(String serialport) { this.serialport = serialport; } + public void setSerialNumber(String serialnumber) { + this.serialnumber = serialnumber; + } + public Dimension getPreferredSize() { return scale(new Dimension(300, height)); } diff --git a/app/src/processing/app/EditorToolbar.java b/app/src/processing/app/EditorToolbar.java index a6b8bce6047..a4d18b5f72d 100644 --- a/app/src/processing/app/EditorToolbar.java +++ b/app/src/processing/app/EditorToolbar.java @@ -341,7 +341,10 @@ public void mousePressed(MouseEvent e) { switch (sel) { case RUN: - editor.handleRun(false, editor.presentHandler, editor.runHandler); + if (!editor.avoidMultipleOperations) { + editor.handleRun(false, editor.presentHandler, editor.runHandler); + editor.avoidMultipleOperations = true; + } break; // case STOP: @@ -370,7 +373,11 @@ public void mousePressed(MouseEvent e) { break; case EXPORT: - editor.handleExport(e.isShiftDown()); + // launch a timeout timer which can reenable to upload button functionality an + if (!editor.avoidMultipleOperations) { + editor.handleExport(e.isShiftDown()); + editor.avoidMultipleOperations = true; + } break; case SERIAL: diff --git a/app/src/processing/app/Sketch.java b/app/src/processing/app/Sketch.java index ecbd54938a6..7bb7dee11e6 100644 --- a/app/src/processing/app/Sketch.java +++ b/app/src/processing/app/Sketch.java @@ -1164,7 +1164,8 @@ private boolean exportApplet(String appletPath, boolean usingProgrammer) private boolean upload(String buildPath, String suggestedClassName, boolean usingProgrammer) throws Exception { - Uploader uploader = new UploaderUtils().getUploaderByPreferences(false); + UploaderUtils uploaderInstance = new UploaderUtils(); + Uploader uploader = uploaderInstance.getUploaderByPreferences(false); boolean success = false; do { @@ -1183,7 +1184,7 @@ private boolean upload(String buildPath, String suggestedClassName, boolean usin List warningsAccumulator = new LinkedList<>(); try { - success = new UploaderUtils().upload(data, uploader, buildPath, suggestedClassName, usingProgrammer, false, warningsAccumulator); + success = uploaderInstance.upload(data, uploader, buildPath, suggestedClassName, usingProgrammer, false, warningsAccumulator); } finally { if (uploader.requiresAuthorization() && !success) { PreferencesData.remove(uploader.getAuthorizationKey()); @@ -1198,6 +1199,14 @@ private boolean upload(String buildPath, String suggestedClassName, boolean usin } while (uploader.requiresAuthorization() && !success); + if (!success) { + String errorMessage = uploader.getFailureMessage(); + if (errorMessage.equals("")) { + errorMessage = tr("An error occurred while uploading the sketch"); + } + editor.statusError(errorMessage); + } + return success; } diff --git a/arduino-core/.classpath b/arduino-core/.classpath index cf3a2da06c2..93dbc9f5cb4 100644 --- a/arduino-core/.classpath +++ b/arduino-core/.classpath @@ -5,7 +5,7 @@ - + diff --git a/arduino-core/lib/jmdns-3.4.1.jar b/arduino-core/lib/jmdns-3.4.1.jar deleted file mode 100644 index 4fcd002b4b5..00000000000 Binary files a/arduino-core/lib/jmdns-3.4.1.jar and /dev/null differ diff --git a/arduino-core/lib/jmdns-3.4.2.jar b/arduino-core/lib/jmdns-3.4.2.jar new file mode 100644 index 00000000000..3112053f297 Binary files /dev/null and b/arduino-core/lib/jmdns-3.4.2.jar differ diff --git a/arduino-core/src/cc/arduino/contributions/packages/ContributedPlatform.java b/arduino-core/src/cc/arduino/contributions/packages/ContributedPlatform.java index 1063b676a32..52305e66468 100644 --- a/arduino-core/src/cc/arduino/contributions/packages/ContributedPlatform.java +++ b/arduino-core/src/cc/arduino/contributions/packages/ContributedPlatform.java @@ -80,6 +80,7 @@ public void resolveToolsDependencies(Collection packages) { if (tool == null) { System.err.println("Index error: could not find referenced tool " + dep); } else { + tool.usetUserArchitecture(this.getParentPackage().getName()+"."+this.getArchitecture()); resolvedToolReferences.put(dep, tool); } } diff --git a/arduino-core/src/cc/arduino/contributions/packages/ContributedTool.java b/arduino-core/src/cc/arduino/contributions/packages/ContributedTool.java index 28d81771722..370177f0dd9 100644 --- a/arduino-core/src/cc/arduino/contributions/packages/ContributedTool.java +++ b/arduino-core/src/cc/arduino/contributions/packages/ContributedTool.java @@ -32,6 +32,7 @@ import cc.arduino.contributions.DownloadableContribution; import processing.app.Platform; +import java.util.LinkedList; import java.util.List; public abstract class ContributedTool { @@ -42,6 +43,20 @@ public abstract class ContributedTool { public abstract List getSystems(); + private LinkedList users = null; + public void usetUserArchitecture(String vendorAndArch) { + if (users == null) { + users = new LinkedList<>(); + } + if (!users.contains(vendorAndArch)) { + users.add(vendorAndArch); + } + } + + public List ugetUserArchitectures() { + return users; + } + public DownloadableContribution getDownloadableContribution(Platform platform) { for (HostDependentDownloadableContribution c : getSystems()) { if (c.isCompatible(platform)) diff --git a/arduino-core/src/cc/arduino/packages/BoardPort.java b/arduino-core/src/cc/arduino/packages/BoardPort.java index e2c16c43e69..0e85ffe135d 100644 --- a/arduino-core/src/cc/arduino/packages/BoardPort.java +++ b/arduino-core/src/cc/arduino/packages/BoardPort.java @@ -36,8 +36,12 @@ public class BoardPort { private String address; private String protocol; private String boardName; + private String vid; + private String pid; + private String iserial; private String label; private final PreferencesMap prefs; + private boolean online; public BoardPort() { this.prefs = new PreferencesMap(); @@ -79,4 +83,36 @@ public String getLabel() { return label; } + public void setOnlineStatus(boolean online) { + this.online = online; + } + + public boolean isOnline() { + return online; + } + + public void setVIDPID(String vid, String pid) { + this.vid = vid; + this.pid = pid; + } + + public String getVID() { + return vid; + } + + public String getPID() { + return pid; + } + + public void setISerial(String iserial) { + this.iserial = iserial; + } + public String getISerial() { + return iserial; + } + + @Override + public String toString() { + return this.address+"_"+this.vid+"_"+this.pid; + } } diff --git a/arduino-core/src/cc/arduino/packages/Discovery.java b/arduino-core/src/cc/arduino/packages/Discovery.java index eb4b41da2b1..911fcc2f6f5 100644 --- a/arduino-core/src/cc/arduino/packages/Discovery.java +++ b/arduino-core/src/cc/arduino/packages/Discovery.java @@ -51,5 +51,6 @@ public interface Discovery { * @return */ List listDiscoveredBoards(); + List listDiscoveredBoards(boolean complete); } diff --git a/arduino-core/src/cc/arduino/packages/DiscoveryManager.java b/arduino-core/src/cc/arduino/packages/DiscoveryManager.java index add7d0671e6..2632386d47e 100644 --- a/arduino-core/src/cc/arduino/packages/DiscoveryManager.java +++ b/arduino-core/src/cc/arduino/packages/DiscoveryManager.java @@ -40,11 +40,13 @@ public class DiscoveryManager { private final List discoverers; + private final SerialDiscovery serialDiscoverer = new SerialDiscovery(); + private final NetworkDiscovery networkDiscoverer = new NetworkDiscovery(); public DiscoveryManager() { discoverers = new ArrayList(); - discoverers.add(new SerialDiscovery()); - discoverers.add(new NetworkDiscovery()); + discoverers.add(serialDiscoverer); + discoverers.add(networkDiscoverer); // Start all discoverers for (Discovery d : discoverers) { @@ -69,6 +71,10 @@ public DiscoveryManager() { Runtime.getRuntime().addShutdownHook(closeHook); } + public SerialDiscovery getSerialDiscoverer() { + return serialDiscoverer; + } + public List discovery() { List res = new ArrayList(); for (Discovery d : discoverers) { @@ -77,6 +83,14 @@ public List discovery() { return res; } + public List discovery(boolean complete) { + List res = new ArrayList(); + for (Discovery d : discoverers) { + res.addAll(d.listDiscoveredBoards(complete)); + } + return res; + } + public BoardPort find(String address) { for (BoardPort boardPort : discovery()) { if (boardPort.getAddress().equals(address)) { @@ -86,4 +100,13 @@ public BoardPort find(String address) { return null; } + public BoardPort find(String address, boolean complete) { + for (BoardPort boardPort : discovery(complete)) { + if (boardPort.getAddress().equals(address)) { + return boardPort; + } + } + return null; + } + } diff --git a/arduino-core/src/cc/arduino/packages/Uploader.java b/arduino-core/src/cc/arduino/packages/Uploader.java index d58d29504f5..28a0ed4427c 100644 --- a/arduino-core/src/cc/arduino/packages/Uploader.java +++ b/arduino-core/src/cc/arduino/packages/Uploader.java @@ -44,6 +44,7 @@ import java.util.Arrays; import java.util.Collection; import java.util.List; +import java.util.concurrent.TimeUnit; import static processing.app.I18n.tr; @@ -85,7 +86,7 @@ protected Uploader(boolean nup) { } private void init(boolean nup) { - this.error = null; + this.error = ""; this.notFoundError = false; this.noUploadPort = nup; } @@ -102,6 +103,9 @@ public String getAuthorizationKey() { return null; } + // static field for last executed programmer process ID + static protected Process programmerPid; + protected boolean executeUploadCommand(Collection command) throws Exception { return executeUploadCommand(command.toArray(new String[command.size()])); } @@ -121,24 +125,31 @@ protected boolean executeUploadCommand(String command[]) throws Exception { System.out.println(); } Process process = ProcessUtils.exec(command); + programmerPid = process; new MessageSiphon(process.getInputStream(), this, 100); new MessageSiphon(process.getErrorStream(), this, 100); - // wait for the process to finish. - result = process.waitFor(); + // wait for the process to finish, but not forever + // kill the flasher process after 2 minutes to avoid 100% cpu spinning + if (!process.waitFor(2, TimeUnit.MINUTES)) { + process.destroyForcibly(); + } + if (!process.isAlive()) { + result = process.exitValue(); + } else { + result = 0; + } } catch (Exception e) { e.printStackTrace(); } - if (error != null) { - RunnerException exception = new RunnerException(error); - exception.hideStackTrace(); - throw exception; - } - return result == 0; } + public String getFailureMessage() { + return error; + } + public void message(String s) { // selectively suppress a bunch of avrdude output for AVR109/Caterina that should already be quelled but isn't if (!verbose && StringUtils.stringContainsOneOf(s, STRINGS_TO_SUPPRESS)) { @@ -148,8 +159,9 @@ public void message(String s) { System.err.print(s); // ignore cautions - if (s.contains("Error")) { + if (s.toLowerCase().contains("error")) { notFoundError = true; + error = s; return; } if (notFoundError) { diff --git a/arduino-core/src/cc/arduino/packages/discoverers/NetworkDiscovery.java b/arduino-core/src/cc/arduino/packages/discoverers/NetworkDiscovery.java index ebbb8c50df1..6fb15b24d82 100644 --- a/arduino-core/src/cc/arduino/packages/discoverers/NetworkDiscovery.java +++ b/arduino-core/src/cc/arduino/packages/discoverers/NetworkDiscovery.java @@ -31,82 +31,38 @@ import cc.arduino.packages.BoardPort; import cc.arduino.packages.Discovery; -import cc.arduino.packages.discoverers.network.BoardReachabilityFilter; -import cc.arduino.packages.discoverers.network.NetworkChecker; -import org.apache.commons.compress.utils.IOUtils; import processing.app.BaseNoGui; -import processing.app.zeroconf.jmdns.ArduinoDNSTaskStarter; import javax.jmdns.*; -import javax.jmdns.impl.DNSTaskStarter; import java.io.IOException; import java.net.InetAddress; import java.util.*; -public class NetworkDiscovery implements Discovery, ServiceListener, cc.arduino.packages.discoverers.network.NetworkTopologyListener { - - private static final int MAX_TIME_AWAITING_FOR_PACKAGES = 5000; - - private final List boardPortsDiscoveredWithJmDNS; - private final Map mappedJmDNSs; - private Timer networkCheckerTimer; - private Timer boardReachabilityFilterTimer; - private final List reachableBoardPorts; +import cc.arduino.packages.discoverers.network.BoardReachabilityFilter; - public NetworkDiscovery() { - DNSTaskStarter.Factory.setClassDelegate(new ArduinoDNSTaskStarter()); - this.boardPortsDiscoveredWithJmDNS = new LinkedList<>(); - this.mappedJmDNSs = new Hashtable<>(); - this.reachableBoardPorts = new LinkedList<>(); - } +public class NetworkDiscovery implements Discovery, ServiceListener { - @Override - public List listDiscoveredBoards() { - synchronized (reachableBoardPorts) { - return new LinkedList<>(reachableBoardPorts); - } - } + private static final int MAX_TIME_AWAITING_FOR_PACKAGES = 5000; - public void setReachableBoardPorts(List newReachableBoardPorts) { - synchronized (reachableBoardPorts) { - this.reachableBoardPorts.clear(); - this.reachableBoardPorts.addAll(newReachableBoardPorts); - } - } + private final List reachableBoardPorts = new LinkedList<>(); + private final List boardPortsDiscoveredWithJmDNS = new LinkedList<>(); + private Timer reachabilityTimer; + private JmDNS jmdns = null; - public List getBoardPortsDiscoveredWithJmDNS() { + private void removeDuplicateBoards(BoardPort newBoard) { synchronized (boardPortsDiscoveredWithJmDNS) { - return new LinkedList<>(boardPortsDiscoveredWithJmDNS); + Iterator iterator = boardPortsDiscoveredWithJmDNS.iterator(); + while (iterator.hasNext()) { + BoardPort board = iterator.next(); + if (newBoard.getAddress().equals(board.getAddress())) { + iterator.remove(); + } + } } } - @Override - public void start() throws IOException { - this.networkCheckerTimer = new Timer(NetworkChecker.class.getName()); - new NetworkChecker(this, NetworkTopologyDiscovery.Factory.getInstance()).start(networkCheckerTimer); - this.boardReachabilityFilterTimer = new Timer(BoardReachabilityFilter.class.getName()); - new BoardReachabilityFilter(this).start(boardReachabilityFilterTimer); - } - - @Override - public void stop() throws IOException { - this.networkCheckerTimer.purge(); - this.boardReachabilityFilterTimer.purge(); - // we don't close each JmDNS instance as it's too slow - } - @Override public void serviceAdded(ServiceEvent serviceEvent) { - String type = serviceEvent.getType(); - String name = serviceEvent.getName(); - - JmDNS dns = serviceEvent.getDNS(); - - dns.requestServiceInfo(type, name); - ServiceInfo serviceInfo = dns.getServiceInfo(type, name); - if (serviceInfo != null) { - dns.requestServiceInfo(type, name); - } } @Override @@ -119,11 +75,9 @@ public void serviceRemoved(ServiceEvent serviceEvent) { @Override public void serviceResolved(ServiceEvent serviceEvent) { - int sleptFor = 0; - while (BaseNoGui.packages == null && sleptFor <= MAX_TIME_AWAITING_FOR_PACKAGES) { + while (BaseNoGui.packages == null) { try { Thread.sleep(1000); - sleptFor += 1000; } catch (InterruptedException e) { e.printStackTrace(); } @@ -144,7 +98,7 @@ public void serviceResolved(ServiceEvent serviceEvent) { port.getPrefs().put("board", board); port.getPrefs().put("distro_version", info.getPropertyString("distro_version")); port.getPrefs().put("port", "" + info.getPort()); - + //Add additional fields to permit generic ota updates //and make sure we do not intefere with Arduino boards // define "ssh_upload=no" TXT property to use generic uploader @@ -183,35 +137,58 @@ public void serviceResolved(ServiceEvent serviceEvent) { } } - private void removeDuplicateBoards(BoardPort newBoard) { - synchronized (boardPortsDiscoveredWithJmDNS) { - Iterator iterator = boardPortsDiscoveredWithJmDNS.iterator(); - while (iterator.hasNext()) { - BoardPort board = iterator.next(); - if (newBoard.getAddress().equals(board.getAddress())) { - iterator.remove(); - } - } - } + public NetworkDiscovery() { + } @Override - public void inetAddressAdded(InetAddress address) { - if (mappedJmDNSs.containsKey(address)) { - return; + public void start() { + try { + jmdns = JmDNS.create(); + jmdns.addServiceListener("_arduino._tcp.local.", this); + } catch (IOException e) { + e.printStackTrace(); } + reachabilityTimer = new Timer(); + new BoardReachabilityFilter(this).start(reachabilityTimer); + } + + @Override + public void stop() { + jmdns.unregisterAllServices(); try { - JmDNS jmDNS = JmDNS.create(address); - jmDNS.addServiceListener("_arduino._tcp.local.", this); - mappedJmDNSs.put(address, jmDNS); - } catch (Exception e) { + jmdns.close(); + } catch (IOException e) { e.printStackTrace(); } + reachabilityTimer.cancel(); + start(); + } + + @Override + public List listDiscoveredBoards() { + synchronized (reachableBoardPorts) { + return new LinkedList<>(reachableBoardPorts); + } } @Override - public void inetAddressRemoved(InetAddress address) { - JmDNS jmDNS = mappedJmDNSs.remove(address); - IOUtils.closeQuietly(jmDNS); + public List listDiscoveredBoards(boolean complete) { + synchronized (reachableBoardPorts) { + return new LinkedList<>(reachableBoardPorts); + } + } + + public void setReachableBoardPorts(List newReachableBoardPorts) { + synchronized (reachableBoardPorts) { + this.reachableBoardPorts.clear(); + this.reachableBoardPorts.addAll(newReachableBoardPorts); + } + } + + public List getBoardPortsDiscoveredWithJmDNS() { + synchronized (boardPortsDiscoveredWithJmDNS) { + return new LinkedList<>(boardPortsDiscoveredWithJmDNS); + } } } diff --git a/arduino-core/src/cc/arduino/packages/discoverers/SerialDiscovery.java b/arduino-core/src/cc/arduino/packages/discoverers/SerialDiscovery.java index 9c3efdc5200..3e6646d6732 100644 --- a/arduino-core/src/cc/arduino/packages/discoverers/SerialDiscovery.java +++ b/arduino-core/src/cc/arduino/packages/discoverers/SerialDiscovery.java @@ -41,6 +41,7 @@ public class SerialDiscovery implements Discovery { private Timer serialBoardsListerTimer; private final List serialBoardPorts; + private SerialBoardsLister serialBoardsLister = new SerialBoardsLister(this);; public SerialDiscovery() { this.serialBoardPorts = new LinkedList<>(); @@ -48,26 +49,45 @@ public SerialDiscovery() { @Override public List listDiscoveredBoards() { - return getSerialBoardPorts(); + return getSerialBoardPorts(false); } - private List getSerialBoardPorts() { - synchronized (serialBoardPorts) { - return new LinkedList<>(serialBoardPorts); - } + public List listDiscoveredBoards(boolean complete) { + return getSerialBoardPorts(complete); + } + + private List getSerialBoardPorts(boolean complete) { + if (complete) { + return new LinkedList<>(serialBoardPorts); + } + List onlineBoardPorts = new LinkedList<>(); + for (BoardPort port : serialBoardPorts) { + if (port.isOnline() == true) { + onlineBoardPorts.add(port); + } + } + return onlineBoardPorts; } public void setSerialBoardPorts(List newSerialBoardPorts) { - synchronized (serialBoardPorts) { serialBoardPorts.clear(); serialBoardPorts.addAll(newSerialBoardPorts); - } } + public void forceRefresh() { + serialBoardsLister.retriggerDiscovery(false); + } + + public void setUploadInProgress(boolean param) { + serialBoardsLister.uploadInProgress = param; + } + + public void pausePolling(boolean param) { serialBoardsLister.pausePolling = param;} + @Override public void start() { this.serialBoardsListerTimer = new Timer(SerialBoardsLister.class.getName()); - new SerialBoardsLister(this).start(serialBoardsListerTimer); + serialBoardsLister.start(serialBoardsListerTimer); } @Override diff --git a/arduino-core/src/cc/arduino/packages/discoverers/network/BoardReachabilityFilter.java b/arduino-core/src/cc/arduino/packages/discoverers/network/BoardReachabilityFilter.java index 433142eb3b9..d4158be4795 100644 --- a/arduino-core/src/cc/arduino/packages/discoverers/network/BoardReachabilityFilter.java +++ b/arduino-core/src/cc/arduino/packages/discoverers/network/BoardReachabilityFilter.java @@ -34,23 +34,51 @@ import processing.app.helpers.NetUtils; import java.net.InetAddress; +import java.net.NetworkInterface; +import java.util.Enumeration; import java.net.UnknownHostException; import java.util.*; public class BoardReachabilityFilter extends TimerTask { private final NetworkDiscovery networkDiscovery; + private Enumeration staticNetworkInterfaces; + private final List staticNetworkInterfacesList = new LinkedList<>(); public BoardReachabilityFilter(NetworkDiscovery networkDiscovery) { this.networkDiscovery = networkDiscovery; } public void start(Timer timer) { - timer.schedule(this, 0, 3000); + try { + staticNetworkInterfaces = NetworkInterface.getNetworkInterfaces(); + while (staticNetworkInterfaces.hasMoreElements()) { + staticNetworkInterfacesList.add(staticNetworkInterfaces.nextElement().getInterfaceAddresses().toString()); + } + } catch (Exception e) { + e.printStackTrace(); + } + + timer.schedule(this, 0, 5000); } @Override public void run() { + + try { + Enumeration networkInterfaces = NetworkInterface.getNetworkInterfaces(); + while (networkInterfaces.hasMoreElements()) { + if (!staticNetworkInterfacesList.contains(networkInterfaces.nextElement().getInterfaceAddresses().toString())) { + networkDiscovery.stop(); + staticNetworkInterfacesList.clear(); + System.out.println("IP changed, restarting jmdns"); + return; + } + } + } catch (Exception e) { + e.printStackTrace(); + } + List boardPorts = networkDiscovery.getBoardPortsDiscoveredWithJmDNS(); Iterator boardPortIterator = boardPorts.iterator(); diff --git a/arduino-core/src/cc/arduino/packages/discoverers/network/NetworkChecker.java b/arduino-core/src/cc/arduino/packages/discoverers/network/NetworkChecker.java deleted file mode 100644 index df58c220d0a..00000000000 --- a/arduino-core/src/cc/arduino/packages/discoverers/network/NetworkChecker.java +++ /dev/null @@ -1,84 +0,0 @@ -/* - * This file is part of Arduino. - * - * Arduino is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA - * - * As a special exception, you may use this file as part of a free software - * library without restriction. Specifically, if other files instantiate - * templates or use macros or inline functions from this file, or you compile - * this file and link it with other files to produce an executable, this - * file does not by itself cause the resulting executable to be covered by - * the GNU General Public License. This exception does not however - * invalidate any other reasons why the executable file might be covered by - * the GNU General Public License. - * - * Copyright 2013 Arduino LLC (http://www.arduino.cc/) - */ - -package cc.arduino.packages.discoverers.network; - -import javax.jmdns.NetworkTopologyDiscovery; -import java.net.InetAddress; -import java.net.NetworkInterface; -import java.net.SocketException; -import java.util.*; - -public class NetworkChecker extends TimerTask { - - private final NetworkTopologyListener topologyListener; - private final NetworkTopologyDiscovery topology; - - private Set knownAddresses; - - public NetworkChecker(NetworkTopologyListener topologyListener, NetworkTopologyDiscovery topology) { - super(); - this.topologyListener = topologyListener; - this.topology = topology; - this.knownAddresses = Collections.synchronizedSet(new HashSet<>()); - } - - public void start(Timer timer) { - timer.schedule(this, 0, 3000); - } - - @Override - public void run() { - if (!hasNetworkInterfaces()) { - return; - } - try { - InetAddress[] curentAddresses = topology.getInetAddresses(); - Set current = new HashSet<>(curentAddresses.length); - for (InetAddress address : curentAddresses) { - current.add(address); - if (!knownAddresses.contains(address)) { - topologyListener.inetAddressAdded(address); - } - } - knownAddresses.stream().filter(address -> !current.contains(address)).forEach(topologyListener::inetAddressRemoved); - knownAddresses = current; - } catch (Exception e) { - e.printStackTrace(); - } - } - - private boolean hasNetworkInterfaces() { - try { - return NetworkInterface.getNetworkInterfaces() != null; - } catch (SocketException e) { - return false; - } - } -} diff --git a/arduino-core/src/cc/arduino/packages/discoverers/network/NetworkTopologyListener.java b/arduino-core/src/cc/arduino/packages/discoverers/network/NetworkTopologyListener.java deleted file mode 100644 index f963baa6c5b..00000000000 --- a/arduino-core/src/cc/arduino/packages/discoverers/network/NetworkTopologyListener.java +++ /dev/null @@ -1,40 +0,0 @@ -/* - * This file is part of Arduino. - * - * Arduino is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA - * - * As a special exception, you may use this file as part of a free software - * library without restriction. Specifically, if other files instantiate - * templates or use macros or inline functions from this file, or you compile - * this file and link it with other files to produce an executable, this - * file does not by itself cause the resulting executable to be covered by - * the GNU General Public License. This exception does not however - * invalidate any other reasons why the executable file might be covered by - * the GNU General Public License. - * - * Copyright 2013 Arduino LLC (http://www.arduino.cc/) - */ - -package cc.arduino.packages.discoverers.network; - -import java.net.InetAddress; - -public interface NetworkTopologyListener { - - void inetAddressAdded(InetAddress address); - - void inetAddressRemoved(InetAddress address); - -} diff --git a/arduino-core/src/cc/arduino/packages/discoverers/serial/SerialBoardsLister.java b/arduino-core/src/cc/arduino/packages/discoverers/serial/SerialBoardsLister.java index 5ca99819b89..5bd10773ece 100644 --- a/arduino-core/src/cc/arduino/packages/discoverers/serial/SerialBoardsLister.java +++ b/arduino-core/src/cc/arduino/packages/discoverers/serial/SerialBoardsLister.java @@ -31,62 +31,120 @@ import cc.arduino.packages.BoardPort; import cc.arduino.packages.discoverers.SerialDiscovery; +import cc.arduino.packages.uploaders.SerialUploader; import processing.app.BaseNoGui; import processing.app.Platform; -import processing.app.Serial; import processing.app.debug.TargetBoard; import java.util.*; +import static processing.app.helpers.OSUtils.isWindows; + public class SerialBoardsLister extends TimerTask { private final SerialDiscovery serialDiscovery; + private final List boardPorts = new LinkedList<>(); + private List oldPorts = new LinkedList<>(); + public boolean uploadInProgress = false; + public boolean pausePolling = false; + private BoardPort oldUploadBoardPort = null; public SerialBoardsLister(SerialDiscovery serialDiscovery) { this.serialDiscovery = serialDiscovery; } public void start(Timer timer) { - timer.schedule(this, 0, 1000); - } - @Override - public void run() { - while (BaseNoGui.packages == null) { - try { - Thread.sleep(1000); - } catch (InterruptedException e) { - // noop - } + if (isWindows()) { + timer.schedule(this, 0, 3000); + } else { + timer.schedule(this, 0, 1000); } + } + public synchronized void retriggerDiscovery(boolean polled) { Platform platform = BaseNoGui.getPlatform(); if (platform == null) { return; } - List boardPorts = new LinkedList<>(); + if (polled && pausePolling) { + return; + } + + List ports = platform.listSerials(); + if (ports.equals(oldPorts)) { + return; + } + + // if (updating) {} + // a port will disappear, another will appear + // use this information to "merge" the boards + // updating must be signaled by SerialUpload class - List ports = Serial.list(); + oldPorts.clear(); + oldPorts.addAll(ports); - String devicesListOutput = null; - if (!ports.isEmpty()) { - devicesListOutput = platform.preListAllCandidateDevices(); + for (BoardPort board : boardPorts) { + if (ports.contains(board.toString())) { + if (board.isOnline()) { + ports.remove(ports.indexOf(board.toString())); + } + } else { + if (uploadInProgress && board.isOnline()) { + oldUploadBoardPort = board; + } + board.setOnlineStatus(false); + } } - for (String port : ports) { - Map boardData = platform.resolveDeviceByVendorIdProductId(port, BaseNoGui.packages, devicesListOutput); + for (String newPort : ports) { + + String[] parts = newPort.split("_"); + String port = parts[0]; - BoardPort boardPort = new BoardPort(); + if (parts.length != 3) { + // something went horribly wrong + continue; + } + + Map boardData = platform.resolveDeviceByVendorIdProductId(port, BaseNoGui.packages); + + BoardPort boardPort = null; + boolean updatingInfos = false; + int i = 0; + // create new board or update existing + for (BoardPort board : boardPorts) { + if (board.toString().equals(newPort)) { + updatingInfos = true; + boardPort = boardPorts.get(i); + break; + } + i++; + } + if (!updatingInfos) { + boardPort = new BoardPort(); + } boardPort.setAddress(port); boardPort.setProtocol("serial"); + boardPort.setOnlineStatus(true); String label = port; if (boardData != null) { boardPort.getPrefs().put("vid", boardData.get("vid").toString()); boardPort.getPrefs().put("pid", boardData.get("pid").toString()); - boardPort.getPrefs().put("iserial", boardData.get("iserial").toString()); + boardPort.setVIDPID(parts[1], parts[2]); + + String iserial = boardData.get("iserial").toString(); + if (iserial.length() >= 10) { + boardPort.getPrefs().put("iserial", iserial); + boardPort.setISerial(iserial); + } + if (uploadInProgress && oldUploadBoardPort!=null) { + oldUploadBoardPort.getPrefs().put("iserial", iserial); + oldUploadBoardPort.setISerial(iserial); + } TargetBoard board = (TargetBoard) boardData.get("board"); if (board != null) { @@ -96,13 +154,28 @@ public void run() { } boardPort.setBoardName(boardName); } + } else { + if (parts[1] != "0000") { + boardPort.setVIDPID(parts[1], parts[2]); + } else { + boardPort.setVIDPID("0000", "0000"); + boardPort.setISerial(""); + } } boardPort.setLabel(label); - - boardPorts.add(boardPort); + if (!updatingInfos) { + boardPorts.add(boardPort); + } } - serialDiscovery.setSerialBoardPorts(boardPorts); } + + @Override + public void run() { + if (BaseNoGui.packages == null) { + return; + } + retriggerDiscovery(true); + } } diff --git a/arduino-core/src/cc/arduino/packages/uploaders/GenericNetworkUploader.java b/arduino-core/src/cc/arduino/packages/uploaders/GenericNetworkUploader.java index 1bc5befe4b4..5e8c58934f3 100644 --- a/arduino-core/src/cc/arduino/packages/uploaders/GenericNetworkUploader.java +++ b/arduino-core/src/cc/arduino/packages/uploaders/GenericNetworkUploader.java @@ -93,7 +93,8 @@ public boolean uploadUsingPreferences(File sourcePath, String buildPath, String pattern = prefs.get("upload.network_pattern"); if(pattern == null) pattern = prefs.getOrExcept("upload.pattern"); - String[] cmd = StringReplacer.formatAndSplit(pattern, prefs, true); + String arch = targetPlatform.getContainerPackage().getId()+"."+targetPlatform.getId(); + String[] cmd = StringReplacer.formatAndSplit(pattern, prefs, arch, true); uploadResult = executeUploadCommand(cmd); } catch (RunnerException e) { throw e; diff --git a/arduino-core/src/cc/arduino/packages/uploaders/SSHUploader.java b/arduino-core/src/cc/arduino/packages/uploaders/SSHUploader.java index b0ba1ec4ee9..cc0a1080827 100644 --- a/arduino-core/src/cc/arduino/packages/uploaders/SSHUploader.java +++ b/arduino-core/src/cc/arduino/packages/uploaders/SSHUploader.java @@ -135,7 +135,7 @@ public boolean uploadUsingPreferences(File sourcePath, String buildPath, String return runUploadTool(ssh, prefs); } catch (JSchException e) { String message = e.getMessage(); - if ("Auth cancel".equals(message) || "Auth fail".equals(message)) { + if (message.contains("Auth cancel") || message.contains("Auth fail") || message.contains("authentication fail")) { return false; } if (e.getMessage().contains("Connection refused")) { diff --git a/arduino-core/src/cc/arduino/packages/uploaders/SerialUploader.java b/arduino-core/src/cc/arduino/packages/uploaders/SerialUploader.java index 971bfb8c6f9..be8c784ef6d 100644 --- a/arduino-core/src/cc/arduino/packages/uploaders/SerialUploader.java +++ b/arduino-core/src/cc/arduino/packages/uploaders/SerialUploader.java @@ -44,6 +44,8 @@ import processing.app.helpers.PreferencesMap; import processing.app.helpers.StringReplacer; +import cc.arduino.packages.discoverers.SerialDiscovery; + import java.io.File; import java.util.ArrayList; import java.util.List; @@ -78,12 +80,19 @@ public boolean uploadUsingPreferences(File sourcePath, String buildPath, String } prefs.putAll(targetPlatform.getTool(tool)); + if (programmerPid != null && programmerPid.isAlive()) { + // kill the previous programmer + programmerPid.destroyForcibly(); + } + // if no protocol is specified for this board, assume it lacks a // bootloader and upload using the selected programmer. if (usingProgrammer || prefs.get("upload.protocol") == null) { return uploadUsingProgrammer(buildPath, className); } + BaseNoGui.getDiscoveryManager().getSerialDiscoverer().pausePolling(true); + if (noUploadPort) { prefs.put("build.path", buildPath); @@ -96,10 +105,13 @@ public boolean uploadUsingPreferences(File sourcePath, String buildPath, String boolean uploadResult; try { String pattern = prefs.getOrExcept("upload.pattern"); - String[] cmd = StringReplacer.formatAndSplit(pattern, prefs, true); + String arch = targetPlatform.getContainerPackage().getId()+"."+targetPlatform.getId(); + String[] cmd = StringReplacer.formatAndSplit(pattern, prefs, arch, true); uploadResult = executeUploadCommand(cmd); } catch (Exception e) { throw new RunnerException(e); + } finally { + BaseNoGui.getDiscoveryManager().getSerialDiscoverer().pausePolling(false); } return uploadResult; } @@ -134,13 +146,20 @@ public boolean uploadUsingPreferences(File sourcePath, String buildPath, String // Scanning for available ports seems to open the port or // otherwise assert DTR, which would cancel the WDT reset if // it happened within 250 ms. So we wait until the reset should - // have already occured before we start scanning. + // have already occurred before we start scanning. actualUploadPort = waitForUploadPort(userSelectedUploadPort, before); + + // on OS X, if the port is opened too quickly after it is detected, + // a "Resource busy" error occurs, add a delay to workaround this, + // apply to other platforms as well. + Thread.sleep(250); } } catch (SerialException e) { throw new RunnerException(e); } catch (InterruptedException e) { throw new RunnerException(e.getMessage()); + } finally { + BaseNoGui.getDiscoveryManager().getSerialDiscoverer().pausePolling(false); } if (actualUploadPort == null) { actualUploadPort = userSelectedUploadPort; @@ -151,13 +170,12 @@ public boolean uploadUsingPreferences(File sourcePath, String buildPath, String } else { prefs.put("serial.port.file", actualUploadPort); } - } - BoardPort boardPort = BaseNoGui.getDiscoveryManager().find(PreferencesData.get("serial.port")); - try { - prefs.put("serial.port.iserial", boardPort.getPrefs().getOrExcept("iserial")); - } catch (Exception e) { - // if serial port does not contain an iserial field + // retrigger a discovery + BaseNoGui.getDiscoveryManager().getSerialDiscoverer().setUploadInProgress(true); + Thread.sleep(100); + BaseNoGui.getDiscoveryManager().getSerialDiscoverer().forceRefresh(); + Thread.sleep(100); } prefs.put("build.path", buildPath); @@ -170,15 +188,22 @@ public boolean uploadUsingPreferences(File sourcePath, String buildPath, String boolean uploadResult; try { + // Architecture field formatted as "arduino.avr" to prepend runtime vars String pattern = prefs.getOrExcept("upload.pattern"); - String[] cmd = StringReplacer.formatAndSplit(pattern, prefs, true); + String arch = targetPlatform.getContainerPackage().getId()+"."+targetPlatform.getId(); + String[] cmd = StringReplacer.formatAndSplit(pattern, prefs, arch, true); uploadResult = executeUploadCommand(cmd); } catch (RunnerException e) { throw e; } catch (Exception e) { throw new RunnerException(e); + } finally { + BaseNoGui.getDiscoveryManager().getSerialDiscoverer().pausePolling(false); } + BaseNoGui.getDiscoveryManager().getSerialDiscoverer().setUploadInProgress(false); + BaseNoGui.getDiscoveryManager().getSerialDiscoverer().pausePolling(false); + String finalUploadPort = null; if (uploadResult && doTouch) { try { @@ -191,15 +216,13 @@ public boolean uploadUsingPreferences(File sourcePath, String buildPath, String long started = System.currentTimeMillis(); while (System.currentTimeMillis() - started < 2000) { List portList = Serial.list(); - if (portList.contains(actualUploadPort)) { - finalUploadPort = actualUploadPort; - break; - } else if (portList.contains(userSelectedUploadPort)) { + if (portList.contains(userSelectedUploadPort)) { finalUploadPort = userSelectedUploadPort; break; } Thread.sleep(250); } + finalUploadPort = actualUploadPort; } } catch (InterruptedException ex) { // noop @@ -213,6 +236,7 @@ public boolean uploadUsingPreferences(File sourcePath, String buildPath, String finalUploadPort = userSelectedUploadPort; } BaseNoGui.selectSerialPort(finalUploadPort); + return uploadResult; } @@ -247,11 +271,10 @@ private String waitForUploadPort(String uploadPort, List before) throws Thread.sleep(250); elapsed += 250; - // On Windows, it can take a long time for the port to disappear and - // come back, so use a longer time out before assuming that the - // selected - // port is the bootloader (not the sketch). - if (((!OSUtils.isWindows() && elapsed >= 500) || elapsed >= 5000) && now.contains(uploadPort)) { + // On Windows and OS X, it can take a few seconds for the port to disappear and + // come back, so use a time out before assuming that the selected port is the + // bootloader (not the sketch). + if (elapsed >= 5000 && now.contains(uploadPort)) { if (verbose) System.out.println("Uploading using selected port: " + uploadPort); return uploadPort; @@ -300,7 +323,8 @@ private boolean uploadUsingProgrammer(String buildPath, String className) throws // } String pattern = prefs.getOrExcept("program.pattern"); - String[] cmd = StringReplacer.formatAndSplit(pattern, prefs, true); + String arch = targetPlatform.getContainerPackage().getId()+"."+targetPlatform.getId(); + String[] cmd = StringReplacer.formatAndSplit(pattern, prefs, arch, true); return executeUploadCommand(cmd); } catch (RunnerException e) { throw e; @@ -363,12 +387,13 @@ public boolean burnBootloader() throws Exception { new LoadVIDPIDSpecificPreferences().load(prefs); String pattern = prefs.getOrExcept("erase.pattern"); - String[] cmd = StringReplacer.formatAndSplit(pattern, prefs, true); + String arch = targetPlatform.getContainerPackage().getId()+"."+targetPlatform.getId(); + String[] cmd = StringReplacer.formatAndSplit(pattern, prefs, arch, true); if (!executeUploadCommand(cmd)) return false; pattern = prefs.getOrExcept("bootloader.pattern"); - cmd = StringReplacer.formatAndSplit(pattern, prefs, true); + cmd = StringReplacer.formatAndSplit(pattern, prefs, arch, true); return executeUploadCommand(cmd); } } diff --git a/arduino-core/src/processing/app/BaseNoGui.java b/arduino-core/src/processing/app/BaseNoGui.java index a2eab2559d6..eb5e107ba21 100644 --- a/arduino-core/src/processing/app/BaseNoGui.java +++ b/arduino-core/src/processing/app/BaseNoGui.java @@ -33,6 +33,8 @@ import java.util.logging.Level; import java.util.logging.Logger; +import cc.arduino.packages.BoardPort; + import static processing.app.I18n.tr; import static processing.app.helpers.filefilters.OnlyDirs.ONLY_DIRS; @@ -857,6 +859,10 @@ public static void createToolPreferences(Collection installedTo } PreferencesData.set(prefix + tool.getName() + ".path", absolutePath); PreferencesData.set(prefix + tool.getName() + "-" + tool.getVersion() + ".path", absolutePath); + for (String userArch : tool.ugetUserArchitectures()) { + PreferencesData.set(prefix + tool.getName() + ".path." + userArch, absolutePath); + PreferencesData.set(prefix + tool.getName() + "-" + tool.getVersion() + ".path." + userArch, absolutePath); + } } } @@ -1119,6 +1125,10 @@ static public void selectBoard(TargetBoard targetBoard) { public static void selectSerialPort(String port) { PreferencesData.set("serial.port", port); + BoardPort boardPort = getDiscoveryManager().find(port, true); + if (boardPort != null) { + PreferencesData.set("serial.port.iserial", boardPort.getPrefs().get("iserial")); + } String portFile = port; if (port.startsWith("/dev/")) { portFile = portFile.substring(5); diff --git a/arduino-core/src/processing/app/Platform.java b/arduino-core/src/processing/app/Platform.java index 29e5e733d87..0c2c1d8cbbd 100644 --- a/arduino-core/src/processing/app/Platform.java +++ b/arduino-core/src/processing/app/Platform.java @@ -35,6 +35,8 @@ import java.util.LinkedList; import java.util.List; import java.util.Map; +import java.util.ArrayList; +import java.util.Arrays; import static processing.app.I18n.tr; @@ -160,13 +162,26 @@ private static void loadLib(File lib) { } } - public native String resolveDeviceAttachedToNative(String serial); + private native String resolveDeviceAttachedToNative(String serial); + private native String[] listSerialsNative(); public String preListAllCandidateDevices() { return null; } - public Map resolveDeviceByVendorIdProductId(String serial, Map packages, String devicesListOutput) { + public List listSerials(){ + return new ArrayList(Arrays.asList(this.listSerialsNative())); + } + + public List listSerialsNames(){ + List list = new LinkedList<>(); + for (String port : this.listSerialsNative()) { + list.add(port.split("_")[0]); + } + return list; + } + + public synchronized Map resolveDeviceByVendorIdProductId(String serial, Map packages) { String vid_pid_iSerial = resolveDeviceAttachedToNative(serial); for (TargetPackage targetPackage : packages.values()) { for (TargetPlatform targetPlatform : targetPackage.getPlatforms().values()) { diff --git a/arduino-core/src/processing/app/helpers/StringReplacer.java b/arduino-core/src/processing/app/helpers/StringReplacer.java index f51bfd7b696..18bb9154efb 100644 --- a/arduino-core/src/processing/app/helpers/StringReplacer.java +++ b/arduino-core/src/processing/app/helpers/StringReplacer.java @@ -29,12 +29,17 @@ public class StringReplacer { public static String[] formatAndSplit(String src, Map dict, boolean recursive) throws Exception { + return formatAndSplit(src, dict, "", recursive); + } + + public static String[] formatAndSplit(String src, Map dict, String arch, + boolean recursive) throws Exception { String res; // Recursive replace with a max depth of 10 levels. for (int i = 0; i < 10; i++) { // Do a replace with dictionary - res = StringReplacer.replaceFromMapping(src, dict); + res = StringReplacer.replaceFromMapping(src, dict, arch); if (!recursive) break; if (res.equals(src)) @@ -85,16 +90,32 @@ public static String[] quotedSplit(String src, String quoteChars, return res.toArray(new String[0]); } + public static String replaceFromMapping(String src, Map map, String arch) { + return replaceFromMapping(src, map, "{", "}", arch); + } + public static String replaceFromMapping(String src, Map map) { - return replaceFromMapping(src, map, "{", "}"); + return replaceFromMapping(src, map, "{", "}", ""); } public static String replaceFromMapping(String src, Map map, String leftDelimiter, - String rightDelimiter) { + String rightDelimiter, + String footer) { for (Map.Entry entry : map.entrySet()) { String keyword = leftDelimiter + entry.getKey() + rightDelimiter; - src = src.replace(keyword, entry.getValue()); + String value = null; + + // if {entry.getKey()+"."+footer} key exists, use it instead + if (map.containsKey(entry.getKey()+"."+footer)) { + value = map.get(entry.getKey()+"."+footer); + } else { + value = entry.getValue(); + } + + if (value != null && keyword != null) { + src = src.replace(keyword, entry.getValue()); + } } return src; } diff --git a/arduino-core/src/processing/app/packages/LibraryList.java b/arduino-core/src/processing/app/packages/LibraryList.java index d4d504cea79..5c9c096dbf5 100644 --- a/arduino-core/src/processing/app/packages/LibraryList.java +++ b/arduino-core/src/processing/app/packages/LibraryList.java @@ -50,29 +50,29 @@ public LibraryList(List ideLibs) { super(ideLibs); } - public UserLibrary getByName(String name) { + public synchronized UserLibrary getByName(String name) { for (UserLibrary l : this) if (l.getName().equals(name)) return l; return null; } - public void addOrReplace(UserLibrary lib) { + public synchronized void addOrReplace(UserLibrary lib) { remove(lib); add(lib); } - public void remove(UserLibrary lib) { + public synchronized void remove(UserLibrary lib) { UserLibrary l = getByName(lib.getName()); if (l != null) super.remove(l); } - public void sort() { + public synchronized void sort() { Collections.sort(this, UserLibrary.CASE_INSENSITIVE_ORDER); } - public LibraryList filterLibrariesInSubfolder(File subFolder) { + public synchronized LibraryList filterLibrariesInSubfolder(File subFolder) { LibraryList res = new LibraryList(); for (UserLibrary lib : this) { if (FileUtils.isSubDirectory(subFolder, lib.getInstalledFolder())) { @@ -82,7 +82,7 @@ public LibraryList filterLibrariesInSubfolder(File subFolder) { return res; } - public boolean hasLibrary(UserLibrary lib) { + public synchronized boolean hasLibrary(UserLibrary lib) { for (UserLibrary l : this) if (l == lib) return true; return false; diff --git a/arduino-core/src/processing/app/zeroconf/jmdns/ArduinoDNSTaskStarter.java b/arduino-core/src/processing/app/zeroconf/jmdns/ArduinoDNSTaskStarter.java deleted file mode 100644 index fe9c2571578..00000000000 --- a/arduino-core/src/processing/app/zeroconf/jmdns/ArduinoDNSTaskStarter.java +++ /dev/null @@ -1,81 +0,0 @@ -package processing.app.zeroconf.jmdns; - -import javax.jmdns.impl.DNSIncoming; -import javax.jmdns.impl.DNSTaskStarter; -import javax.jmdns.impl.JmDNSImpl; -import javax.jmdns.impl.ServiceInfoImpl; -import javax.jmdns.impl.tasks.RecordReaper; -import java.util.Timer; - -public class ArduinoDNSTaskStarter implements DNSTaskStarter.Factory.ClassDelegate { - - @Override - public DNSTaskStarter newDNSTaskStarter(final JmDNSImpl jmDNSImpl) { - final DNSTaskStarter.DNSTaskStarterImpl delegate = new DNSTaskStarter.DNSTaskStarterImpl(jmDNSImpl); - final DNSTaskStarter.DNSTaskStarterImpl.StarterTimer timer = new DNSTaskStarter.DNSTaskStarterImpl.StarterTimer("JmDNS(" + jmDNSImpl.getName() + ").Timer", true); - - return new DNSTaskStarter() { - - public void purgeTimer() { - delegate.purgeTimer(); - timer.purge(); - } - - public void purgeStateTimer() { - delegate.purgeStateTimer(); - } - - public void cancelTimer() { - delegate.cancelTimer(); - timer.cancel(); - } - - public void cancelStateTimer() { - delegate.cancelStateTimer(); - } - - public void startProber() { - delegate.startProber(); - } - - public void startAnnouncer() { - delegate.startAnnouncer(); - } - - public void startRenewer() { - delegate.startRenewer(); - } - - public void startCanceler() { - delegate.startCanceler(); - } - - public void startReaper() { - new RecordReaper(jmDNSImpl) { - @Override - public void start(Timer timer) { - if (!this.getDns().isCanceling() && !this.getDns().isCanceled()) { - timer.schedule(this, 0, 500); - } - } - }.start(timer); - } - - public void startServiceInfoResolver(ServiceInfoImpl info) { - delegate.startServiceInfoResolver(info); - } - - public void startTypeResolver() { - delegate.startTypeResolver(); - } - - public void startServiceResolver(String type) { - delegate.startServiceResolver(type); - } - - public void startResponder(DNSIncoming in, int port) { - delegate.startResponder(in, port); - } - }; - } -} diff --git a/build/build.xml b/build/build.xml index 43464da190c..6f83f086603 100644 --- a/build/build.xml +++ b/build/build.xml @@ -436,12 +436,12 @@ - - - + + + - + @@ -639,12 +639,12 @@ - - - + + + - + @@ -796,12 +796,9 @@ - - + - - - + @@ -908,12 +905,12 @@ - - - + + + - + diff --git a/build/liblistSerials-1.0.5.zip.sha b/build/liblistSerials-1.0.5.zip.sha deleted file mode 100644 index 6484b2fbc08..00000000000 --- a/build/liblistSerials-1.0.5.zip.sha +++ /dev/null @@ -1 +0,0 @@ -edb1c858a243e465f5797d7e5d0baa66daa1eba0 diff --git a/build/liblistSerials-1.0.7.zip.sha b/build/liblistSerials-1.0.7.zip.sha new file mode 100644 index 00000000000..8a43a7fe9c7 --- /dev/null +++ b/build/liblistSerials-1.0.7.zip.sha @@ -0,0 +1 @@ +6ddda4d86d49a4b496718b401b8cc91117bd2930 diff --git a/build/windows/launcher/config.xml b/build/windows/launcher/config.xml index 0bfadc6632a..496f6d56df2 100644 --- a/build/windows/launcher/config.xml +++ b/build/windows/launcher/config.xml @@ -41,7 +41,7 @@ %EXEDIR%/lib/jackson-databind-2.6.3.jar %EXEDIR%/lib/jackson-module-mrbean-2.6.3.jar %EXEDIR%/lib/java-semver-0.8.0.jar - %EXEDIR%/lib/jmdns-3.4.1.jar + %EXEDIR%/lib/jmdns-3.4.2.jar %EXEDIR%/lib/jna-4.1.0.jar %EXEDIR%/lib/jna-platform-4.1.0.jar %EXEDIR%/lib/jsch-0.1.50.jar diff --git a/build/windows/launcher/config_debug.xml b/build/windows/launcher/config_debug.xml index 708ef610fdf..8c9da11eb2d 100644 --- a/build/windows/launcher/config_debug.xml +++ b/build/windows/launcher/config_debug.xml @@ -41,7 +41,7 @@ %EXEDIR%/lib/jackson-databind-2.6.3.jar %EXEDIR%/lib/jackson-module-mrbean-2.6.3.jar %EXEDIR%/lib/java-semver-0.8.0.jar - %EXEDIR%/lib/jmdns-3.4.1.jar + %EXEDIR%/lib/jmdns-3.4.2.jar %EXEDIR%/lib/jna-4.1.0.jar %EXEDIR%/lib/jna-platform-4.1.0.jar %EXEDIR%/lib/jsch-0.1.50.jar diff --git a/hardware/arduino/avr/cores/arduino/CDC.cpp b/hardware/arduino/avr/cores/arduino/CDC.cpp index f19b44c7f1e..f6750665105 100644 --- a/hardware/arduino/avr/cores/arduino/CDC.cpp +++ b/hardware/arduino/avr/cores/arduino/CDC.cpp @@ -34,6 +34,8 @@ typedef struct static volatile LineInfo _usbLineInfo = { 57600, 0x00, 0x00, 0x00, 0x00 }; static volatile int32_t breakValue = -1; +bool _updatedLUFAbootloader = false; + #define WEAK __attribute__ ((weak)) extern const CDCDescriptor _cdcInterface PROGMEM; @@ -99,24 +101,32 @@ bool CDC_Setup(USBSetup& setup) // with a relatively long period so it can finish housekeeping tasks // like servicing endpoints before the sketch ends -#ifndef MAGIC_KEY -#define MAGIC_KEY 0x7777 -#endif -#ifndef MAGIC_KEY_POS -#define MAGIC_KEY_POS 0x0800 + uint16_t magic_key_pos = MAGIC_KEY_POS; + +// If we don't use the new RAMEND directly, check manually if we have a newer bootloader. +// This is used to keep compatible with the old leonardo bootloaders. +// You are still able to set the magic key position manually to RAMEND-1 to save a few bytes for this check. +#if MAGIC_KEY_POS != (RAMEND-1) + // For future boards save the key in the inproblematic RAMEND + // Which is reserved for the main() return value (which will never return) + if (_updatedLUFAbootloader) { + // horray, we got a new bootloader! + magic_key_pos = (RAMEND-1); + } #endif // We check DTR state to determine if host port is open (bit 0 of lineState). if (1200 == _usbLineInfo.dwDTERate && (_usbLineInfo.lineState & 0x01) == 0) { #if MAGIC_KEY_POS != (RAMEND-1) - *(uint16_t *)(RAMEND-1) = *(uint16_t *)MAGIC_KEY_POS; - *(uint16_t *)MAGIC_KEY_POS = MAGIC_KEY; -#else - // for future boards save the key in the inproblematic RAMEND - // which is reserved for the main() return value (which will never return) - *(uint16_t *)MAGIC_KEY_POS = MAGIC_KEY; + // Backup ram value if its not a newer bootloader. + // This should avoid memory corruption at least a bit, not fully + if (magic_key_pos != (RAMEND-1)) { + *(uint16_t *)(RAMEND-1) = *(uint16_t *)magic_key_pos; + } #endif + // Store boot key + *(uint16_t *)magic_key_pos = MAGIC_KEY; wdt_enable(WDTO_120MS); } else @@ -129,10 +139,14 @@ bool CDC_Setup(USBSetup& setup) wdt_disable(); wdt_reset(); #if MAGIC_KEY_POS != (RAMEND-1) - *(uint16_t *)MAGIC_KEY_POS = *(uint16_t *)(RAMEND-1); -#else - *(uint16_t *)MAGIC_KEY_POS = 0x0000; + // Restore backed up (old bootloader) magic key data + if (magic_key_pos != (RAMEND-1)) { + *(uint16_t *)magic_key_pos = *(uint16_t *)(RAMEND-1); + } else { #endif + // Clean up RAMEND key + *(uint16_t *)magic_key_pos = 0x0000; + } } } return true; diff --git a/hardware/arduino/avr/cores/arduino/USBCore.cpp b/hardware/arduino/avr/cores/arduino/USBCore.cpp index 62b90ed47d6..248da62732e 100644 --- a/hardware/arduino/avr/cores/arduino/USBCore.cpp +++ b/hardware/arduino/avr/cores/arduino/USBCore.cpp @@ -35,6 +35,7 @@ extern const u8 STRING_PRODUCT[] PROGMEM; extern const u8 STRING_MANUFACTURER[] PROGMEM; extern const DeviceDescriptor USB_DeviceDescriptor PROGMEM; extern const DeviceDescriptor USB_DeviceDescriptorB PROGMEM; +extern bool _updatedLUFAbootloader; const u16 STRING_LANGUAGE[2] = { (3<<8) | (2+2), @@ -115,16 +116,10 @@ static inline void Recv(volatile u8* data, u8 count) { while (count--) *data++ = UEDATX; - - RXLED1; // light the RX LED - RxLEDPulse = TX_RX_LED_PULSE_MS; } static inline u8 Recv8() { - RXLED1; // light the RX LED - RxLEDPulse = TX_RX_LED_PULSE_MS; - return UEDATX; } @@ -226,6 +221,9 @@ int USB_Recv(u8 ep, void* d, int len) if (!_usbConfiguration || len < 0) return -1; + RXLED1; // light the RX LED + RxLEDPulse = TX_RX_LED_PULSE_MS; + LockEP lock(ep); u8 n = FifoByteCount(); len = min(n,len); @@ -806,6 +804,12 @@ void USBDevice_::attach() UDIEN = (1<