diff --git a/README b/README index a57906d4a327793100d5dfd82a7ed27c50938d23..5c9e04703799dd5023b7543b4140b05ad34c565f 100644 --- a/README +++ b/README @@ -1,8 +1,11 @@ This is a library intended to provide suppport for Lasercutters on any platform. -Currently it supports moste Epilog Lasers -and the current LAOS board. (www.laoslaser.org). +Currently it supports most Epilog Lasers, +the current LAOS board. (www.laoslaser.org), +the SmoothieBoard (www.smoothieware.org), +generic GRBL based boards, +and some untested work-in-progress drivers like the Roland iModela and the Lasersaur. It was created for VisiCut (http://visicut.org) but you are invited to use it for your own programs. diff --git a/lib/corn-httpclient-1.0.12.jar b/lib/corn-httpclient-1.0.12.jar new file mode 100644 index 0000000000000000000000000000000000000000..84f0b546db05da5a14dbe7c293757ad890fadf01 Binary files /dev/null and b/lib/corn-httpclient-1.0.12.jar differ diff --git a/nbproject/project.properties b/nbproject/project.properties index 7a20d16d240688e687da3d1cb1d61fdc64c0c63c..72619c653fade49c0e9b6c73811c01b443bf092c 100644 --- a/nbproject/project.properties +++ b/nbproject/project.properties @@ -44,6 +44,7 @@ dist.javadoc.dir=${dist.dir}/javadoc endorsed.classpath= excludes= file.reference.commons-net-3.1.jar=lib/commons-net-3.1.jar +file.reference.corn-httpclient-1.0.12.jar=lib/corn-httpclient-1.0.12.jar file.reference.GLPKSolverPack.jar=lib/GLPKSolverPack.jar file.reference.jna-4.0.0.jar=lib/jna-4.0.0.jar file.reference.js.jar=lib/js.jar @@ -59,7 +60,8 @@ javac.classpath=\ ${file.reference.GLPKSolverPack.jar}:\ ${file.reference.SCPSolver.jar}:\ ${file.reference.jna-4.0.0.jar}:\ - ${file.reference.purejavacomm-0.0.22.jar} + ${file.reference.purejavacomm-0.0.22.jar}:\ + ${file.reference.corn-httpclient-1.0.12.jar} # Space-separated list of extra javac options javac.compilerargs= javac.deprecation=false diff --git a/src/com/t_oster/liblasercut/FloatPowerSpeedFocusProperty.java b/src/com/t_oster/liblasercut/FloatPowerSpeedFocusProperty.java new file mode 100644 index 0000000000000000000000000000000000000000..d8ae027f3df0eed8f3c464c231e99c35726fbb89 --- /dev/null +++ b/src/com/t_oster/liblasercut/FloatPowerSpeedFocusProperty.java @@ -0,0 +1,237 @@ +/** + * This file is part of LibLaserCut. + * Copyright (C) 2011 - 2014 Thomas Oster <mail@thomas-oster.de> + * + * LibLaserCut is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * LibLaserCut 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with LibLaserCut. If not, see <http://www.gnu.org/licenses/>. + * + **/ +package com.t_oster.liblasercut; + +/** + * The LaserProperty holds all the parameters for parts of the LaserJob. + * The Frequency value is ignored for Engraving operations + * + * @author oster + */ +public class FloatPowerSpeedFocusProperty implements LaserProperty +{ + + private float power = 20; + private float speed = 100; + private float focus = 0; + + public FloatPowerSpeedFocusProperty() + { + } + + /** + * Sets the Laserpower. Valid values are from 0 to 100. + * In 3d-Raster mode, the intensity is scaled to this power setting + * @param power + */ + public void setPower(float power) + { + power = power < 0 ? 0 : power; + power = power > 100 ? 100 : power; + this.power = power; + } + + public float getPower() + { + return power; + } + + /** + * Sets the speed for the Laser. Valid values is from 0 to 100 + * @param speed + */ + public void setSpeed(float speed) + { + speed = speed < 0 ? 0 : speed; + speed = speed > 100 ? 100 : speed; + this.speed = speed; + } + + public float getSpeed() + { + return speed; + } + + /** + * Sets the Focus aka moves the Z axis. Values are given in mm. + * Positive values move the Z axis down aka makes the distance between + * laser and object bigger. + * The possible range depends on the LaserCutter, so wrong setting + * may result in IllegalJobExceptions + * @param focus the relative Distance from object to Laser in mm + */ + public void setFocus(float focus) + { + this.focus = focus; + } + + /** + * Returns the relative (to the distance at starting the job) distance + * between laser and object in mm/10s + */ + public float getFocus() + { + return this.focus; + } + + @Override + public FloatPowerSpeedFocusProperty clone() + { + FloatPowerSpeedFocusProperty p = new FloatPowerSpeedFocusProperty(); + p.focus = focus; + p.power = power; + p.speed = speed; + return p; + } + + private static String[] propertyNames = new String[]{"power", "speed", "focus"}; + + @Override + public String[] getPropertyKeys() + { + return propertyNames; + } + + @Override + public Object getProperty(String name) + { + if ("power".equals(name)) + { + return (Float) this.getPower(); + } + else if ("speed".equals(name)) + { + return (Float) this.getSpeed(); + } + else if ("focus".equals(name)) + { + return (Float) this.getFocus(); + } + return null; + } + + @Override + public void setProperty(String name, Object value) + { + if ("power".equals(name)) + { + this.setPower((Float) value); + } + else if ("speed".equals(name)) + { + this.setSpeed((Float) value); + } + else if ("focus".equals(name)) + { + this.setFocus((Float) value); + } + else + { + throw new IllegalArgumentException("Unknown setting '"+name+"'"); + } + } + + @Override + public Object getMinimumValue(String name) + { + if ("power".equals(name)) + { + return (Float) 0f; + } + else if ("speed".equals(name)) + { + return (Float) 0f; + } + else if ("focus".equals(name)) + { + return null; + } + else if ("frequency".equals(name)) + { + return null; + } + else + { + throw new IllegalArgumentException("Unknown setting '"+name+"'"); + } + } + + @Override + public Object getMaximumValue(String name) + { + if ("power".equals(name)) + { + return (Float) 100f; + } + else if ("speed".equals(name)) + { + return (Float) 100f; + } + else if ("focus".equals(name)) + { + return null; + } + else if ("frequency".equals(name)) + { + return null; + } + else + { + throw new IllegalArgumentException("Unknown setting '"+name+"'"); + } + } + + @Override + public Object[] getPossibleValues(String name) + { + return null; + } + + @Override + public boolean equals(Object obj) { + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + final FloatPowerSpeedFocusProperty other = (FloatPowerSpeedFocusProperty) obj; + if (Float.floatToIntBits(this.power) != Float.floatToIntBits(other.power)) { + return false; + } + if (Float.floatToIntBits(this.speed) != Float.floatToIntBits(other.speed)) { + return false; + } + if (Float.floatToIntBits(this.focus) != Float.floatToIntBits(other.focus)) { + return false; + } + return true; + } + + @Override + public int hashCode() { + int hash = 7; + hash = 67 * hash + Float.floatToIntBits(this.power); + hash = 67 * hash + Float.floatToIntBits(this.speed); + hash = 67 * hash + Float.floatToIntBits(this.focus); + return hash; + } + + +} diff --git a/src/com/t_oster/liblasercut/LaserCutter.java b/src/com/t_oster/liblasercut/LaserCutter.java index e994c893dbc49dae307ce8b188d40840761b8f96..9fdbe43046a75881514cbdfbd6c46078172eed16 100644 --- a/src/com/t_oster/liblasercut/LaserCutter.java +++ b/src/com/t_oster/liblasercut/LaserCutter.java @@ -22,6 +22,7 @@ */ package com.t_oster.liblasercut; +import com.t_oster.liblasercut.platform.Point; import com.t_oster.liblasercut.platform.Util; import java.util.LinkedList; import java.util.List; @@ -173,6 +174,118 @@ public abstract class LaserCutter implements Cloneable, Customizable { public abstract String getModelName(); + protected VectorPart convertRasterToVectorPart(RasterPart rp, LaserProperty blackPixelProperty, LaserProperty whitePixelProperty, double resolution, boolean unidirectional) + { + boolean dirRight = true; + Point rasterStart = rp.getRasterStart(); + VectorPart result = new VectorPart(blackPixelProperty, resolution); + for (int line = 0; line < rp.getRasterHeight(); line++) + { + Point lineStart = rasterStart.clone(); + lineStart.y += line; + //Convert BlackWhite line into line of 0 and 255 bytes + BlackWhiteRaster bwr = rp.image; + List<Byte> bytes = new LinkedList<Byte>(); + boolean lookForStart = true; + for (int x = 0; x < bwr.getWidth(); x++) + { + if (lookForStart) + { + if (bwr.isBlack(x, line)) + { + lookForStart = false; + bytes.add((byte) 255); + } + else + { + lineStart.x += 1; + } + } + else + { + bytes.add(bwr.isBlack(x, line) ? (byte) 255 : (byte) 0); + } + } + //remove trailing zeroes + while (bytes.size() > 0 && bytes.get(bytes.size() - 1) == 0) + { + bytes.remove(bytes.size() - 1); + } + if (bytes.size() > 0) + { + if (dirRight) + { + //move to the first nonempyt point of the line + result.moveto(lineStart.x, lineStart.y); + byte old = bytes.get(0); + for (int pix = 0; pix < bytes.size(); pix++) + { + if (bytes.get(pix) != old) + { + if (old == 0) + {//beginning of "black" segment -> move with 0 power + result.setProperty(whitePixelProperty); + result.lineto(lineStart.x + pix, lineStart.y); + } + else + {//end of "black" segment -> move with power to pixel before + result.setProperty(blackPixelProperty); + result.lineto(lineStart.x + pix - 1, lineStart.y); + } + old = bytes.get(pix); + } + } + result.setProperty(blackPixelProperty); + result.lineto(lineStart.x + bytes.size() - 1, lineStart.y); + } + else + { + //move to the last nonempty point of the line + result.moveto(lineStart.x + bytes.size() - 1, lineStart.y); + byte old = bytes.get(bytes.size() - 1); + for (int pix = bytes.size() - 1; pix >= 0; pix--) + { + if (bytes.get(pix) != old || pix == 0) + { + if (old == 0) + { + result.setProperty(whitePixelProperty); + result.lineto(lineStart.x + pix, lineStart.y); + } + else + { + result.setProperty(blackPixelProperty); + result.lineto(lineStart.x + pix + 1, lineStart.y); + } + old = bytes.get(pix); + } + } + //last pixel is always black (white pixels are stripped before) + result.setProperty(blackPixelProperty); + result.lineto(lineStart.x, lineStart.y); + } + } + if (!unidirectional) + { + dirRight = !dirRight; + } + } + return result; + } + + /** + * Intented for use in the clone mehtod. Copies all properties + * of that to this + * @param that + */ + protected void copyProperties(LaserCutter that) + { + for (String prop : that.getPropertyKeys()) + { + setProperty(prop, that.getProperty(prop)); + } + } + @Override public abstract LaserCutter clone(); } diff --git a/src/com/t_oster/liblasercut/LibInfo.java b/src/com/t_oster/liblasercut/LibInfo.java index ec2f5705ddf12053f86c90ee3a80bd430668f1e9..2b7bcb40bcd8f3f25aafb7cb4b44aea944eebc86 100644 --- a/src/com/t_oster/liblasercut/LibInfo.java +++ b/src/com/t_oster/liblasercut/LibInfo.java @@ -45,7 +45,10 @@ public class LibInfo Dummy.class, IModelaMill.class, SampleDriver.class, - ExportSVG.class + ExportSVG.class, + GenericGcodeDriver.class, + SmoothieBoard.class, + Marlin.class }; } } diff --git a/src/com/t_oster/liblasercut/drivers/GenericGcodeDriver.java b/src/com/t_oster/liblasercut/drivers/GenericGcodeDriver.java new file mode 100644 index 0000000000000000000000000000000000000000..a41cc9c9c3397de55eb2cb45a187a05a5fc5bb58 --- /dev/null +++ b/src/com/t_oster/liblasercut/drivers/GenericGcodeDriver.java @@ -0,0 +1,828 @@ +/** + * This file is part of LibLaserCut. + * Copyright (C) 2011 - 2014 Thomas Oster <mail@thomas-oster.de> + * + * LibLaserCut is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * LibLaserCut 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with LibLaserCut. If not, see <http://www.gnu.org/licenses/>. + * + **/ +package com.t_oster.liblasercut.drivers; + +import com.t_oster.liblasercut.*; +import com.t_oster.liblasercut.platform.Util; +import java.io.BufferedReader; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStreamReader; +import java.io.PrintStream; +import java.io.UnsupportedEncodingException; +import java.net.InetSocketAddress; +import java.net.Socket; +import java.net.URI; +import java.net.URISyntaxException; +import java.net.URL; +import purejavacomm.*; +import java.util.*; +import net.sf.corn.httpclient.HttpClient; +import net.sf.corn.httpclient.HttpResponse; + +/** + * This class implements a driver for a generic GRBL GCode Lasercutter. + * It should contain all possible options and is inteded to be the superclass + * for e.g. the SmoothieBoard and the Lasersaur driver. + * + * @author Thomas Oster <thomas.oster@rwth-aachen.de> + */ +public class GenericGcodeDriver extends LaserCutter { + + protected static final String SETTING_HOST = "IP/Hostname"; + protected static final String SETTING_COMPORT = "COM Port"; + protected static final String SETTING_BAUDRATE = "Baud Rate (Serial)"; + protected static final String SETTING_BEDWIDTH = "Laserbed width"; + protected static final String SETTING_BEDHEIGHT = "Laserbed height"; + protected static final String SETTING_FLIP_X = "Flip X Axis"; + protected static final String SETTING_FLIP_Y = "Flip Y Axis"; + protected static final String SETTING_HTTP_UPLOAD_URL = "HTTP Upload URL"; + protected static final String SETTING_LINEEND = "Lineend (CR,LF,CRLF)"; + protected static final String SETTING_MAX_SPEED = "Max speed (in mm/min)"; + protected static final String SETTING_TRAVEL_SPEED = "Travel (non laser moves) speed (in mm/min)"; + protected static final String SETTING_PRE_JOB_GCODE = "Pre-Job GCode (comma separated)"; + protected static final String SETTING_POST_JOB_GCODE = "Post-Job GCode (comma separated)"; + protected static final String SETTING_RESOLUTIONS = "Supported DPI (comma separated)"; + protected static final String SETTING_IDENTIFICATION_STRING = "Board Identification String (startsWith)"; + protected static final String SETTING_WAIT_FOR_OK = "Wait for OK after each line (interactive mode)"; + protected static final String SETTING_INIT_DELAY = "Seconds to wait for board reset (Serial)"; + protected static final String SETTING_SERIAL_TIMEOUT = "Milliseconds to wait for response"; + + private String lineend = "LF"; + + public String getLineend() + { + return lineend; + } + + public void setLineend(String lineend) + { + this.lineend = lineend; + } + + protected String LINEEND() + { + return getLineend() + .replace("LF", "\n") + .replace("CR", "\r") + .replace("\\r", "\r") + .replace("\\n", "\n"); + } + + protected int baudRate = 115200; + + public int getBaudRate() + { + return baudRate; + } + + public void setBaudRate(int baudRate) + { + this.baudRate = baudRate; + } + + protected boolean flipXaxis = false; + + public boolean isFlipXaxis() + { + return flipXaxis; + } + + public void setFlipXaxis(boolean flipXaxis) + { + this.flipXaxis = flipXaxis; + } + + protected boolean flipYaxis = false; + + public boolean isFlipYaxis() + { + return flipYaxis; + } + + public void setFlipYaxis(boolean flipYaxis) + { + this.flipYaxis = flipYaxis; + } + + protected String httpUploadUrl = "http://10.10.10.100/upload"; + + public String getHttpUploadUrl() + { + return httpUploadUrl; + } + + public void setHttpUploadUrl(String httpUploadUrl) + { + this.httpUploadUrl = httpUploadUrl; + } + + protected String supportedResolutions = "100,500,1000"; + + public String getSupportedResolutions() + { + return supportedResolutions; + } + + public void setSupportedResolutions(String supportedResolutions) + { + this.resolutions = null; + this.supportedResolutions = supportedResolutions; + } + + protected boolean waitForOKafterEachLine = true; + + public boolean isWaitForOKafterEachLine() + { + return waitForOKafterEachLine; + } + + public void setWaitForOKafterEachLine(boolean waitForOKafterEachLine) + { + this.waitForOKafterEachLine = waitForOKafterEachLine; + } + + public String getIdentificationLine() + { + return identificationLine; + } + + public void setIdentificationLine(String identificationLine) + { + this.identificationLine = identificationLine; + } + + protected String preJobGcode = "G21,G90"; + + public String getPreJobGcode() + { + return preJobGcode; + } + + public void setPreJobGcode(String preJobGcode) + { + this.preJobGcode = preJobGcode; + } + + protected String postJobGcode = "G0 X0 Y0"; + + public String getPostJobGcode() + { + return postJobGcode; + } + + public void setPostJobGcode(String postJobGcode) + { + this.postJobGcode = postJobGcode; + } + + protected int serialTimeout= 15000; + + public int getSerialTimeout() + { + return serialTimeout; + } + + public void setSerialTimeout(int serialTimeout) + { + this.serialTimeout = serialTimeout; + } + + /** + * What is expected to be received after serial/telnet connection + * Used e.g. for auto-detecting the serial port. + */ + protected String identificationLine = "Grbl"; + + @Override + public String getModelName() { + return "Generic GRBL GCode Driver"; + } + + /** + * Time to wait before firsts reads of serial port. + * See autoreset feature on arduinos. + */ + protected int initDelay = 5; + + public int getInitDelay() + { + return initDelay; + } + + public void setInitDelay(int initDelay) + { + this.initDelay = initDelay; + } + + protected String host = "10.10.10.222"; + + public String getHost() + { + return host; + } + + public void setHost(String host) + { + this.host = host; + } + + protected String comport = "auto"; + + public String getComport() + { + return comport; + } + + public void setComport(String comport) + { + this.comport = comport; + } + + protected double max_speed = 20*60; + + public double getMax_speed() + { + return max_speed; + } + + public void setMax_speed(double max_speed) + { + this.max_speed = max_speed; + } + + protected double travel_speed = 60*60; + + public double getTravel_speed() + { + return travel_speed; + } + + public void setTravel_speed(double travel_speed) + { + this.travel_speed = travel_speed; + } + + @Override + /** + * We do not support Frequency atm, so we return power,speed and focus + */ + public LaserProperty getLaserPropertyForVectorPart() { + return new FloatPowerSpeedFocusProperty(); + } + + @Override + public LaserProperty getLaserPropertyForRaster3dPart() + { + return new FloatPowerSpeedFocusProperty(); + } + + @Override + public LaserProperty getLaserPropertyForRasterPart() + { + return new FloatPowerSpeedFocusProperty(); + } + + protected void writeVectorGCode(VectorPart vp, double resolution) throws UnsupportedEncodingException, IOException { + for (VectorCommand cmd : vp.getCommandList()) { + switch (cmd.getType()) { + case MOVETO: + int x = cmd.getX(); + int y = cmd.getY(); + move(out, x, y, resolution); + break; + case LINETO: + x = cmd.getX(); + y = cmd.getY(); + line(out, x, y, resolution); + break; + case SETPROPERTY: + FloatPowerSpeedFocusProperty p = (FloatPowerSpeedFocusProperty) cmd.getProperty(); + setPower(p.getPower()); + setSpeed(p.getSpeed()); + setFocus(out, p.getFocus(), resolution); + break; + } + } + } + private double currentPower = -1; + private double currentSpeed = -1; + private double nextPower = -1; + private double nextSpeed = -1; + private double currentFocus = 0; + + protected void setSpeed(double speedInPercent) { + nextSpeed = speedInPercent; + } + + protected void setPower(double powerInPercent) { + nextPower = powerInPercent; + } + + protected void setFocus(PrintStream out, double focus, double resolution) throws IOException { + if (currentFocus != focus) + { + sendLine("G0 Z%f", Util.px2mm(focus, resolution)); + currentFocus = focus; + } + } + + protected void move(PrintStream out, double x, double y, double resolution) throws IOException { + x = isFlipXaxis() ? getBedWidth() - Util.px2mm(x, resolution) : Util.px2mm(x, resolution); + y = isFlipYaxis() ? getBedHeight() - Util.px2mm(y, resolution) : Util.px2mm(y, resolution); + currentSpeed = getTravel_speed(); + sendLine("G0 X%f Y%f F%d", x, y, (int) (travel_speed)); + } + + protected void line(PrintStream out, double x, double y, double resolution) throws IOException { + x = isFlipXaxis() ? getBedWidth() - Util.px2mm(x, resolution) : Util.px2mm(x, resolution); + y = isFlipYaxis() ? getBedHeight() - Util.px2mm(y, resolution) : Util.px2mm(y, resolution); + String append = ""; + if (nextPower != currentPower) + { + append += String.format(Locale.US, " S%f", nextPower/100.0); + currentPower = nextPower; + } + if (nextSpeed != currentSpeed) + { + append += String.format(Locale.US, " F%d", (int) (max_speed*nextSpeed/100.0)); + currentSpeed = nextSpeed; + } + sendLine("G1 X%f Y%f"+append, x, y); + } + + private void writeInitializationCode() throws IOException { + if (preJobGcode != null) + { + for (String line : preJobGcode.split(",")) + { + sendLine(line); + } + } + } + + private void writeShutdownCode() throws IOException { + if (postJobGcode != null) + { + for (String line : postJobGcode.split(",")) + { + sendLine(line); + } + } + } + + private BufferedReader in; + private PrintStream out; + private Socket socket; + private CommPort port; + private CommPortIdentifier portIdentifier; + + protected void sendLine(String text, Object... parameters) throws IOException + { + out.format(text+LINEEND(), parameters); + //TODO: Remove + System.out.format("> "+text+LINEEND(), parameters); + out.flush(); + if (isWaitForOKafterEachLine()) + { + String line = waitForLine(); + if (!"ok".equals(line)) + { + throw new IOException("Lasercutter did not respond 'ok', but '"+line+"'instead."); + } + } + } + + protected void http_upload(URI url, String data, String filename) throws IOException + { + HttpClient client = new HttpClient(url); + client.putAdditionalRequestProperty("X-Filename", filename); + HttpResponse response = client.sendData(HttpClient.HTTP_METHOD.POST, data); + if (response == null || response.hasError()) + { + throw new IOException("Error during POST Request"); + } + System.out.println("Response: "+response.toString());//TODO: Remove + } + + protected String waitForLine() throws IOException + { + String line = ""; + while ("".equals(line)) + {//skip empty lines + line = in.readLine(); + } + System.out.println("< "+line);//TODO: remove + return line; + } + + /** + * Waits for the Identification line and returns null if it's allright + * Otherwise it returns the wrong line + * @return + * @throws IOException + */ + protected String waitForIdentificationLine() throws IOException + { + if (getIdentificationLine() != null && getIdentificationLine().length() > 0) + { + String line = ""; + for (int trials = 3; trials > 0; trials--) + { + line = waitForLine(); + if (line.startsWith(getIdentificationLine())) + { + return null; + } + } + return line; + } + return null; + } + + protected String connect_serial(CommPortIdentifier i, ProgressListener pl) throws PortInUseException, IOException, UnsupportedCommOperationException + { + pl.taskChanged(this, "opening '"+i.getName()+"'"); + if (i.getPortType() == CommPortIdentifier.PORT_SERIAL) + { + try + { + port = i.open("VisiCut", 1000); + try + { + port.enableReceiveTimeout(getSerialTimeout()); + } + catch (UnsupportedCommOperationException e) + { + System.err.println("Serial timeout not supported. Driver may hang if device does not respond properly."); + } + if (this.getBaudRate() > 0 && port instanceof SerialPort) + { + SerialPort sp = (SerialPort) port; + sp.setSerialPortParams(getBaudRate(), 8, 1, 0); + } + out = new PrintStream(port.getOutputStream(), true, "US-ASCII"); + in = new BufferedReader(new InputStreamReader(port.getInputStream())); + // Wait 5 seconds since GRBL is long to wake up.. + for (int rest = getInitDelay(); rest > 0; rest--) { + pl.taskChanged(this, String.format("Waiting %ds", rest)); + try + { + Thread.sleep(1000); + } + catch(InterruptedException ex) + { + Thread.currentThread().interrupt(); + } + } + if (waitForIdentificationLine() != null) + { + in.close(); + out.close(); + port.close(); + return "Does not seem to be a "+getModelName()+" on "+i.getName(); + } + portIdentifier = i; + return null; + } + catch (PortInUseException e) + { + return "Port in use "+i.getName(); + } + catch (IOException e) + { + return "IO Error "+i.getName(); + } + catch (PureJavaIllegalStateException e) + { + return "Could not open "+i.getName(); + } + } + else + { + return "Not a serial Port "+comport; + } + } + /** + * Used to buffer the file before uploading via http + */ + private ByteArrayOutputStream outputBuffer; + protected void connect(ProgressListener pl) throws IOException, PortInUseException, NoSuchPortException, UnsupportedCommOperationException + { + outputBuffer = null; + if (getHost() != null && getHost().length() > 0) + { + socket = new Socket(); + socket.connect(new InetSocketAddress(getHost(), 23), 1000); + in = new BufferedReader(new InputStreamReader(socket.getInputStream())); + out = new PrintStream(socket.getOutputStream(), true, "US-ASCII"); + String line = waitForIdentificationLine(); + if (line != null) + { + in.close(); + out.close(); + throw new IOException("Wrong identification Line: "+line+"\n instead of "+getIdentificationLine()); + } + } + else if (getComport() != null && !getComport().equals("")) + { + String error = "No serial port found"; + if (portIdentifier == null && !getComport().equals("auto")) + { + portIdentifier = CommPortIdentifier.getPortIdentifier(getComport()); + } + + if (portIdentifier != null) + {//use port identifier we had last time + error = connect_serial(portIdentifier, pl); + } + else + { + Enumeration<CommPortIdentifier> e = CommPortIdentifier.getPortIdentifiers(); + while (e.hasMoreElements()) + { + CommPortIdentifier i = e.nextElement(); + if (i.getPortType() == CommPortIdentifier.PORT_SERIAL) + { + error = connect_serial(i, pl); + if (error == null) + { + break; + } + } + } + } + if (error != null) + { + throw new IOException(error); + } + } + else if (getHttpUploadUrl() != null && getHttpUploadUrl().length() > 0) + { + outputBuffer = new ByteArrayOutputStream(); + out = new PrintStream(outputBuffer); + setWaitForOKafterEachLine(false); + in = null; + } + else + { + throw new IOException("Either COM Port or IP/Host has to be set"); + } + } + + protected void disconnect(String jobname) throws IOException, URISyntaxException + { + if (outputBuffer != null) + { + out.close(); + http_upload(new URI(getHttpUploadUrl()), outputBuffer.toString("UTF-8"), jobname); + } + else + { + in.close(); + out.close(); + if (this.socket != null) + { + socket.close(); + socket = null; + } + else if (this.port != null) + { + this.port.close(); + this.port = null; + } + } + + } + + @Override + public void sendJob(LaserJob job, ProgressListener pl, List<String> warnings) throws IllegalJobException, Exception { + pl.progressChanged(this, 0); + this.currentPower = -1; + this.currentSpeed = -1; + + pl.taskChanged(this, "checking job"); + checkJob(job); + job.applyStartPoint(); + pl.taskChanged(this, "connecting..."); + connect(pl); + pl.taskChanged(this, "sending"); + writeInitializationCode(); + pl.progressChanged(this, 20); + int i = 0; + int max = job.getParts().size(); + for (JobPart p : job.getParts()) + { + if (p instanceof RasterPart) + { + RasterPart rp = (RasterPart) p; + LaserProperty black = rp.getLaserProperty(); + LaserProperty white = black.clone(); + white.setProperty("power", 0.0f); + p = convertRasterToVectorPart((RasterPart) p, black, white, p.getDPI(), false); + } + if (p instanceof VectorPart) + { + //TODO: in direct mode use progress listener to indicate progress + //of individual job + writeVectorGCode((VectorPart) p, p.getDPI()); + } + i++; + pl.progressChanged(this, 20 + (int) (i*(double) 60/max)); + } + writeShutdownCode(); + disconnect(job.getName()+".gcode"); + pl.taskChanged(this, "sent."); + pl.progressChanged(this, 100); + } + private List<Double> resolutions; + + @Override + public List<Double> getResolutions() { + if (resolutions == null) { + resolutions = new LinkedList<Double>(); + for (String s : getSupportedResolutions().split(",")) + { + resolutions.add(Double.parseDouble(s)); + } + } + return resolutions; + } + protected double bedWidth = 250; + + /** + * Get the value of bedWidth + * + * @return the value of bedWidth + */ + @Override + public double getBedWidth() { + return bedWidth; + } + + /** + * Set the value of bedWidth + * + * @param bedWidth new value of bedWidth + */ + public void setBedWidth(double bedWidth) { + this.bedWidth = bedWidth; + } + protected double bedHeight = 280; + + /** + * Get the value of bedHeight + * + * @return the value of bedHeight + */ + @Override + public double getBedHeight() { + return bedHeight; + } + + /** + * Set the value of bedHeight + * + * @param bedHeight new value of bedHeight + */ + public void setBedHeight(double bedHeight) { + this.bedHeight = bedHeight; + } + + private static String[] settingAttributes = new String[]{ + SETTING_BAUDRATE, + SETTING_BEDWIDTH, + SETTING_BEDHEIGHT, + SETTING_COMPORT, + SETTING_FLIP_X, + SETTING_FLIP_Y, + SETTING_HOST, + SETTING_HTTP_UPLOAD_URL, + SETTING_IDENTIFICATION_STRING, + SETTING_INIT_DELAY, + SETTING_LINEEND, + SETTING_MAX_SPEED, + SETTING_TRAVEL_SPEED, + SETTING_PRE_JOB_GCODE, + SETTING_POST_JOB_GCODE, + SETTING_RESOLUTIONS, + SETTING_WAIT_FOR_OK, + SETTING_SERIAL_TIMEOUT + }; + + @Override + public String[] getPropertyKeys() { + return settingAttributes; + } + + @Override + public Object getProperty(String attribute) { + if (SETTING_HOST.equals(attribute)) { + return this.getHost(); + } else if (SETTING_BAUDRATE.equals(attribute)) { + return this.getBaudRate(); + } else if (SETTING_BEDWIDTH.equals(attribute)) { + return this.getBedWidth(); + } else if (SETTING_BEDHEIGHT.equals(attribute)) { + return this.getBedHeight(); + } else if (SETTING_COMPORT.equals(attribute)) { + return this.getComport(); + } else if (SETTING_FLIP_X.equals(attribute)) { + return this.isFlipXaxis(); + } else if (SETTING_FLIP_Y.equals(attribute)) { + return this.isFlipYaxis(); + } else if (SETTING_HOST.equals(attribute)) { + return this.getHost(); + } else if (SETTING_HTTP_UPLOAD_URL.equals(attribute)) { + return this.getHttpUploadUrl(); + } else if (SETTING_IDENTIFICATION_STRING.equals(attribute)) { + return this.getIdentificationLine(); + } else if (SETTING_INIT_DELAY.equals(attribute)) { + return this.getInitDelay(); + } else if (SETTING_LINEEND.equals(attribute)) { + return this.getLineend(); + } else if (SETTING_MAX_SPEED.equals(attribute)) { + return this.getMax_speed(); + } else if (SETTING_TRAVEL_SPEED.equals(attribute)) { + return this.getTravel_speed(); + } else if (SETTING_PRE_JOB_GCODE.equals(attribute)) { + return this.getPreJobGcode(); + } else if (SETTING_POST_JOB_GCODE.equals(attribute)) { + return this.getPostJobGcode(); + } else if (SETTING_RESOLUTIONS.equals(attribute)) { + return this.getSupportedResolutions(); + } else if (SETTING_WAIT_FOR_OK.equals(attribute)) { + return this.isWaitForOKafterEachLine(); + } else if (SETTING_SERIAL_TIMEOUT.equals(attribute)) { + return this.getSerialTimeout(); + } + + return null; + } + + @Override + public void setProperty(String attribute, Object value) { + if (SETTING_HOST.equals(attribute)) { + this.setHost((String) value); + } else if (SETTING_BAUDRATE.equals(attribute)) { + this.setBaudRate((Integer) value); + } else if (SETTING_BEDWIDTH.equals(attribute)) { + this.setBedWidth((Double) value); + } else if (SETTING_BEDHEIGHT.equals(attribute)) { + this.setBedHeight((Double) value); + } else if (SETTING_COMPORT.equals(attribute)) { + this.setComport((String) value); + } else if (SETTING_FLIP_X.equals(attribute)) { + this.setFlipXaxis((Boolean) value); + } else if (SETTING_FLIP_Y.equals(attribute)) { + this.setFlipYaxis((Boolean) value); + } else if (SETTING_HOST.equals(attribute)) { + this.setHost((String) value); + } else if (SETTING_HTTP_UPLOAD_URL.equals(attribute)) { + this.setHttpUploadUrl((String) value); + } else if (SETTING_IDENTIFICATION_STRING.equals(attribute)) { + this.setIdentificationLine((String) value); + } else if (SETTING_INIT_DELAY.equals(attribute)) { + this.setInitDelay((Integer) value); + } else if (SETTING_LINEEND.equals(attribute)) { + this.setLineend((String) value); + } else if (SETTING_MAX_SPEED.equals(attribute)) { + this.setMax_speed((Double) value); + } else if (SETTING_TRAVEL_SPEED.equals(attribute)) { + this.setTravel_speed((Double) value); + } else if (SETTING_PRE_JOB_GCODE.equals(attribute)) { + this.setPreJobGcode((String) value); + } else if (SETTING_POST_JOB_GCODE.equals(attribute)) { + this.setPostJobGcode((String) value); + } else if (SETTING_RESOLUTIONS.equals(attribute)) { + this.setSupportedResolutions((String) value); + } else if (SETTING_WAIT_FOR_OK.equals(attribute)) { + this.setWaitForOKafterEachLine((Boolean) value); + } else if (SETTING_SERIAL_TIMEOUT.equals(attribute)) { + this.setSerialTimeout((Integer) value); + } + } + + @Override + public GenericGcodeDriver clone() { + GenericGcodeDriver clone = new GenericGcodeDriver(); + clone.copyProperties(this); + return clone; + } +} diff --git a/src/com/t_oster/liblasercut/drivers/Marlin.java b/src/com/t_oster/liblasercut/drivers/Marlin.java new file mode 100644 index 0000000000000000000000000000000000000000..f900a7c9cfaed3ae585cbcd8e5514d8213eb1c70 --- /dev/null +++ b/src/com/t_oster/liblasercut/drivers/Marlin.java @@ -0,0 +1,110 @@ +/** + * This file is part of LibLaserCut. + * Copyright (C) 2011 - 2014 Thomas Oster <mail@thomas-oster.de> + * + * LibLaserCut is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * LibLaserCut 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with LibLaserCut. If not, see <http://www.gnu.org/licenses/>. + * + **/ +package com.t_oster.liblasercut.drivers; + +import com.t_oster.liblasercut.drivers.GenericGcodeDriver; +import java.io.IOException; +import java.util.Arrays; +import java.util.LinkedList; +import java.util.List; + +/** + * This class implements a driver for the laser cutter fork of Marlin. + * + * @author quillford + */ +public class Marlin extends GenericGcodeDriver { + + public Marlin() + { + //set some Marlin specific defaults + setIdentificationLine("start"); + setWaitForOKafterEachLine(true); + setBaudRate(115200); + setLineend("CRLF"); + setInitDelay(0); + setPreJobGcode(getPreJobGcode()+",G28 XY,M5"); + setPostJobGcode(getPostJobGcode()+",M5,G28 XY"); + setSerialTimeout(35000); + + //Marlin has no way to upload over the network so remove the upload url text + setHttpUploadUrl(""); + setHost(""); + } + + @Override + public String getIdentificationLine() + { + return("start"); + } + + @Override + public String[] getPropertyKeys() + { + List<String> result = new LinkedList<String>(); + result.addAll(Arrays.asList(super.getPropertyKeys())); + result.remove(GenericGcodeDriver.SETTING_IDENTIFICATION_STRING); + result.remove(GenericGcodeDriver.SETTING_WAIT_FOR_OK); + result.remove(GenericGcodeDriver.SETTING_LINEEND); + result.remove(GenericGcodeDriver.SETTING_INIT_DELAY); + result.remove(GenericGcodeDriver.SETTING_HTTP_UPLOAD_URL); + result.remove(GenericGcodeDriver.SETTING_HOST); + return result.toArray(new String[0]); + } + + /** + * Waits for the Identification line and returns null if it's alright + * Otherwise it returns the wrong line + * @return + * @throws IOException + */ + @Override + protected String waitForIdentificationLine() throws IOException + { + if (getIdentificationLine() != null && getIdentificationLine().length() > 0) + { + String line = waitForLine(); + if (line.startsWith(getIdentificationLine())) + {//we received the identification line ("start"), now we have to skip the rest of Marlin's dump + while(!(waitForLine().startsWith("echo:SD"))) + { + //do nothing and wait until Marlin has dumped all of the settings + } + return null; + } + } + return null; + } + + + @Override + public String getModelName() + { + return "Marlin"; + } + + @Override + public Marlin clone() + { + Marlin clone = new Marlin(); + clone.copyProperties(this); + return clone; + } + +} \ No newline at end of file diff --git a/src/com/t_oster/liblasercut/drivers/SmoothieBoard.java b/src/com/t_oster/liblasercut/drivers/SmoothieBoard.java new file mode 100644 index 0000000000000000000000000000000000000000..6eef4cbcaa268fe6d8fedb2275b3c388e02af9de --- /dev/null +++ b/src/com/t_oster/liblasercut/drivers/SmoothieBoard.java @@ -0,0 +1,97 @@ +/** + * This file is part of LibLaserCut. + * Copyright (C) 2011 - 2014 Thomas Oster <mail@thomas-oster.de> + * + * LibLaserCut is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * LibLaserCut 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with LibLaserCut. If not, see <http://www.gnu.org/licenses/>. + * + **/ +package com.t_oster.liblasercut.drivers; + +import java.io.IOException; +import java.util.Arrays; +import java.util.LinkedList; +import java.util.List; + +/** + * This class implements a driver for SmoothieBoard. + * + * @author Thomas Oster <mail@thomas-oster.de> + */ +public class SmoothieBoard extends GenericGcodeDriver { + + public SmoothieBoard() + { + //set some smoothie-specific defaults + setIdentificationLine("Smoothie"); + setWaitForOKafterEachLine(true); + setBaudRate(115200); + setLineend("CRLF"); + setInitDelay(0); + setPreJobGcode(getPreJobGcode()+",M3"); + setPostJobGcode(getPostJobGcode()+",M5"); + } + + @Override + public String getIdentificationLine() + { + if (getHost() == null || "".equals(getHost())) + { + return "Smoothie"; + } + else + { + return "Smoothie command shell"; + } + } + + @Override + protected String waitForLine() throws IOException + { + String line = super.waitForLine(); + //The telnet interface for smoothie prepends lines with '> ' + if (getHost() != null && !"".equals(getHost()) && line.startsWith("> ")) + { + return line.substring(2); + } + return line; + } + + @Override + public String[] getPropertyKeys() + { + List<String> result = new LinkedList<String>(); + result.addAll(Arrays.asList(super.getPropertyKeys())); + result.remove(GenericGcodeDriver.SETTING_IDENTIFICATION_STRING); + result.remove(GenericGcodeDriver.SETTING_WAIT_FOR_OK); + result.remove(GenericGcodeDriver.SETTING_BAUDRATE); + result.remove(GenericGcodeDriver.SETTING_LINEEND); + result.remove(GenericGcodeDriver.SETTING_INIT_DELAY); + return result.toArray(new String[0]); + } + + @Override + public String getModelName() + { + return "Smoothie Board"; + } + + @Override + public SmoothieBoard clone() + { + SmoothieBoard clone = new SmoothieBoard(); + clone.copyProperties(this); + return clone; + } + +} \ No newline at end of file