/* * Copyright (C) 2011 Jason von Nieda <jason@vonnieda.org> * * This file is part of OpenPnP. * * OpenPnP 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 3 of the * License, or (at your option) any later version. * * OpenPnP 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 OpenPnP. If not, see * <http://www.gnu.org/licenses/>. * * For more information about OpenPnP visit http://openpnp.org */ package org.openpnp.machine.reference.driver; import java.io.IOException; import java.util.ArrayList; import java.util.List; import java.util.Locale; import java.util.Queue; import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.TimeoutException; import javax.swing.Action; import org.openpnp.gui.support.PropertySheetWizardAdapter; import org.openpnp.gui.support.Wizard; import org.openpnp.machine.reference.ReferenceActuator; import org.openpnp.machine.reference.ReferenceHead; import org.openpnp.machine.reference.ReferenceHeadMountable; import org.openpnp.machine.reference.ReferenceNozzle; import org.openpnp.machine.reference.driver.wizards.AbstractSerialPortDriverConfigurationWizard; import org.openpnp.model.LengthUnit; import org.openpnp.model.Location; import org.openpnp.spi.PropertySheetHolder; import org.pmw.tinylog.Logger; import org.simpleframework.xml.Attribute; /** * TODO: Consider adding some type of heartbeat to the firmware. */ // Implemented Codes // ------------------- // G0 -> G1 // G1 - Coordinated Movement X Y Z E // G2 - CW ARC // G3 - CCW ARC // G4 - Dwell S<seconds> or P<milliseconds> // G28 - Home all Axis // G90 - Use Absolute Coordinates // G91 - Use Relative Coordinates // G92 - Set current position to cordinates given // RepRap M Codes // M104 - Set extruder target temp // M105 - Read current temp // M106 - Fan on // M107 - Fan off // M109 - Wait for extruder current temp to reach target temp. // M114 - Display current position // Custom M Codes // M20 - List SD card // M21 - Init SD card // M22 - Release SD card // M23 - Select SD file (M23 filename.g) // M24 - Start/resume SD print // M25 - Pause SD print // M26 - Set SD position in bytes (M26 S12345) // M27 - Report SD print status // M28 - Start SD write (M28 filename.g) // M29 - Stop SD write // - <filename> - Delete file on sd card // M42 - Set output on free pins, on a non pwm pin (over pin 13 on an arduino mega) use S255 to turn // it on and S0 to turn it off. Use P to decide the pin (M42 P23 S255) would turn pin 23 on // M80 - Turn on Power Supply // M81 - Turn off Power Supply // M82 - Set E codes absolute (default) // M83 - Set E codes relative while in Absolute Coordinates (G90) mode // M84 - Disable steppers until next move, // or use S<seconds> to specify an inactivity timeout, after which the steppers will be disabled. S0 // to disable the timeout. // M85 - Set inactivity shutdown timer with parameter S<seconds>. To disable set zero (default) // M92 - Set axis_steps_per_unit - same syntax as G92 // M93 - Send axis_steps_per_unit // M115 - Capabilities string // M119 - Show Endstopper State // M140 - Set bed target temp // M190 - Wait for bed current temp to reach target temp. // M201 - Set maximum acceleration in units/s^2 for print moves (M201 X1000 Y1000) // M202 - Set maximum feedrate that your machine can sustain (M203 X200 Y200 Z300 E10000) in mm/sec // M203 - Set temperture monitor to Sx // M204 - Set default acceleration: S normal moves T filament only moves (M204 S3000 T7000) in // mm/sec^2 // M205 - advanced settings: minimum travel speed S=while printing T=travel only, X=maximum xy jerk, // Z=maximum Z jerk // M206 - set additional homing offset // M220 - set speed factor override percentage S=factor in percent // M221 - set extruder multiply factor S100 --> original Extrude Speed // M301 - Set PID parameters P I and D // M303 - PID relay autotune S<temperature> sets the target temperature. (default target temperature // = 150C) // M400 - Finish all moves // M500 - stores paramters in EEPROM // M501 - reads parameters from EEPROM (if you need to reset them after you changed them // temporarily). // M502 - reverts to the default "factory settings". You still need to store them in EEPROM // afterwards if you want to. // M503 - Print settings // Debug feature / Testing the PID for Hotend // M601 - Show Temp jitter from Extruder (min / max value from Hotend Temperature while printing) // M602 - Reset Temp jitter from Extruder (min / max val) --> Don't use it while Printing // M603 - Show Free Ram public class SprinterDriver extends AbstractSerialPortDriver implements Runnable { /* * @Attribute(required=false) private int vacpumpPin; * * @Attribute(required=false) private boolean invertVacpump; * */ // private static final double minimumRequiredVersion = 0.75; @Attribute(required = false) private int vacuumPin = 31; @Attribute(required = false) private boolean invertVacuum; @Attribute(required = false) private int actuatorPin = 33; @Attribute(required = false) private boolean invertActuator; @Attribute(required = false) private boolean homeX; @Attribute(required = false) private boolean homeY; @Attribute(required = false) private boolean homeZ; @Attribute(required = false) private boolean homeC; @Attribute(required = false) private double feedRateMmPerMinute = 5000; private double x, y, z, c; private Thread readerThread; private boolean disconnectRequested; private Object commandLock = new Object(); private boolean connected; // private double connectedVersion; private Queue<String> responseQueue = new ConcurrentLinkedQueue<>(); public SprinterDriver() {} @Override public void home(ReferenceHead head) throws Exception { if (homeX || homeY || homeZ || homeC) { sendCommand(String.format("G28 %s %s %s %s", homeX ? "X" : "", homeY ? "Y" : "", homeZ ? "Z" : "", homeC ? "E" : "")); dwell(); } else { throw new Exception( "No homing axes defined. See the homeX, homeY, homeZ and homeC parameters."); } // Reset all axes to 0. This is required so that the Head and Driver // stay in sync. sendCommand("G92 X0 Y0 Z0 E0"); x = y = z = c = 0; } @Override public void moveTo(ReferenceHeadMountable hm, Location location, double speed) throws Exception { location = location.subtract(hm.getHeadOffsets()); location = location.convertToUnits(LengthUnit.Millimeters); double x = location.getX(); double y = location.getY(); double z = location.getZ(); double c = location.getRotation(); StringBuffer sb = new StringBuffer(); if (!Double.isNaN(x) && x != this.x) { sb.append(String.format(Locale.US, "X%2.4f ", x)); } if (!Double.isNaN(y) && y != this.y) { sb.append(String.format(Locale.US, "Y%2.4f ", y)); } if (!Double.isNaN(z) && z != this.z) { sb.append(String.format(Locale.US, "Z%2.4f ", z)); } if (!Double.isNaN(c) && c != this.c) { sb.append(String.format(Locale.US, "E%2.4f ", c)); } if (sb.length() > 0) { sb.append(String.format(Locale.US, "F%2.4f ", feedRateMmPerMinute * speed)); sendCommand("G1" + sb.toString()); dwell(); } if (!Double.isNaN(x)) { this.x = x; } if (!Double.isNaN(y)) { this.y = y; } if (!Double.isNaN(z)) { this.z = z; } if (!Double.isNaN(c)) { this.c = c; } } @Override public Location getLocation(ReferenceHeadMountable hm) { return new Location(LengthUnit.Millimeters, x, y, z, c).add(hm.getHeadOffsets()); } @Override public void pick(ReferenceNozzle nozzle) throws Exception { sendCommand(String.format("M42 P%d S%d", vacuumPin, invertVacuum ? 0 : 255)); dwell(); } @Override public void place(ReferenceNozzle nozzle) throws Exception { sendCommand(String.format("M42 P%d S%d", vacuumPin, invertVacuum ? 255 : 0)); dwell(); } @Override public void actuate(ReferenceActuator actuator, boolean on) throws Exception { if (actuator == null || actuator.getIndex() == 0) { sendCommand(String.format("M42 P%d S%d", actuatorPin, on ^ invertActuator ? 255 : 0)); dwell(); } } @Override public void actuate(ReferenceActuator actuator, double value) throws Exception { // TODO Auto-generated method stub } @Override public void setEnabled(boolean enabled) throws Exception { if (enabled && !connected) { connect(); } if (connected) { sendCommand(String.format("M84 %s", enabled ? "T" : "")); place(null); actuate(null, false); } } public synchronized void connect() throws Exception { super.connect(); /** * Connection process notes: * * On some platforms, as soon as we open the serial port it will reset Sprinter and we'll * start getting some data. On others, Sprinter may already be running and we will get * nothing on connect. */ List<String> responses; synchronized (commandLock) { // Start the reader thread with the commandLock held. This will // keep the thread from quickly parsing any responses messages // and notifying before we get a chance to wait. readerThread = new Thread(this); readerThread.start(); // Wait up to 3 seconds for Sprinter to say Hi // If we get anything at this point it will have been the settings // dump that is sent after reset. responses = sendCommand(null, 3000); } processConnectionResponses(responses); for (int i = 0; i < 5 && !connected; i++) { responses = sendCommand("M115", 5000); processConnectionResponses(responses); } if (!connected) { throw new Exception( // String.format("Unable to receive connection response from Sprinter. Check // your port and baud rate, and that you are running at least version %f of // Sprinter", // minimumRequiredVersion)); String.format( "Unable to receive connection response from Sprinter. Check your port and baud rate, and that you are running the latest version of Sprinter.")); } // TODO: Version Info // if (connectedVersion < minimumRequiredVersion) { // throw new Error(String.format("This driver requires Sprinter version %.2f or higher. You // are running version %.2f", minimumRequiredVersion, connectedVersion)); // } // We are connected to at least the minimum required version now // So perform some setup // Turn off the stepper drivers setEnabled(false); // Reset all axes to 0, in case the firmware was not reset on // connect. sendCommand("G92 X0 Y0 Z0 E0"); } private void processConnectionResponses(List<String> responses) { for (String response : responses) { if (response.startsWith("FIRMWARE_NAME:") || response.equals("Sprinter")) { // String[] versionComponents = response.split(" "); // connectedVersion = Double.parseDouble(versionComponents[2]); connected = true; // Logger.debug(String.format("Connected to Sprinter Version: %.2f", // connectedVersion)); Logger.debug(String.format("Connected to Sprinter.")); } } } public synchronized void disconnect() { disconnectRequested = true; connected = false; try { if (readerThread != null && readerThread.isAlive()) { readerThread.join(); } } catch (Exception e) { Logger.error("disconnect()", e); } try { super.disconnect(); } catch (Exception e) { Logger.error("disconnect()", e); } disconnectRequested = false; } protected List<String> sendCommand(String command) throws Exception { return sendCommand(command, -1); } private List<String> sendCommand(String command, long timeout) throws Exception { synchronized (commandLock) { if (command != null) { Logger.debug("> " + command); output.write(command.getBytes()); output.write("\n".getBytes()); } long t = System.currentTimeMillis(); if (timeout == -1) { commandLock.wait(); } else { commandLock.wait(timeout); } Logger.debug("Waited {} ms for command to return.", (System.currentTimeMillis() - t)); } List<String> responses = drainResponseQueue(); return responses; } public void run() { while (!disconnectRequested) { String line; try { line = readLine().trim(); } catch (TimeoutException ex) { continue; } catch (IOException e) { Logger.error("Read error", e); return; } line = line.trim(); Logger.debug("< " + line); responseQueue.offer(line); // We have a special case of accepting "start" when we are not // connected because Sprinter does not send an "ok" when it starts // up. if (line.equals("ok") || line.startsWith("error: ") || (!connected && line.equals("start"))) { // This is the end of processing for a command synchronized (commandLock) { commandLock.notify(); } } } } /** * Causes Sprinter to block until all commands are complete. * * @throws Exception */ protected void dwell() throws Exception { sendCommand("M400"); } private List<String> drainResponseQueue() { List<String> responses = new ArrayList<>(); String response; while ((response = responseQueue.poll()) != null) { responses.add(response); } return responses; } @Override public Wizard getConfigurationWizard() { return new AbstractSerialPortDriverConfigurationWizard(this); } @Override public String getPropertySheetHolderTitle() { return getClass().getSimpleName(); } @Override public PropertySheetHolder[] getChildPropertySheetHolders() { // TODO Auto-generated method stub return null; } @Override public PropertySheet[] getPropertySheets() { return new PropertySheet[] {new PropertySheetWizardAdapter(getConfigurationWizard())}; } @Override public Action[] getPropertySheetHolderActions() { // TODO Auto-generated method stub return null; } }