package pif.arduino; import java.io.File; import java.io.FileNotFoundException; import java.io.IOException; import java.io.PrintStream; import org.apache.commons.cli.BasicParser; import org.apache.commons.cli.CommandLine; import org.apache.commons.cli.HelpFormatter; import org.apache.commons.cli.Options; import org.apache.commons.cli.ParseException; import org.apache.log4j.Logger; import pif.arduino.tools.*; import cc.arduino.packages.uploaders.MySerialUploader; import processing.app.PreferencesData; import processing.app.Serial; import processing.app.debug.TargetBoard; /** * Main class for serial console * @author pif */ public class ArdConsole implements Console.ConsolePeer, FileScanner.FileScanHandler { private static Logger logger = Logger.getLogger(ArdConsole.class); static final short STATE_NONE = 0; static final short STATE_UPLOADING = 1; static final short STATE_CONNECTED = 2; static final short STATE_FAIL = -1; static short state = STATE_NONE; static Options options; static { options = new Options(); options.addOption("h", "help", false, "usage"); options.addOption("I", "arduino-ide", true, "base installation directory of Arduino IDE"); options.addOption(LoadConfig.PREFERENCES_OPTION, false, "alternate Arduino IDE preferences file"); options.addOption("B", "boards", false, "list supported target boards"); options.addOption("b", "board", true, "set target boards"); options.addOption("P", "ports", false, "list available ports"); options.addOption("p", "port", true, "set port to connect to"); options.addOption("s", "baudrate", true, "set port baudrate"); options.addOption("f", "file", true, "file to scan / upload"); options.addOption("u", "upload", false, "launch upload at startup"); options.addOption("v", "verbose", false, "set upload verbosity"); options.addOption("d", "debug", false, "set debug level"); options.addOption("x", "exit", false, "exit after dump commands instead of launching console"); options.addOption("r", "raw", false, "raw mode : dump commands list only ids, console is raw, no history nor editing facilities"); options.addOption("c", "command", true, "comma separated list of commands to send after connection"); } protected static boolean rawMode = false; protected String boardName, portName; protected ArduinoConfig.PortBoard port; protected File uploadFile; String uploadFilePath, uploadFileName; protected FileScanner scanner; protected MySerialUploader uploader; // "arduino IDE" serial object protected MySerial serial = null; protected Console console; /** * parse command line, load arduino config and instantiate a ArdConsole * @param args */ public static void main(String[] args) { CommandLine commandLine = null; try { commandLine = new BasicParser().parse(options, args); } catch (ParseException e) { logger.error(e); usage(1); } if (commandLine.hasOption('h')) { usage(0); } if (!LoadConfig.load(commandLine)) { usage(2); } if (commandLine.hasOption('r')) { rawMode = true; } if (commandLine.hasOption('P')) { ArduinoConfig.listPorts(System.out, rawMode); } if (commandLine.hasOption('B')) { ArduinoConfig.listBoards(System.out, rawMode, false); } // set verbosity if (commandLine.hasOption('v')) { PreferencesData.setBoolean("upload.verbose", true); } else { PreferencesData.setBoolean("upload.verbose", false); } if (commandLine.hasOption('d')) { Logger.getRootLogger().setLevel(org.apache.log4j.Level.DEBUG); } // ok, it will become a bit more complicated, pass to an instance... new ArdConsole(commandLine); } /** * initialize board, port ..., launch file scanner if file specified, * launch console and connect * @param commandLine */ ArdConsole(CommandLine commandLine) { if (commandLine.hasOption('p')) { setPort(commandLine.getOptionValue('p')); } if (commandLine.hasOption('b')) { setBoard(commandLine.getOptionValue('b')); } if (commandLine.hasOption('s')) { ArduinoConfig.baudrate = Integer.parseInt(commandLine.getOptionValue('s')); } if (commandLine.hasOption('f')) { uploadFile = new File(commandLine.getOptionValue('f')); // upload config waits for a file name specified as : {build.path}/{build.project_name}.hex or .bin // thus, have to split it uploadFilePath = uploadFile.getParent(); uploadFileName = uploadFile.getName(); if (!uploadFileName.endsWith(".hex") && !uploadFileName.endsWith(".bin")) { logger.error("file to upload must have .hex or .bin extension"); System.exit(2); } uploadFileName = uploadFileName.substring(0, uploadFileName.length() - 4); } if (commandLine.hasOption('u')) { if (uploadFile == null) { logger.error("Can't upload : no file specified (-f option)"); usage(2); } else { launchUpload(); } } if (commandLine.hasOption('x')) { System.exit(0); } try { if (uploadFile != null) { scanner = new FileScanner(uploadFile, this); } } catch (FileNotFoundException e) { logger.error("File to upload doesn't exists"); usage(2); } // establish console console = new Console(this, rawMode); if (portName != null) { connect(); } console.start(); if (commandLine.hasOption('c')) { for (String cmd : commandLine.getOptionValue('c').split(",")) { console.handleCommand(cmd); } } } protected static void usage(int exitCode) { HelpFormatter fmt = new HelpFormatter(); String footer = "\narduino ide path is looked for respectivly in command line option, java properties (-DARDUINO_IDE=...)," + " ARDUINO_IDE environment variable, location of BaseNoGui arduino class if already loaded" + " (if classpath was set accordingly for example)"; fmt.printHelp(80, "java -jar main_jar_file.jar pif.arduino.ArdConsole options ...", "options :", options, footer); System.exit(exitCode); } void help() { String help = "!ports [x]: list serial port (as -P command line option). Append any character after command to force a new port scan\n" + " !boards : list target plateforms (as -B command line option)\n" + " !port xxx : set current serial port (like -p option)\n" + " !board xxx : set current board (like -b option)\n" + " !connect and !disconnect : as the name suggests ...\n" + " !baudrate nn : set baudrate\n" + " !reset : try to reset serial port, then reconnect if was connected (useful after upload in some cases)\n" + " !upload : launch upload then reconnect if was connected\n" + " !verbose [0,off]: set/reset verbosity flag\n" + " !file filename: set file path to scan for modification"; System.out.println(help); } protected void setPort(String portName) { ArduinoConfig.PortBoard newPort = ArduinoConfig.getPortByName(portName); if (newPort == null) { logger.error("Unknown port " + portName); return; } port = newPort; this.portName = portName; ArduinoConfig.setPort(port); // if no board selected and port identified one, set it if (boardName == null) { if (port.board != null) { ArduinoConfig.setBoard(port.board); } } else if (port.board != null && !boardName.equals(port.board.getId())) { logger.warn(String.format("port was detected has a different board (%s) than specifed one (%s)", port.board.getId(), boardName)); } } protected void setBoard(String boardName) { TargetBoard board = ArduinoConfig.setBoard(boardName); if (board == null) { logger.error("Unknown board " + boardName); return; } if (port != null && port.board != null && port.board != board) { logger.warn(String.format("port was detected has a different board (%s) than specifed one (%s)", port.board.getId(), board.getId())); } this.boardName = boardName; } @Override public void onOutgoingData(byte[] data) { if (state == STATE_CONNECTED) { serial.write(data); } else { logger.warn("Not connected : can't send data"); } } @Override public boolean onCommand(String command) { String args = null; int space = command.indexOf(' '); if (space != -1) { args = command.substring(space + 1); command = command.substring(0, space); } if (command.equals("upload") || command.equals("u")) { launchUpload(); } else if (command.equals("boards")) { ArduinoConfig.listBoards(System.out, rawMode, false); } else if (command.equals("board")) { setBoard(args); } else if (command.equals("ports")) { ArduinoConfig.listPorts(System.out, rawMode, args != null); } else if (command.equals("port")) { setPort(args); } else if (command.equals("baudrate")) { int baudrate = Integer.parseInt(args); ArduinoConfig.baudrate = baudrate; } else if (command.equals("connect")) { connect(); } else if (command.equals("disconnect")) { disconnect(); } else if (command.equals("verbose")) { if ("off".equals(args) || "0".equals(args)) { PreferencesData.setBoolean("upload.verbose", false); } else { PreferencesData.setBoolean("upload.verbose", true); } } else if (command.equals("file") || command.equals("scan")) { if (args == null) { logger.error("missing file path to scan"); } else { if (scanner != null) { scanner.stop(); } try { scanner = new FileScanner(new File(args), this); } catch (FileNotFoundException e) { logger.error("Bad file path to scan"); } } } else if (command.equals("reset")) { resetPort(); } else if (command.equals("status")) { status(System.out); } else if (command.equals("help")) { // trap exit to output our own help help(); // but return false to let caller handle its own return false; } else { return false; } return true; } @Override public void onDisconnect(int status) { while (state == STATE_UPLOADING) { // if uploading, wait it to finish try { Thread.sleep(1000); } catch (InterruptedException e) {} } if (state == STATE_CONNECTED) { disconnect(); } if (scanner != null) { scanner.stop(); } } @Override public void onFileChange() { try { console.insertString("** Change detected in scanned file **"); } catch (IOException e) { logger.info("** Change detected in scanned file **"); } launchUpload(); } protected void connect() { if (portName == null) { logger.error("port was not specified, can't connect"); return; } // create this object lately because its constructor opens connection // and we want to be able to upload at startup, thus before connection if (state == STATE_NONE) { // serial must be null try { serial = new MySerial(console, ArduinoConfig.baudrate); state = STATE_CONNECTED; } catch (/*processing.app.Serial*/Exception e) { // just specify Exception or jvm crashes at startup // with java.lang.NoClassDefFoundError logger.error("Can't connect", e); try { // try to clean up ? serial.dispose(); } catch (IOException e1) {} serial = null; state = STATE_FAIL; } } else if (state == STATE_CONNECTED) { logger.warn("already connected"); } else if (state == STATE_UPLOADING) { logger.warn("Can't connect during upload"); } } protected void disconnect() { if (state == STATE_CONNECTED) { try { serial.dispose(); state = STATE_NONE; } catch (IOException e) { logger.error("Can't disconnect", e); state = STATE_FAIL; } finally { serial = null; } } else { logger.warn("not connected"); } } protected void resetPort() { if (portName == null) { logger.error("port was not specified, can't connect"); return; } if (state == STATE_CONNECTED) { disconnect(); } try { Serial.touchPort(port.address, 1200); } catch (Exception e) { logger.error("port reset failed", e); } connect(); } protected void status(PrintStream output) { if (boardName != null) { output.println("Target platform is " + boardName); } if (port != null) { output.println("Configured to connect on port " + port.address); } output.println("Baudrate is " + ArduinoConfig.baudrate); if (uploadFile != null) { output.println("Scanning file " + uploadFile); } switch(state) { case STATE_NONE: case STATE_FAIL: output.println("Not connected"); break; case STATE_UPLOADING: output.println("Uploading code"); break; case STATE_CONNECTED: output.println("Connected"); break; } } protected void launchUpload() { if (portName == null) { logger.error("port was not specified, can't connect"); return; } logger.info("** launching upload **"); if (uploader == null) { uploader = new MySerialUploader(); } short stateBefore = state; switch(state) { case STATE_UPLOADING: // possible ? logger.warn("Already uploading, abort"); return; case STATE_CONNECTED: disconnect(); break; case STATE_FAIL: case STATE_NONE: // noop } MessageRenderer warnings = new MessageRenderer(System.out, "[upload]"); try { uploader.uploadUsingPreferences(uploadFile, uploadFilePath, uploadFileName, false, warnings); } catch (Exception e) { logger.error("Upload failed", e); } // for (String line: warnings) { // logger.warn(line); // } if (stateBefore == STATE_CONNECTED) { connect(); } } }