mirror of
https://github.com/arduino/Arduino.git
synced 2024-11-29 10:24:12 +01:00
HTTPUploader is now SSHUploader
running avrdude with ssh, thus recovering incremental output
This commit is contained in:
parent
becadbf8cf
commit
ae222c10e7
@ -1,19 +1,15 @@
|
||||
package cc.arduino.packages;
|
||||
|
||||
import processing.app.AbstractMonitor;
|
||||
import processing.app.Base;
|
||||
import processing.app.Constants;
|
||||
import processing.app.NetworkMonitor;
|
||||
import processing.app.SerialMonitor;
|
||||
import processing.app.debug.TargetBoard;
|
||||
import cc.arduino.packages.uploaders.HttpUploader;
|
||||
import cc.arduino.packages.uploaders.SSHUploader;
|
||||
import cc.arduino.packages.uploaders.SerialUploader;
|
||||
import processing.app.*;
|
||||
import processing.app.debug.TargetBoard;
|
||||
|
||||
public class UploaderFactory {
|
||||
|
||||
public Uploader newUploader(TargetBoard board, String port) {
|
||||
if ("true".equals(board.getPreferences().get("upload.via_http")) && Constants.IPV4_ADDRESS.matcher(port).find()) {
|
||||
return new HttpUploader(port);
|
||||
if ("true".equals(board.getPreferences().get("upload.via_ssh")) && Constants.IPV4_ADDRESS.matcher(port).find()) {
|
||||
return new SSHUploader(port);
|
||||
}
|
||||
|
||||
return new SerialUploader();
|
||||
|
@ -1,227 +0,0 @@
|
||||
package cc.arduino.packages.uploaders;
|
||||
|
||||
import cc.arduino.packages.Uploader;
|
||||
import com.jcraft.jsch.*;
|
||||
import org.apache.commons.codec.binary.Base64;
|
||||
import org.apache.commons.httpclient.HttpClient;
|
||||
import org.apache.commons.httpclient.HttpStatus;
|
||||
import org.apache.commons.httpclient.NameValuePair;
|
||||
import org.apache.commons.httpclient.methods.GetMethod;
|
||||
import org.apache.commons.httpclient.methods.PostMethod;
|
||||
import org.apache.commons.httpclient.protocol.Protocol;
|
||||
import processing.app.Base;
|
||||
import processing.app.Constants;
|
||||
import processing.app.NetworkMonitor;
|
||||
import processing.app.Preferences;
|
||||
import processing.app.debug.EasySSLProtocolSocketFactory;
|
||||
import processing.app.debug.RunnerException;
|
||||
import processing.app.debug.TargetPlatform;
|
||||
import processing.app.helpers.PreferencesMap;
|
||||
|
||||
import java.io.*;
|
||||
import java.util.regex.Matcher;
|
||||
|
||||
import static processing.app.I18n._;
|
||||
|
||||
public class HttpUploader extends Uploader {
|
||||
|
||||
private static final String PROTOCOL = "https://";
|
||||
|
||||
static {
|
||||
Protocol.registerProtocol("https", new Protocol("https", new EasySSLProtocolSocketFactory(), 443));
|
||||
}
|
||||
|
||||
private final HttpClient client;
|
||||
private final String ipAddress;
|
||||
private final String baseUrl;
|
||||
|
||||
public HttpUploader(String port) {
|
||||
client = new HttpClient();
|
||||
Matcher matcher = Constants.IPV4_ADDRESS.matcher(port);
|
||||
if (!matcher.find()) {
|
||||
throw new IllegalArgumentException(port);
|
||||
}
|
||||
ipAddress = matcher.group();
|
||||
baseUrl = PROTOCOL + ipAddress + "/cgi-bin/luci/arduino";
|
||||
}
|
||||
|
||||
public boolean requiresAuthorization() {
|
||||
return true;
|
||||
}
|
||||
|
||||
public String getAuthorizationKey() {
|
||||
return "runtime.pwd." + ipAddress;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean uploadUsingPreferences(String buildPath, String className, boolean usingProgrammer) throws RunnerException {
|
||||
if (usingProgrammer) {
|
||||
System.err.println(_("Http upload using programmer not supported"));
|
||||
return false;
|
||||
}
|
||||
|
||||
client.getHttpConnectionManager().getParams().setConnectionTimeout(5000);
|
||||
String password = Preferences.get(getAuthorizationKey());
|
||||
String auth = Base64.encodeBase64String(("root:" + password).getBytes());
|
||||
|
||||
int sleptTimes = 1;
|
||||
while (boardNotReady(auth)) {
|
||||
try {
|
||||
Thread.sleep(100);
|
||||
} catch (InterruptedException e) {
|
||||
throw new RunnerException(e);
|
||||
}
|
||||
if (sleptTimes >= 3) {
|
||||
throw new RunnerException(_("The board is not yet ready"));
|
||||
}
|
||||
sleptTimes += 1;
|
||||
}
|
||||
|
||||
try {
|
||||
scpHexToBoard(buildPath, className);
|
||||
} catch (Exception e) {
|
||||
throw new RunnerException(e);
|
||||
}
|
||||
|
||||
TargetPlatform targetPlatform = Base.getTargetPlatform();
|
||||
PreferencesMap prefs = Preferences.getMap();
|
||||
prefs.putAll(Base.getBoardPreferences());
|
||||
prefs.putAll(targetPlatform.getTool(prefs.get("upload.tool")));
|
||||
|
||||
PostMethod post = new PostMethod(baseUrl + "/flash");
|
||||
post.setRequestHeader("Authorization", "Basic " + auth);
|
||||
NameValuePair[] data = {
|
||||
new NameValuePair("params", verbose ? prefs.get("upload.params.verbose") : prefs.get("upload.params.quiet"))
|
||||
};
|
||||
post.setRequestBody(data);
|
||||
|
||||
int statusCode;
|
||||
try {
|
||||
statusCode = client.executeMethod(post);
|
||||
|
||||
System.err.println(post.getResponseBodyAsString());
|
||||
return statusCode == HttpStatus.SC_OK;
|
||||
} catch (IOException e) {
|
||||
throw new RunnerException(e);
|
||||
} finally {
|
||||
post.releaseConnection();
|
||||
}
|
||||
}
|
||||
|
||||
private void scpHexToBoard(String buildPath, String className) throws JSchException, IOException {
|
||||
Session session = null;
|
||||
Channel channel = null;
|
||||
OutputStream out = null;
|
||||
InputStream in = null;
|
||||
try {
|
||||
JSch jSch = new JSch();
|
||||
session = jSch.getSession("root", ipAddress, 22);
|
||||
session.setPassword(Preferences.get(getAuthorizationKey()));
|
||||
|
||||
session.setUserInfo(new NetworkMonitor.NoInteractionUserInfo());
|
||||
session.connect(30000);
|
||||
|
||||
channel = session.openChannel("exec");
|
||||
((ChannelExec) channel).setCommand("scp -t /tmp/sketch.hex");
|
||||
|
||||
out = channel.getOutputStream();
|
||||
in = channel.getInputStream();
|
||||
|
||||
channel.connect();
|
||||
|
||||
ensureAcknowledged(out, in);
|
||||
|
||||
File hex = new File(buildPath, className + ".hex");
|
||||
|
||||
sendFileSizeAndName(out, in, hex);
|
||||
ensureAcknowledged(out, in);
|
||||
|
||||
sendFileContents(out, hex);
|
||||
ensureAcknowledged(out, in);
|
||||
} finally {
|
||||
if (out != null) {
|
||||
out.close();
|
||||
}
|
||||
if (channel != null) {
|
||||
channel.disconnect();
|
||||
}
|
||||
if (session != null) {
|
||||
session.disconnect();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void ensureAcknowledged(OutputStream out, InputStream in) throws IOException {
|
||||
out.flush();
|
||||
|
||||
int b = in.read();
|
||||
|
||||
if (b == 0) return;
|
||||
if (b == -1) return;
|
||||
|
||||
if (b == 1 || b == 2) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append("SCP error: ");
|
||||
|
||||
int c;
|
||||
do {
|
||||
c = in.read();
|
||||
sb.append((char) c);
|
||||
} while (c != '\n');
|
||||
|
||||
throw new IOException(sb.toString());
|
||||
}
|
||||
|
||||
throw new IOException("Uknown SCP error: " + b);
|
||||
}
|
||||
|
||||
private void sendFileContents(OutputStream out, File hex) throws IOException {
|
||||
// send file contents
|
||||
FileInputStream fis = null;
|
||||
try {
|
||||
fis = new FileInputStream(hex);
|
||||
byte[] buf = new byte[4096];
|
||||
while (true) {
|
||||
int len = fis.read(buf, 0, buf.length);
|
||||
if (len <= 0) break;
|
||||
out.write(buf, 0, len); //out.flush();
|
||||
}
|
||||
|
||||
// send \0 (terminates file)
|
||||
buf[0] = 0;
|
||||
out.write(buf, 0, 1);
|
||||
} finally {
|
||||
if (fis != null) {
|
||||
fis.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void sendFileSizeAndName(OutputStream out, InputStream in, File hex) throws IOException {
|
||||
// send "C0644 filesize filename"
|
||||
long filesize = hex.length();
|
||||
String command = "C0644 " + filesize + " " + hex.getName() + "\n";
|
||||
out.write(command.getBytes());
|
||||
}
|
||||
|
||||
protected boolean boardNotReady(String auth) throws RunnerException {
|
||||
GetMethod get = new GetMethod(baseUrl + "/ready");
|
||||
get.setRequestHeader("Authorization", "Basic " + auth);
|
||||
try {
|
||||
int httpStatus = client.executeMethod(get);
|
||||
if (httpStatus % HttpStatus.SC_BAD_REQUEST < 100 || httpStatus % HttpStatus.SC_INTERNAL_SERVER_ERROR < 100) {
|
||||
System.err.println(get.getResponseBodyAsString());
|
||||
throw new RunnerException("Problem knowing if the board was ready");
|
||||
}
|
||||
return httpStatus != HttpStatus.SC_OK;
|
||||
} catch (IOException e) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean burnBootloader() throws RunnerException {
|
||||
throw new RunnerException("Can't burn bootloader via http");
|
||||
}
|
||||
|
||||
}
|
266
app/src/cc/arduino/packages/uploaders/SSHUploader.java
Normal file
266
app/src/cc/arduino/packages/uploaders/SSHUploader.java
Normal file
@ -0,0 +1,266 @@
|
||||
package cc.arduino.packages.uploaders;
|
||||
|
||||
import cc.arduino.packages.Uploader;
|
||||
import com.jcraft.jsch.*;
|
||||
import processing.app.Base;
|
||||
import processing.app.Constants;
|
||||
import processing.app.NetworkMonitor;
|
||||
import processing.app.Preferences;
|
||||
import processing.app.debug.RunnerException;
|
||||
import processing.app.debug.TargetPlatform;
|
||||
import processing.app.helpers.PreferencesMap;
|
||||
|
||||
import java.io.*;
|
||||
import java.util.regex.Matcher;
|
||||
|
||||
import static processing.app.I18n._;
|
||||
|
||||
public class SSHUploader extends Uploader {
|
||||
|
||||
private final String ipAddress;
|
||||
|
||||
public SSHUploader(String port) {
|
||||
Matcher matcher = Constants.IPV4_ADDRESS.matcher(port);
|
||||
if (!matcher.find()) {
|
||||
throw new IllegalArgumentException(port);
|
||||
}
|
||||
ipAddress = matcher.group();
|
||||
}
|
||||
|
||||
public boolean requiresAuthorization() {
|
||||
return true;
|
||||
}
|
||||
|
||||
public String getAuthorizationKey() {
|
||||
return "runtime.pwd." + ipAddress;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean uploadUsingPreferences(String buildPath, String className, boolean usingProgrammer) throws RunnerException {
|
||||
if (usingProgrammer) {
|
||||
System.err.println(_("Http upload using programmer not supported"));
|
||||
return false;
|
||||
}
|
||||
|
||||
Session session = null;
|
||||
try {
|
||||
JSch jSch = new JSch();
|
||||
session = jSch.getSession("root", ipAddress, 22);
|
||||
session.setPassword(Preferences.get(getAuthorizationKey()));
|
||||
|
||||
session.setUserInfo(new NetworkMonitor.NoInteractionUserInfo());
|
||||
session.connect(30000);
|
||||
|
||||
String uploadedSketchFile = new SCP(session).scpHexToBoard(buildPath, className);
|
||||
|
||||
TargetPlatform targetPlatform = Base.getTargetPlatform();
|
||||
PreferencesMap prefs = Preferences.getMap();
|
||||
prefs.putAll(Base.getBoardPreferences());
|
||||
prefs.putAll(targetPlatform.getTool(prefs.get("upload.tool")));
|
||||
|
||||
return new SSHAVRDude(session).runAVRDude(uploadedSketchFile, verbose ? prefs.get("upload.params.verbose") : prefs.get("upload.params.quiet"));
|
||||
} catch (JSchException e) {
|
||||
if ("Auth cancel".equals(e.getMessage())) {
|
||||
return false;
|
||||
}
|
||||
throw new RunnerException(e);
|
||||
} catch (Exception e) {
|
||||
throw new RunnerException(e);
|
||||
} finally {
|
||||
if (session != null) {
|
||||
session.disconnect();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean burnBootloader() throws RunnerException {
|
||||
throw new RunnerException("Can't burn bootloader via SSH");
|
||||
}
|
||||
|
||||
private static abstract class SSH {
|
||||
|
||||
protected final Session session;
|
||||
|
||||
public SSH(Session session) {
|
||||
this.session = session;
|
||||
}
|
||||
|
||||
protected boolean execSyncCommand(String command) throws JSchException, IOException {
|
||||
return execSyncCommand(command, false);
|
||||
}
|
||||
|
||||
protected boolean execSyncCommand(String command, boolean ignoreError) throws JSchException, IOException {
|
||||
InputStream stdout = null;
|
||||
InputStream stderr = null;
|
||||
Channel channel = null;
|
||||
try {
|
||||
channel = session.openChannel("exec");
|
||||
((ChannelExec) channel).setCommand(command);
|
||||
|
||||
channel.setInputStream(null);
|
||||
|
||||
stdout = channel.getInputStream();
|
||||
if (!ignoreError) {
|
||||
stderr = ((ChannelExec) channel).getErrStream();
|
||||
}
|
||||
|
||||
channel.connect();
|
||||
|
||||
int exitCode = consumeOutputSyncAndReturnExitCode(channel, stdout, stderr);
|
||||
|
||||
return ignoreError || exitCode == 0;
|
||||
|
||||
} finally {
|
||||
if (stdout != null) {
|
||||
stdout.close();
|
||||
}
|
||||
if (stderr != null) {
|
||||
stderr.close();
|
||||
}
|
||||
if (channel != null) {
|
||||
channel.disconnect();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected int consumeOutputSyncAndReturnExitCode(Channel channel, InputStream stdout, InputStream stderr) throws IOException {
|
||||
byte[] tmp = new byte[102400];
|
||||
while (true) {
|
||||
while (stdout.available() > 0) {
|
||||
int i = stdout.read(tmp, 0, tmp.length);
|
||||
if (i < 0) break;
|
||||
System.out.print(new String(tmp, 0, i));
|
||||
}
|
||||
while (stderr != null && stderr.available() > 0) {
|
||||
int i = stderr.read(tmp, 0, tmp.length);
|
||||
if (i < 0) break;
|
||||
System.err.print(new String(tmp, 0, i));
|
||||
}
|
||||
if (channel.isClosed()) {
|
||||
return channel.getExitStatus();
|
||||
}
|
||||
try {
|
||||
Thread.sleep(100);
|
||||
} catch (Exception ee) {
|
||||
// noop
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static class SSHAVRDude extends SSH {
|
||||
|
||||
public SSHAVRDude(Session session) {
|
||||
super(session);
|
||||
}
|
||||
|
||||
public boolean runAVRDude(String sketchFile, String additionalParams) throws IOException, JSchException {
|
||||
boolean success = execSyncCommand("merge-sketch-with-bootloader " + sketchFile);
|
||||
success = success && execSyncCommand("kill-bridge", true);
|
||||
success = success && execSyncCommand("run-avrdude " + sketchFile + " '" + additionalParams + "'");
|
||||
return success;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static class SCP extends SSH {
|
||||
|
||||
private static final String SKETCH_FILE = "/tmp/sketch.hex";
|
||||
|
||||
public SCP(Session session) {
|
||||
super(session);
|
||||
}
|
||||
|
||||
public String scpHexToBoard(String buildPath, String className) throws JSchException, IOException {
|
||||
Channel channel = null;
|
||||
OutputStream out = null;
|
||||
InputStream in = null;
|
||||
try {
|
||||
channel = session.openChannel("exec");
|
||||
((ChannelExec) channel).setCommand("scp -t " + SKETCH_FILE);
|
||||
|
||||
out = channel.getOutputStream();
|
||||
in = channel.getInputStream();
|
||||
|
||||
channel.connect();
|
||||
|
||||
ensureAcknowledged(out, in);
|
||||
|
||||
File hex = new File(buildPath, className + ".hex");
|
||||
|
||||
sendFileSizeAndName(out, in, hex);
|
||||
ensureAcknowledged(out, in);
|
||||
|
||||
sendFileContents(out, hex);
|
||||
ensureAcknowledged(out, in);
|
||||
|
||||
return SKETCH_FILE;
|
||||
} finally {
|
||||
if (out != null) {
|
||||
out.close();
|
||||
}
|
||||
if (in != null) {
|
||||
in.close();
|
||||
}
|
||||
if (channel != null) {
|
||||
channel.disconnect();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void ensureAcknowledged(OutputStream out, InputStream in) throws IOException {
|
||||
out.flush();
|
||||
|
||||
int b = in.read();
|
||||
|
||||
if (b == 0) return;
|
||||
if (b == -1) return;
|
||||
|
||||
if (b == 1 || b == 2) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append("SCP error: ");
|
||||
|
||||
int c;
|
||||
do {
|
||||
c = in.read();
|
||||
sb.append((char) c);
|
||||
} while (c != '\n');
|
||||
|
||||
throw new IOException(sb.toString());
|
||||
}
|
||||
|
||||
throw new IOException("Uknown SCP error: " + b);
|
||||
}
|
||||
|
||||
private void sendFileContents(OutputStream out, File hex) throws IOException {
|
||||
FileInputStream fis = null;
|
||||
try {
|
||||
fis = new FileInputStream(hex);
|
||||
byte[] buf = new byte[4096];
|
||||
while (true) {
|
||||
int len = fis.read(buf, 0, buf.length);
|
||||
if (len <= 0) break;
|
||||
out.write(buf, 0, len);
|
||||
}
|
||||
|
||||
// \0 terminates file
|
||||
buf[0] = 0;
|
||||
out.write(buf, 0, 1);
|
||||
} finally {
|
||||
if (fis != null) {
|
||||
fis.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void sendFileSizeAndName(OutputStream out, InputStream in, File hex) throws IOException {
|
||||
long filesize = hex.length();
|
||||
String command = "C0644 " + filesize + " " + hex.getName() + "\n";
|
||||
out.write(command.getBytes());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -1,12 +1,11 @@
|
||||
package processing.app.debug;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
import cc.arduino.packages.Uploader;
|
||||
import cc.arduino.packages.UploaderFactory;
|
||||
import cc.arduino.packages.uploaders.HttpUploader;
|
||||
import cc.arduino.packages.uploaders.SSHUploader;
|
||||
import cc.arduino.packages.uploaders.SerialUploader;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import processing.app.AbstractWithPreferencesTest;
|
||||
|
||||
import java.io.File;
|
||||
@ -23,15 +22,15 @@ public class UploaderFactoryTest extends AbstractWithPreferencesTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldCreateAnInstanceOfHttpUploader() throws Exception {
|
||||
public void shouldCreateAnInstanceOfSSHUploader() throws Exception {
|
||||
TargetBoard board = targetPackage.getPlatforms().get("avr").getBoards().get("yun");
|
||||
Uploader uploader = new UploaderFactory().newUploader(board, "192.168.0.1 (yun)");
|
||||
|
||||
assertTrue(uploader instanceof HttpUploader);
|
||||
assertTrue(uploader instanceof SSHUploader);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldCreateAnInstanceOfBasicUploaderWhenHTTPIsUnsupported() throws Exception {
|
||||
public void shouldCreateAnInstanceOfBasicUploaderWhenSSHIsUnsupported() throws Exception {
|
||||
TargetBoard board = targetPackage.getPlatforms().get("avr").getBoards().get("uno");
|
||||
Uploader uploader = new UploaderFactory().newUploader(board, "192.168.0.1 (myyun)");
|
||||
|
||||
|
@ -5,7 +5,7 @@ menu.cpu=Processor
|
||||
##############################################################
|
||||
|
||||
yun.name=Arduino Yún
|
||||
yun.upload.via_http=true
|
||||
yun.upload.via_ssh=true
|
||||
|
||||
yun.vid.0=0x2341
|
||||
yun.pid.0=0x0041
|
||||
|
Loading…
Reference in New Issue
Block a user