diff --git a/app/src/processing/app/Base.java b/app/src/processing/app/Base.java index e7f76d6da..62ebfc2ac 100644 --- a/app/src/processing/app/Base.java +++ b/app/src/processing/app/Base.java @@ -267,6 +267,8 @@ public class Base { splash.splashText(tr("Initializing packages...")); BaseNoGui.initPackages(); + parser.getUploadPort().ifPresent(BaseNoGui::selectSerialPort); + splash.splashText(tr("Preparing boards...")); if (!isCommandLine()) { diff --git a/app/src/processing/app/Editor.java b/app/src/processing/app/Editor.java index 93d791a35..dc0a5b7cb 100644 --- a/app/src/processing/app/Editor.java +++ b/app/src/processing/app/Editor.java @@ -52,7 +52,6 @@ import java.net.URLClassLoader; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; -import java.util.Comparator; import java.util.Enumeration; import java.util.HashMap; import java.util.LinkedList; @@ -96,6 +95,7 @@ import cc.arduino.view.StubMenuListener; import cc.arduino.view.findreplace.FindReplace; import jssc.SerialPortException; import processing.app.debug.RunnerException; +import processing.app.debug.TargetBoard; import processing.app.forms.PasswordAuthorizationDialog; import processing.app.helpers.DocumentTextChangeListener; import processing.app.helpers.Keys; @@ -149,9 +149,6 @@ public class Editor extends JFrame implements RunnerListener { } } - private final static List BOARD_PROTOCOLS_ORDER = Arrays.asList("serial", "network"); - private final static List BOARD_PROTOCOLS_ORDER_TRANSLATIONS = Arrays.asList(tr("Serial ports"), tr("Network ports")); - final Base base; // otherwise, if the window is resized with the message label @@ -1043,22 +1040,30 @@ public class Editor extends JFrame implements RunnerListener { private BoardPort port; public BoardPortJCheckBoxMenuItem(BoardPort port) { - super(port.getLabel()); + super(); + this.port = port; + setText(toString()); addActionListener(e -> { selectSerialPort(port.getAddress()); base.onBoardOrPortChange(); }); - this.port = port; } @Override public String toString() { // This is required for serialPrompt() - return port.getLabel(); + String label = port.getLabel(); + if (port.getBoardName() != null && !port.getBoardName().isEmpty()) { + label += " (" + port.getBoardName() + ")"; + } + return label; } } private void populatePortMenu() { + final List PROTOCOLS_ORDER = Arrays.asList("serial", "network"); + final List PROTOCOLS_LABELS = Arrays.asList(tr("Serial ports"), tr("Network ports")); + portMenu.removeAll(); String selectedPort = PreferencesData.get("serial.port"); @@ -1067,31 +1072,43 @@ public class Editor extends JFrame implements RunnerListener { ports = platform.filterPorts(ports, PreferencesData.getBoolean("serial.ports.showall")); - Collections.sort(ports, new Comparator() { - @Override - public int compare(BoardPort o1, BoardPort o2) { - return (BOARD_PROTOCOLS_ORDER.indexOf(o1.getProtocol()) - BOARD_PROTOCOLS_ORDER.indexOf(o2.getProtocol())) * 10 + - o1.getAddress().compareTo(o2.getAddress()); - } + ports.stream() // + .filter(port -> port.getProtocolLabel() == null || port.getProtocolLabel().isEmpty()) + .forEach(port -> { + int labelIdx = PROTOCOLS_ORDER.indexOf(port.getProtocol()); + if (labelIdx != -1) { + port.setProtocolLabel(PROTOCOLS_LABELS.get(labelIdx)); + } else { + port.setProtocolLabel(port.getProtocol()); + } + }); + + Collections.sort(ports, (port1, port2) -> { + String pr1 = port1.getProtocol(); + String pr2 = port2.getProtocol(); + int prIdx1 = PROTOCOLS_ORDER.contains(pr1) ? PROTOCOLS_ORDER.indexOf(pr1) : 999; + int prIdx2 = PROTOCOLS_ORDER.contains(pr2) ? PROTOCOLS_ORDER.indexOf(pr2) : 999; + int r = prIdx1 - prIdx2; + if (r != 0) + return r; + r = port1.getProtocolLabel().compareTo(port2.getProtocolLabel()); + if (r != 0) + return r; + return port1.getAddress().compareTo(port2.getAddress()); }); - String lastProtocol = null; - String lastProtocolTranslated; + String lastProtocol = ""; + String lastProtocolLabel = ""; for (BoardPort port : ports) { - if (lastProtocol == null || !port.getProtocol().equals(lastProtocol)) { - if (lastProtocol != null) { + if (!port.getProtocol().equals(lastProtocol) || !port.getProtocolLabel().equals(lastProtocolLabel)) { + if (!lastProtocol.isEmpty()) { portMenu.addSeparator(); } lastProtocol = port.getProtocol(); - - if (BOARD_PROTOCOLS_ORDER.indexOf(port.getProtocol()) != -1) { - lastProtocolTranslated = BOARD_PROTOCOLS_ORDER_TRANSLATIONS.get(BOARD_PROTOCOLS_ORDER.indexOf(port.getProtocol())); - } else { - lastProtocolTranslated = port.getProtocol(); - } - JMenuItem lastProtocolMenuItem = new JMenuItem(tr(lastProtocolTranslated)); - lastProtocolMenuItem.setEnabled(false); - portMenu.add(lastProtocolMenuItem); + lastProtocolLabel = port.getProtocolLabel(); + JMenuItem item = new JMenuItem(tr(lastProtocolLabel)); + item.setEnabled(false); + portMenu.add(item); } String address = port.getAddress(); @@ -2403,9 +2420,9 @@ public class Editor extends JFrame implements RunnerListener { for (BoardPort port : ports) { if (port.getAddress().equals(selectedPort)) { label = port.getBoardName(); - vid = port.getVID(); - pid = port.getPID(); - iserial = port.getISerial(); + vid = port.getPrefs().get("vid"); + pid = port.getPrefs().get("pid"); + iserial = port.getPrefs().get("iserial"); protocol = port.getProtocol(); found = true; break; @@ -2575,12 +2592,12 @@ public class Editor extends JFrame implements RunnerListener { // . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . protected void onBoardOrPortChange() { - Map boardPreferences = BaseNoGui.getBoardPreferences(); - if (boardPreferences != null) - lineStatus.setBoardName(boardPreferences.get("name")); + TargetBoard board = BaseNoGui.getTargetBoard(); + if (board != null) + lineStatus.setBoardName(board.getName()); else lineStatus.setBoardName("-"); - lineStatus.setSerialPort(PreferencesData.get("serial.port")); + lineStatus.setPort(PreferencesData.get("serial.port")); lineStatus.repaint(); } diff --git a/app/src/processing/app/EditorLineStatus.java b/app/src/processing/app/EditorLineStatus.java index f71dd4573..7635437da 100644 --- a/app/src/processing/app/EditorLineStatus.java +++ b/app/src/processing/app/EditorLineStatus.java @@ -51,8 +51,7 @@ public class EditorLineStatus extends JComponent { String text = ""; String name = ""; - String serialport = ""; - String serialnumber = ""; + String port = ""; public EditorLineStatus() { background = Theme.getColor("linestatus.bgcolor"); @@ -92,13 +91,13 @@ public class EditorLineStatus extends JComponent { public void paintComponent(Graphics graphics) { Graphics2D g = Theme.setupGraphics2D(graphics); - if (name.isEmpty() && serialport.isEmpty()) { + if (name.isEmpty() && port.isEmpty()) { PreferencesMap boardPreferences = BaseNoGui.getBoardPreferences(); if (boardPreferences != null) setBoardName(boardPreferences.get("name")); else setBoardName("-"); - setSerialPort(PreferencesData.get("serial.port")); + setPort(PreferencesData.get("serial.port")); } g.setColor(background); Dimension size = getSize(); @@ -112,8 +111,8 @@ public class EditorLineStatus extends JComponent { g.setColor(messageForeground); String statusText; - if (serialport != null && !serialport.isEmpty()) { - statusText = I18n.format(tr("{0} on {1}"), name, serialport); + if (port != null && !port.isEmpty()) { + statusText = I18n.format(tr("{0} on {1}"), name, port); } else { statusText = name; } @@ -132,12 +131,8 @@ public class EditorLineStatus extends JComponent { this.name = name; } - public void setSerialPort(String serialport) { - this.serialport = serialport; - } - - public void setSerialNumber(String serialnumber) { - this.serialnumber = serialnumber; + public void setPort(String port) { + this.port = port; } public Dimension getPreferredSize() { diff --git a/arduino-core/src/cc/arduino/packages/BoardPort.java b/arduino-core/src/cc/arduino/packages/BoardPort.java index 0e85ffe13..2052339f1 100644 --- a/arduino-core/src/cc/arduino/packages/BoardPort.java +++ b/arduino-core/src/cc/arduino/packages/BoardPort.java @@ -29,22 +29,36 @@ package cc.arduino.packages; +import processing.app.BaseNoGui; +import processing.app.debug.TargetBoard; +import processing.app.debug.TargetPackage; +import processing.app.debug.TargetPlatform; import processing.app.helpers.PreferencesMap; public class BoardPort { - private String address; - private String protocol; + private String address; // unique name for this port, used by Preferences + private String protocol; // how to communicate, used for Ports menu sections + private String protocolLabel; // protocol extended name to display on GUI private String boardName; - private String vid; - private String pid; - private String iserial; - private String label; - private final PreferencesMap prefs; - private boolean online; + private String label; // friendly name shown in Ports menu + private final PreferencesMap identificationPrefs; // data to match with boards.txt + private final PreferencesMap prefs; // "vendorId", "productId", "serialNumber" + private boolean online; // used by SerialBoardsLister (during upload??) public BoardPort() { this.prefs = new PreferencesMap(); + this.identificationPrefs = new PreferencesMap(); + } + + public BoardPort(BoardPort bp) { + prefs = new PreferencesMap(bp.prefs); + identificationPrefs = new PreferencesMap(bp.identificationPrefs); + address = bp.address; + protocol = bp.protocol; + boardName = bp.boardName; + label = bp.label; + online = bp.online; } public String getAddress() { @@ -63,6 +77,14 @@ public class BoardPort { this.protocol = protocol; } + public String getProtocolLabel() { + return protocolLabel; + } + + public void setProtocolLabel(String protocolLabel) { + this.protocolLabel = protocolLabel; + } + public String getBoardName() { return boardName; } @@ -75,6 +97,10 @@ public class BoardPort { return prefs; } + public PreferencesMap getIdentificationPrefs() { + return identificationPrefs; + } + public void setLabel(String label) { this.label = label; } @@ -91,28 +117,76 @@ public class BoardPort { 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; + return this.address; } + + // Search for the board which matches identificationPrefs. + // If found, boardName is set to the name from boards.txt + // and the board is returned. If not found, null is returned. + public TargetBoard searchMatchingBoard() { + if (identificationPrefs == null || identificationPrefs.isEmpty()) return null; + for (TargetPackage targetPackage : BaseNoGui.packages.values()) { + for (TargetPlatform targetPlatform : targetPackage.getPlatforms().values()) { + for (TargetBoard board : targetPlatform.getBoards().values()) { + if (matchesBoard(board)) { + setBoardName(board.getName()); + return board; + } + } + } + } + return null; + } + + public boolean matchesBoard(TargetBoard board) { + PreferencesMap identificationProps = getIdentificationPrefs(); + PreferencesMap boardProps = board.getPreferences(); + + String wildMatcher = identificationProps.get("."); + if (wildMatcher != null) { + if (wildMatcher.equals(board.getId())) { + return true; + } + if (wildMatcher.equals(board.getFQBN())) { + return true; + } + } + + // Identification properties are defined in boards.txt with a ".N" suffix + // for example: + // + // uno.name=Arduino/Genuino Uno + // uno.vid.0=0x2341 + // uno.pid.0=0x0043 + // uno.vid.1=0x2341 + // uno.pid.1=0x0001 + // uno.vid.2=0x2A03 + // uno.pid.2=0x0043 + // uno.vid.3=0x2341 + // uno.pid.3=0x0243 + // + // so we must search starting from suffix ".0" and increasing until we + // found a match or the board has no more identification properties defined + + for (int suffix = 0;; suffix++) { + boolean found = true; + for (String prop : identificationProps.keySet()) { + String value = identificationProps.get(prop); + prop += "." + suffix; + if (!boardProps.containsKey(prop)) { + return false; + } + if (!value.equalsIgnoreCase(boardProps.get(prop))) { + found = false; + break; + } + } + if (found) { + return true; + } + } + } + } diff --git a/arduino-core/src/cc/arduino/packages/DiscoveryManager.java b/arduino-core/src/cc/arduino/packages/DiscoveryManager.java index b1ec50d85..b344009db 100644 --- a/arduino-core/src/cc/arduino/packages/DiscoveryManager.java +++ b/arduino-core/src/cc/arduino/packages/DiscoveryManager.java @@ -29,13 +29,21 @@ package cc.arduino.packages; -import cc.arduino.packages.discoverers.NetworkDiscovery; -import cc.arduino.packages.discoverers.SerialDiscovery; +import static processing.app.I18n.format; +import static processing.app.I18n.tr; import java.util.ArrayList; import java.util.List; +import java.util.Map; -import static processing.app.I18n.tr; +import cc.arduino.packages.discoverers.PluggableDiscovery; +import cc.arduino.packages.discoverers.serial.SerialDiscovery; +import cc.arduino.packages.discoverers.NetworkDiscovery; +import processing.app.PreferencesData; +import processing.app.debug.TargetPackage; +import processing.app.debug.TargetPlatform; +import processing.app.helpers.PreferencesMap; +import processing.app.helpers.StringReplacer; public class DiscoveryManager { @@ -43,17 +51,47 @@ public class DiscoveryManager { private final SerialDiscovery serialDiscoverer = new SerialDiscovery(); private final NetworkDiscovery networkDiscoverer = new NetworkDiscovery(); - public DiscoveryManager() { +// private final Map packages; + + public DiscoveryManager(Map packages) { +// this.packages = packages; + discoverers = new ArrayList<>(); discoverers.add(serialDiscoverer); discoverers.add(networkDiscoverer); + // Search for discoveries in installed packages + for (TargetPackage targetPackage : packages.values()) { + for (TargetPlatform platform: targetPackage.getPlatforms().values()) { + //System.out.println("installed: "+platform); + PreferencesMap prefs = platform.getPreferences().subTree("discovery"); + for (String discoveryName : prefs.firstLevelMap().keySet()) { + PreferencesMap discoveryPrefs = prefs.subTree(discoveryName); + + String pattern = discoveryPrefs.get("pattern"); + if (pattern == null) { + System.out.println(format(tr("No recipes defined for discovery '{0}'"),discoveryName)); + continue; + } + try { + System.out.println("found discovery: " + discoveryName + " -> " + pattern); + System.out.println("with preferencess -> " + discoveryPrefs); + pattern = StringReplacer.replaceFromMapping(pattern, PreferencesData.getMap()); + String[] cmd = StringReplacer.formatAndSplit(pattern, discoveryPrefs); + discoverers.add(new PluggableDiscovery(discoveryName, cmd)); + } catch (Exception e) { + System.out.println(format(tr("Could not start discovery '{0}': {1}"), discoveryName, e.getMessage())); + } + } + } + } + // Start all discoverers for (Discovery d : discoverers) { try { new Thread(d).start(); } catch (Exception e) { - System.err.println(tr("Error starting discovery method: ") + d.getClass()); + System.err.println(tr("Error starting discovery method: ") + d.toString()); e.printStackTrace(); } } diff --git a/arduino-core/src/cc/arduino/packages/discoverers/NetworkDiscovery.java b/arduino-core/src/cc/arduino/packages/discoverers/NetworkDiscovery.java index 713956690..8619a92f0 100644 --- a/arduino-core/src/cc/arduino/packages/discoverers/NetworkDiscovery.java +++ b/arduino-core/src/cc/arduino/packages/discoverers/NetworkDiscovery.java @@ -113,15 +113,12 @@ public class NetworkDiscovery implements Discovery, ServiceListener, Runnable { String label = name + " at " + address; if (board != null && BaseNoGui.packages != null) { String boardName = BaseNoGui.getPlatform().resolveDeviceByBoardID(BaseNoGui.packages, board); - if (boardName != null) { - label += " (" + boardName + ")"; - } + port.setBoardName(boardName); } else if (description != null) { label += " (" + description + ")"; } port.setAddress(address); - port.setBoardName(name); port.setProtocol("network"); port.setLabel(label); @@ -165,7 +162,7 @@ public class NetworkDiscovery implements Discovery, ServiceListener, Runnable { @Override public List listDiscoveredBoards() { - synchronized (reachableBoardPorts) { + synchronized (reachableBoardPorts) { return getBoardPortsDiscoveredWithJmDNS(); } } @@ -179,8 +176,8 @@ public class NetworkDiscovery implements Discovery, ServiceListener, Runnable { public void setReachableBoardPorts(List newReachableBoardPorts) { synchronized (reachableBoardPorts) { - this.reachableBoardPorts.clear(); - this.reachableBoardPorts.addAll(newReachableBoardPorts); + reachableBoardPorts.clear(); + reachableBoardPorts.addAll(newReachableBoardPorts); } } diff --git a/arduino-core/src/cc/arduino/packages/discoverers/PluggableDiscovery.java b/arduino-core/src/cc/arduino/packages/discoverers/PluggableDiscovery.java new file mode 100644 index 000000000..d37166699 --- /dev/null +++ b/arduino-core/src/cc/arduino/packages/discoverers/PluggableDiscovery.java @@ -0,0 +1,292 @@ +/* + * 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 2018 Arduino SA (http://www.arduino.cc/) + */ + +package cc.arduino.packages.discoverers; + +import static processing.app.I18n.format; + +import java.io.InputStream; +import java.io.OutputStream; +import java.util.ArrayList; +import java.util.List; + +import com.fasterxml.jackson.annotation.JsonAutoDetect.Visibility; +import com.fasterxml.jackson.annotation.PropertyAccessor; +import com.fasterxml.jackson.core.JsonFactory; +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.DeserializationFeature; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; + +import cc.arduino.packages.BoardPort; +import cc.arduino.packages.Discovery; +import processing.app.PreferencesData; +import processing.app.helpers.StringUtils; + +public class PluggableDiscovery implements Discovery { + + private final String discoveryName; + private final String[] cmd; + private final List portList = new ArrayList<>(); + private Process program=null; + private Thread pollingThread; + + private void debug(String x) { + if (PreferencesData.getBoolean("discovery.debug")) + System.out.println(discoveryName + ": " + x); + } + + public PluggableDiscovery(String discoveryName, String[] cmd) { + this.cmd = cmd; + this.discoveryName = discoveryName; + } + + @Override + public void run() { + // this method is started as a new thread, it will constantly listen + // to the discovery tool and keep track of the discovered ports + try { + start(); + InputStream input = program.getInputStream(); + JsonFactory factory = new JsonFactory(); + JsonParser parser = factory.createParser(input); + ObjectMapper mapper = new ObjectMapper(); + mapper.setVisibility(PropertyAccessor.ALL, Visibility.NONE); + mapper.setVisibility(PropertyAccessor.FIELD, Visibility.ANY); + mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); + + while (program != null && program.isAlive()) { + JsonNode tree = mapper.readTree(parser); + if (tree == null) { + if (program != null && program.isAlive()) { + System.err.println(format("{0}: Invalid json message", discoveryName)); + } + break; + } + debug("Received json: " + tree); + + processJsonNode(mapper, tree); + } + debug("thread exit normally"); + } catch (InterruptedException e) { + debug("thread exit by interrupt"); + e.printStackTrace(); + } catch (Exception e) { + debug("thread exit other exception"); + e.printStackTrace(); + } + try { + stop(); + } catch (Exception e) { + } + } + + private void processJsonNode(ObjectMapper mapper, JsonNode node) { + JsonNode eventTypeNode = node.get("eventType"); + if (eventTypeNode == null) { + System.err.println(format("{0}: Invalid message, missing eventType", discoveryName)); + return; + } + + switch (eventTypeNode.asText()) { + case "error": + try { + PluggableDiscoveryMessage msg = mapper.treeToValue(node, PluggableDiscoveryMessage.class); + debug("error: " + msg.getMessage()); + if (msg.getMessage().contains("START_SYNC")) { + startPolling(); + } + } catch (JsonProcessingException e) { + e.printStackTrace(); + } + return; + + case "list": + JsonNode portsNode = node.get("ports"); + if (portsNode == null) { + System.err.println(format("{0}: Invalid message, missing ports list", discoveryName)); + return; + } + if (!portsNode.isArray()) { + System.err.println(format("{0}: Invalid message, ports list should be an array", discoveryName)); + return; + } + + synchronized (portList) { + portList.clear(); + } + portsNode.forEach(portNode -> { + BoardPort port = mapJsonNodeToBoardPort(mapper, node); + if (port != null) { + addOrUpdate(port); + } + }); + return; + + // Messages for SYNC updates + + case "add": + BoardPort addedPort = mapJsonNodeToBoardPort(mapper, node); + if (addedPort != null) { + addOrUpdate(addedPort); + } + return; + + case "remove": + BoardPort removedPort = mapJsonNodeToBoardPort(mapper, node); + if (removedPort != null) { + remove(removedPort); + } + return; + + default: + debug("Invalid event: " + eventTypeNode.asText()); + return; + } + } + + private BoardPort mapJsonNodeToBoardPort(ObjectMapper mapper, JsonNode node) { + try { + BoardPort port = mapper.treeToValue(node.get("port"), BoardPort.class); + // if no label, use address + if (port.getLabel() == null || port.getLabel().isEmpty()) { + port.setLabel(port.getAddress()); + } + port.searchMatchingBoard(); + return port; + } catch (JsonProcessingException e) { + System.err.println(format("{0}: Invalid BoardPort message", discoveryName)); + e.printStackTrace(); + return null; + } + } + + @Override + public void start() throws Exception { + try { + debug("Starting: " + StringUtils.join(cmd, " ")); + program = Runtime.getRuntime().exec(cmd); + } catch (Exception e) { + program = null; + return; + } + debug("START_SYNC"); + write("START_SYNC\n"); + pollingThread = null; + } + + private void startPolling() { + // Discovery tools not supporting START_SYNC require a periodic + // LIST command. A second thread is created to send these + // commands, while the run() thread above listens for the + // discovery tool output. + debug("START"); + write("START\n"); + Thread pollingThread = new Thread() { + public void run() { + try { + while (program != null && program.isAlive()) { + debug("LIST"); + write("LIST\n"); + sleep(2500); + } + } catch (Exception e) { + } + } + }; + pollingThread.start(); + } + + @Override + public void stop() throws Exception { + if (pollingThread != null) { + pollingThread.interrupt(); + pollingThread = null; + } + write("STOP\n"); + if (program != null) { + program.destroy(); + program = null; + } + } + + private void write(String command) { + if (program != null && program.isAlive()) { + OutputStream out = program.getOutputStream(); + try { + out.write(command.getBytes()); + out.flush(); + } catch (Exception e) { + } + } + } + + private void addOrUpdate(BoardPort port) { + String address = port.getAddress(); + if (address == null) + return; // address required for "add" & "remove" + + synchronized (portList) { + // if address already on the list, discard old info + portList.removeIf(bp -> address.equals(bp.getAddress())); + portList.add(port); + } + } + + private void remove(BoardPort port) { + String address = port.getAddress(); + if (address == null) + return; // address required for "add" & "remove" + synchronized (portList) { + portList.removeIf(bp -> address.equals(bp.getAddress())); + } + } + + @Override + public List listDiscoveredBoards() { + synchronized (portList) { + return new ArrayList<>(portList); + } + } + + @Override + public List listDiscoveredBoards(boolean complete) { + // XXX: parameter "complete "is really needed? + // should be checked on all existing discoveries + synchronized (portList) { + return new ArrayList<>(portList); + } + } + + @Override + public String toString() { + return discoveryName; + } +} diff --git a/arduino-core/src/cc/arduino/packages/discoverers/PluggableDiscoveryMessage.java b/arduino-core/src/cc/arduino/packages/discoverers/PluggableDiscoveryMessage.java new file mode 100644 index 000000000..3a377d064 --- /dev/null +++ b/arduino-core/src/cc/arduino/packages/discoverers/PluggableDiscoveryMessage.java @@ -0,0 +1,43 @@ +/* + * 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 2018 Arduino SA (http://www.arduino.cc/) + */ + +package cc.arduino.packages.discoverers; + +public class PluggableDiscoveryMessage { + private String eventType; // "add", "remove", "error" + private String message; // optional message, e.g. "START_SYNC not supported" + + public String getEventType() { + return eventType; + } + + public String getMessage() { + return message; + } +} diff --git a/arduino-core/src/cc/arduino/packages/discoverers/SerialDiscovery.java b/arduino-core/src/cc/arduino/packages/discoverers/SerialDiscovery.java deleted file mode 100644 index 4de78552c..000000000 --- a/arduino-core/src/cc/arduino/packages/discoverers/SerialDiscovery.java +++ /dev/null @@ -1,103 +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; - -import cc.arduino.packages.BoardPort; -import cc.arduino.packages.Discovery; -import cc.arduino.packages.discoverers.serial.SerialBoardsLister; - -import java.util.LinkedList; -import java.util.List; -import java.util.Timer; - -public class SerialDiscovery implements Discovery, Runnable { - - private Timer serialBoardsListerTimer; - private final List serialBoardPorts; - private SerialBoardsLister serialBoardsLister = new SerialBoardsLister(this); - - public SerialDiscovery() { - this.serialBoardPorts = new LinkedList<>(); - } - - @Override - public List listDiscoveredBoards() { - return getSerialBoardPorts(false); - } - - @Override - 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) { - 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 run() { - start(); - } - - @Override - public void start() { - this.serialBoardsListerTimer = new Timer(SerialBoardsLister.class.getName()); - serialBoardsLister.start(serialBoardsListerTimer); - } - - @Override - public void stop() { - this.serialBoardsListerTimer.purge(); - } -} diff --git a/arduino-core/src/cc/arduino/packages/discoverers/serial/SerialBoardsLister.java b/arduino-core/src/cc/arduino/packages/discoverers/serial/SerialDiscovery.java similarity index 69% rename from arduino-core/src/cc/arduino/packages/discoverers/serial/SerialBoardsLister.java rename to arduino-core/src/cc/arduino/packages/discoverers/serial/SerialDiscovery.java index d055a921a..3d5fb2fd7 100644 --- a/arduino-core/src/cc/arduino/packages/discoverers/serial/SerialBoardsLister.java +++ b/arduino-core/src/cc/arduino/packages/discoverers/serial/SerialDiscovery.java @@ -29,41 +29,90 @@ package cc.arduino.packages.discoverers.serial; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Timer; +import java.util.TimerTask; + import cc.arduino.packages.BoardPort; -import cc.arduino.packages.discoverers.SerialDiscovery; +import cc.arduino.packages.Discovery; import processing.app.BaseNoGui; import processing.app.Platform; import processing.app.debug.TargetBoard; -import java.util.*; +public class SerialDiscovery implements Discovery, Runnable { -public class SerialBoardsLister extends TimerTask { - - private final SerialDiscovery serialDiscovery; - private final List boardPorts = new LinkedList<>(); - private List oldPorts = new LinkedList<>(); + private Timer serialBoardsListerTimer; + private final List serialBoardPorts = new ArrayList<>(); + private final List boardPorts = new ArrayList<>(); + private final List oldPorts = new ArrayList<>(); public boolean uploadInProgress = false; public boolean pausePolling = false; private BoardPort oldUploadBoardPort = null; - public SerialBoardsLister(SerialDiscovery serialDiscovery) { - this.serialDiscovery = serialDiscovery; + + @Override + public List listDiscoveredBoards() { + return listDiscoveredBoards(false); } - public void start(Timer timer) { - timer.schedule(this, 0, 1000); + @Override + public List listDiscoveredBoards(boolean complete) { + if (complete) { + return new ArrayList<>(serialBoardPorts); + } + List onlineBoardPorts = new ArrayList<>(); + for (BoardPort port : serialBoardPorts) { + if (port.isOnline() == true) { + onlineBoardPorts.add(port); + } + } + return onlineBoardPorts; } - public synchronized void retriggerDiscovery(boolean polled) { + public void setSerialBoardPorts(List newSerialBoardPorts) { + serialBoardPorts.clear(); + serialBoardPorts.addAll(newSerialBoardPorts); + } + + public void setUploadInProgress(boolean param) { + uploadInProgress = param; + } + + public void pausePolling(boolean param) { + pausePolling = param; + } + + @Override + public void run() { + start(); + } + + @Override + public void start() { + serialBoardsListerTimer = new Timer(SerialDiscovery.class.getName()); + serialBoardsListerTimer.schedule(new TimerTask() { + @Override + public void run() { + if (BaseNoGui.packages != null && !pausePolling) { + forceRefresh(); + } + } + }, 0, 1000); + } + + @Override + public void stop() { + serialBoardsListerTimer.cancel(); + } + + public synchronized void forceRefresh() { Platform platform = BaseNoGui.getPlatform(); if (platform == null) { return; } - if (polled && pausePolling) { - return; - } - List ports = platform.listSerials(); if (ports.equals(oldPorts)) { return; @@ -113,19 +162,18 @@ public class SerialBoardsLister extends TimerTask { 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) { + if (boardPort == null) { boardPort = new BoardPort(); + boardPorts.add(boardPort); } boardPort.setAddress(port); boardPort.setProtocol("serial"); @@ -136,50 +184,35 @@ public class SerialBoardsLister extends TimerTask { if (boardData != null) { boardPort.getPrefs().put("vid", boardData.get("vid").toString()); boardPort.getPrefs().put("pid", boardData.get("pid").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) { String boardName = board.getName(); - if (boardName != null) { - label += " (" + boardName + ")"; - } boardPort.setBoardName(boardName); } } else { if (!parts[1].equals("0000")) { - boardPort.setVIDPID(parts[1], parts[2]); + boardPort.getPrefs().put("vid", parts[1]); + boardPort.getPrefs().put("pid", parts[2]); // ask Cloud API to match the board with known VID/PID pair platform.getBoardWithMatchingVidPidFromCloud(parts[1], parts[2]); } else { - boardPort.setVIDPID("0000", "0000"); - boardPort.setISerial(""); + boardPort.getPrefs().put("vid", "0000"); + boardPort.getPrefs().put("pid", "0000"); + boardPort.getPrefs().put("iserial", ""); } } boardPort.setLabel(label); - if (!updatingInfos) { - boardPorts.add(boardPort); - } } - serialDiscovery.setSerialBoardPorts(boardPorts); - } - - @Override - public void run() { - if (BaseNoGui.packages == null) { - return; - } - retriggerDiscovery(true); + setSerialBoardPorts(boardPorts); } } diff --git a/arduino-core/src/processing/app/BaseNoGui.java b/arduino-core/src/processing/app/BaseNoGui.java index 7ab457dcf..0a5876971 100644 --- a/arduino-core/src/processing/app/BaseNoGui.java +++ b/arduino-core/src/processing/app/BaseNoGui.java @@ -223,7 +223,7 @@ public class BaseNoGui { public static DiscoveryManager getDiscoveryManager() { if (discoveryManager == null) { - discoveryManager = new DiscoveryManager(); + discoveryManager = new DiscoveryManager(packages); } return discoveryManager; } @@ -506,7 +506,7 @@ public class BaseNoGui { } if (discoveryManager == null) { - discoveryManager = new DiscoveryManager(); + discoveryManager = new DiscoveryManager(packages); } } diff --git a/arduino-core/src/processing/app/debug/LegacyTargetBoard.java b/arduino-core/src/processing/app/debug/LegacyTargetBoard.java index 16770a635..09e7ac508 100644 --- a/arduino-core/src/processing/app/debug/LegacyTargetBoard.java +++ b/arduino-core/src/processing/app/debug/LegacyTargetBoard.java @@ -100,4 +100,8 @@ public class LegacyTargetBoard implements TargetBoard { return containerPlatform; } + @Override + public String getFQBN() { + return getContainerPlatform().getContainerPackage().getId() + ":" + getContainerPlatform().getId() + ":" + getId(); + } } diff --git a/arduino-core/src/processing/app/debug/TargetBoard.java b/arduino-core/src/processing/app/debug/TargetBoard.java index 5dae86906..d635bbf1d 100644 --- a/arduino-core/src/processing/app/debug/TargetBoard.java +++ b/arduino-core/src/processing/app/debug/TargetBoard.java @@ -92,4 +92,6 @@ public interface TargetBoard { public TargetPlatform getContainerPlatform(); + public String getFQBN(); + } diff --git a/arduino-core/src/processing/app/helpers/CommandlineParser.java b/arduino-core/src/processing/app/helpers/CommandlineParser.java index 83d34fed7..4c8b3a241 100644 --- a/arduino-core/src/processing/app/helpers/CommandlineParser.java +++ b/arduino-core/src/processing/app/helpers/CommandlineParser.java @@ -41,6 +41,7 @@ public class CommandlineParser { private String getPref; private String boardToInstall; private String libraryToInstall; + private Optional uploadPort = Optional.empty(); private final List filenames = new LinkedList<>(); public CommandlineParser(String[] args) { @@ -141,7 +142,7 @@ public class CommandlineParser { i++; if (i >= args.length) BaseNoGui.showError(null, tr("Argument required for --port"), 3); - BaseNoGui.selectSerialPort(args[i]); + uploadPort = Optional.of(args[i]); if (action == ACTION.GUI) action = ACTION.NOOP; continue; @@ -356,4 +357,8 @@ public class CommandlineParser { public boolean isPreserveTempFiles() { return preserveTempFiles; } + + public Optional getUploadPort() { + return uploadPort; + } }