/** * 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/>. * **/ /** * Author: Sven Jung <sven.jung@rwth-aachen.de> */ package com.t_oster.liblasercut.drivers; import com.t_oster.liblasercut.IllegalJobException; import com.t_oster.liblasercut.*; import com.t_oster.liblasercut.LaserJob; import com.t_oster.liblasercut.ProgressListener; import com.t_oster.liblasercut.platform.Point; import com.t_oster.liblasercut.platform.Util; import java.io.BufferedOutputStream; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.io.PrintWriter; import java.io.UnsupportedEncodingException; import java.util.Arrays; import java.util.LinkedList; import java.util.List; import java.util.Locale; import purejavacomm.CommPortIdentifier; import purejavacomm.NoSuchPortException; import purejavacomm.PortInUseException; import purejavacomm.SerialPort; /** * * @author Sven Jung */ public class MakeBlockXYPlotter extends LaserCutter { private enum ToolState { ON, OFF } /* * Internal Settings */ private final boolean debug = false; // print to command line private static final String MODELNAME = "MakeBlockXYPlotter"; private double addSpacePerRasterLine = 0.5; private String hostname = ""; private double bedWidth = 300; private double bedHeight = 210; private int delayRate = 5000; private int powerRate = 255; private String usedTool = "PEN"; // PEN, Laser private List<Double> resolutions = Arrays.asList(new Double[]{ 64d // fine liner }); private int chosenDelay; private int chosenPower; private ToolState toolState; private PrintWriter w = null; BufferedReader portReader = null; private BufferedOutputStream out = null; private SerialPort port = null; /* * Global Settings */ private static final String SETTING_HOSTNAME = "Target port:// or file://"; private static final String SETTING_RASTER_WHITESPACE = "Additional space per Raster line (mm)"; private static final String SETTING_BEDWIDTH = "Laserbed width (mm)"; private static final String SETTING_BEDHEIGHT = "Laserbed height (mm)"; private static final String SETTING_DELAY_RATE = "Max. Delay Rate (abs. us)"; private static final String SETTING_POWER_RATE = "Max. Power Rate (abs. pwm)"; private static final String SETTING_TOOL = "Tool (PEN, LASER)"; private static String[] settingAttributes = new String[]{ SETTING_HOSTNAME, SETTING_RASTER_WHITESPACE, SETTING_BEDWIDTH, SETTING_BEDHEIGHT, SETTING_DELAY_RATE, SETTING_POWER_RATE, SETTING_TOOL }; /** * Get the value of MODELNAME * * @return the value of MODELNAME */ @Override public String getModelName() { return MODELNAME; } @Override public List<Double> getResolutions() { return resolutions; } @Override public MakeBlockXYPlotterProperty getLaserPropertyForVectorPart() { return new MakeBlockXYPlotterProperty(this.usedTool.equals("LASER")); // show power and speed only if laser } @Override public MakeBlockXYPlotterProperty getLaserPropertyForRasterPart() { return new MakeBlockXYPlotterProperty(this.usedTool.equals("LASER")); // show power and speed only if laser } /** * Get the value of bedWidth * * @return the value of bedWidth */ @Override public double getBedWidth() { return bedWidth; } /** * Get the value of bedHeight * * @return the value of bedHeight */ @Override public double getBedHeight() { return bedHeight; } private void generateInitializationGCode() throws Exception { toolOff(); this.sendCommand("G28 X Y");//move to 0 0 } private void generateShutdownGCode() throws Exception { //back to origin and shutdown toolOff(); this.sendCommand("G28 X Y");//move to 0 0 } private void toolOff() throws Exception { if(toolState != ToolState.OFF) { if(usedTool.equals("PEN")) { this.sendCommand("M1 90"); this.sendCommand(String.format("M3 %d", 0)); // to ensure fastest speed } else if(usedTool.equals("LASER")) { this.sendCommand(String.format("M4 %d", 0)); this.sendCommand(String.format("M3 %d", 0)); // to move faster with tool off } else { throw new Exception("Tool " + this.usedTool + " not supported!"); } toolState = ToolState.OFF; } } private void toolOn() throws Exception { if(toolState != ToolState.ON) { if(usedTool.equals("PEN")) { this.sendCommand(String.format("M3 %d", 0)); // to ensure fastest speed this.sendCommand("M1 130"); } else if(usedTool.equals("LASER")) { this.sendCommand(String.format("M3 %d", (int) ((double) delayRate * this.chosenDelay / 100))); this.sendCommand(String.format("M4 %d", (int) ((double) powerRate * this.chosenPower / 100))); } else { throw new Exception("Tool " + this.usedTool + " not supported!"); } toolState = ToolState.ON; } } private void setDelay(int value) throws Exception{ // saves just the chosen delay value // delay of the plotter really set on toolOn(), to move faster with tool off if(usedTool.equals("LASER")) { // property option only supported if laser if (value != chosenDelay) { chosenDelay = value; } } } private void setPower(int value) throws Exception{ // saves just the chosen power value // power of the laser really set on toolOn() if(usedTool.equals("LASER")) { // property option only supported if laser if (value != chosenPower) { chosenPower = value; } } } private void move(int x, int y, double resolution) throws Exception{ toolOff(); this.sendCommand(String.format(Locale.US, "G0 X%f Y%f", Util.px2mm(x, resolution), Util.px2mm(y, resolution))); } private void line(int x, int y, double resolution) throws Exception{ toolOn(); this.sendCommand(String.format(Locale.US, "G1 X%f Y%f", Util.px2mm(x, resolution), Util.px2mm(y, resolution))); } private void generateVectorGCode(VectorPart vp, double resolution, ProgressListener pl, int startProgress, int maxProgress) throws UnsupportedEncodingException, Exception { int i = 0; int progress; int max = vp.getCommandList().length; for (VectorCommand cmd : vp.getCommandList()) { switch (cmd.getType()) { case MOVETO: int x = cmd.getX(); int y = cmd.getY(); this.move(x, y, resolution); break; case LINETO: x = cmd.getX(); y = cmd.getY(); this.line(x, y, resolution); break; case SETPROPERTY: // called once per part to set chosen properties MakeBlockXYPlotterProperty p = (MakeBlockXYPlotterProperty) cmd.getProperty(); // only set with LASER tool // ensure percent power int pPercent = p.getPower(); pPercent = pPercent<0?0:pPercent; pPercent = pPercent>100?100:pPercent; this.setPower(pPercent); // ensure percent speed int sPercent = p.getSpeed(); sPercent = sPercent<0?0:sPercent; sPercent = sPercent>100?100:sPercent; int dPercent = 100-sPercent; // convert speed to delay this.setDelay(dPercent); break; } i++; progress = (startProgress + (int) (i*(double) maxProgress/max)); pl.progressChanged(this, progress); } } private void generatePseudoRasterGCode(RasterPart rp, double resolution, ProgressListener pl, int startProgress, int maxProgress) throws UnsupportedEncodingException, Exception { int i = 0; int progress; int max = rp.getRasterHeight(); boolean dirRight = true; Point rasterStart = rp.getRasterStart(); // called once per part to set chosen properties PowerSpeedFocusProperty prop = (PowerSpeedFocusProperty) rp.getLaserProperty(); this.setDelay(prop.getSpeed()); this.setPower(prop.getPower()); for (int line = 0; line < rp.getRasterHeight(); line++) { Point lineStart = rasterStart.clone(); lineStart.y += line; List<Byte> bytes = new LinkedList<Byte>(); boolean lookForStart = true; for (int x = 0; x < rp.getRasterWidth(); x++) { if (lookForStart) { if (rp.isBlack(x, line)) { lookForStart = false; bytes.add((byte) 255); } else { lineStart.x += 1; } } else { bytes.add(rp.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) { //add some space to the left this.move(Math.max(0, (int) (lineStart.x - Util.mm2px(this.addSpacePerRasterLine, resolution))), lineStart.y, resolution); //move to the first nonempyt point of the line this.move(lineStart.x, lineStart.y, resolution); byte old = bytes.get(0); for (int pix = 0; pix < bytes.size(); pix++) { if (bytes.get(pix) != old) { if (old == 0) { this.move(lineStart.x + pix, lineStart.y, resolution); } else { this.setPower(prop.getPower() * (0xFF & old) / 255); this.line(lineStart.x + pix - 1, lineStart.y, resolution); this.move(lineStart.x + pix, lineStart.y, resolution); } old = bytes.get(pix); } } //last point is also not "white" this.setPower(prop.getPower() * (0xFF & bytes.get(bytes.size() - 1)) / 255); this.line(lineStart.x + bytes.size() - 1, lineStart.y, resolution); //add some space to the right this.move(Math.min((int) Util.mm2px(bedWidth, resolution), (int) (lineStart.x + bytes.size() - 1 + Util.mm2px(this.addSpacePerRasterLine, resolution))), lineStart.y, resolution); } else { //add some space to the right this.move(Math.min((int) Util.mm2px(bedWidth, resolution), (int) (lineStart.x + bytes.size() - 1 + Util.mm2px(this.addSpacePerRasterLine, resolution))), lineStart.y, resolution); //move to the last nonempty point of the line this.move(lineStart.x + bytes.size() - 1, lineStart.y, resolution); 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) { this.move(lineStart.x + pix, lineStart.y, resolution); } else { this.setPower(prop.getPower() * (0xFF & old) / 255); this.line(lineStart.x + pix + 1, lineStart.y, resolution); this.move(lineStart.x + pix, lineStart.y, resolution); } old = bytes.get(pix); } } //last point is also not "white" this.setPower(prop.getPower() * (0xFF & bytes.get(0)) / 255); this.line(lineStart.x, lineStart.y, resolution); //add some space to the left this.move(Math.max(0, (int) (lineStart.x - Util.mm2px(this.addSpacePerRasterLine, resolution))), lineStart.y, resolution); } } dirRight = !dirRight; i = line + 1; progress = (startProgress + (int) (i*(double) maxProgress/max)); pl.progressChanged(this, progress); } } private void connect() throws NoSuchPortException, PortInUseException, Exception { if(!this.debug){ if (this.hostname.startsWith("port://")) { String portString = this.hostname.replace("port://", ""); try{ CommPortIdentifier cpi = CommPortIdentifier.getPortIdentifier(portString); port = (SerialPort) cpi.open("VisiCut", 2000); } catch(Exception e) { throw new Exception("Port '"+portString+"' is not available."); } if (port == null) { throw new Exception("Error: Could not Open COM-Port '"+portString+"'"); } if (!(port instanceof SerialPort)) { throw new Exception("Port '"+portString+"' is not a serial port."); } port.setSerialPortParams(115200, SerialPort.DATABITS_8, SerialPort.STOPBITS_1, SerialPort.PARITY_NONE); out = new BufferedOutputStream(port.getOutputStream()); portReader = new BufferedReader(new InputStreamReader(port.getInputStream())); // wake up firmware String command = "\r\n\r\n"; out.write(command.getBytes("US-ASCII")); out.flush(); Thread.sleep(2000); portReader.readLine(); // "ok" portReader.readLine(); // "ok" this.checkVersion(); } else if (hostname.startsWith("file://")) { String filename = this.hostname.replace("file://", ""); try { w = new PrintWriter(filename); } catch(Exception e) { throw new Exception(String.format("No correct absolute file path: %s Exception %s", this.hostname, e)); } } else { throw new Exception(String.format("Unknown hostname: %s", this.hostname)); } } } private void disconnect() throws Exception{ if(w != null) { w.close(); w = null; } if(out != null) { out.close(); out = null; } if(port != null){ port.close(); port = null; } } private void checkResponse(String command, String response, String expectedAnswer) throws Exception { if(!response.toLowerCase().contains(expectedAnswer.toLowerCase())) { throw new Exception(String.format("Got wrong response to command \"%s\":\n\"%s\" instead of \"%s\"", command, response, expectedAnswer)); } } private void sendCommand(String command) throws Exception { this.send(command); if(!debug) { if (this.hostname.startsWith("port://")) { String resp = this.receive(); this.checkResponse(command, resp, "ok"); } } } private void checkVersion() throws Exception { // check if firmware matches implemented protocol this.send("M115"); if(!debug) { if (this.hostname.startsWith("port://")) { String resp = this.receive(); this.checkResponse("Version", resp, "Version"); String resp2 = this.receive(); this.checkResponse("Version", resp2, "ok"); } } } private void send(String command) throws Exception { if(!debug) { if (this.hostname.startsWith("port://")) { // send String sendString = command + "\n"; out.write(sendString.getBytes("US-ASCII")); out.flush(); } else if (hostname.startsWith("file://")) { w.println(command); } else { throw new Exception(String.format("Unknown hostname: %s", this.hostname)); } } else { System.out.println(command); } } private String receive() throws Exception{ if(!debug) { if (this.hostname.startsWith("port://")) { String line; try { line = portReader.readLine(); line = line.replace("\n", "").replace("\r", ""); return line; } catch(IOException e) { throw new IOException("IO Exception, e.g. timeout"); } } } return ""; } @Override public void sendJob(LaserJob job, ProgressListener pl, List<String> warnings) throws IllegalJobException, Exception { this.chosenPower = 0; this.chosenDelay = 0; this.toolState = ToolState.ON; // assume worst case, set to OFF in initialization code pl.progressChanged(this, 0); pl.taskChanged(this, "checking job"); checkJob(job); job.applyStartPoint(); pl.taskChanged(this, "connecting"); this.connect(); pl.taskChanged(this, "sending"); this.generateInitializationGCode(); int startProgress = 20; pl.progressChanged(this, startProgress); int i = 0; int progress = startProgress; int max = job.getParts().size(); for (JobPart p : job.getParts()) { if (p instanceof Raster3dPart) { throw new Exception("Raster 3D parts are not implemented for " + this.getModelName()); } else if (p instanceof RasterPart) { this.generatePseudoRasterGCode((RasterPart) p, p.getDPI(), pl, progress, ((int) ((i+1)*(double) 80/max))); } else if (p instanceof VectorPart) { this.generateVectorGCode((VectorPart) p, p.getDPI(), pl, progress, ((int) ((i+1)*(double) 80/max))); } i++; progress = (startProgress + (int) (i*(double) 80/max)); pl.progressChanged(this, progress); } this.generateShutdownGCode(); pl.taskChanged(this, "disconnecting"); this.disconnect(); pl.taskChanged(this, "sent"); pl.progressChanged(this, 100); } @Override public LaserCutter clone() { MakeBlockXYPlotter clone = new MakeBlockXYPlotter(); clone.addSpacePerRasterLine = addSpacePerRasterLine; clone.hostname = hostname; clone.bedWidth = bedWidth; clone.bedHeight = bedHeight; clone.delayRate = delayRate; clone.powerRate = powerRate; clone.usedTool = usedTool; return clone; } @Override public String[] getPropertyKeys() { return settingAttributes; } @Override public Object getProperty(String attribute) { if (SETTING_HOSTNAME.equals(attribute)) { return this.hostname; } else if (SETTING_RASTER_WHITESPACE.equals(attribute)) { return this.addSpacePerRasterLine; } else if (SETTING_BEDWIDTH.equals(attribute)) { return this.bedWidth; } else if (SETTING_BEDHEIGHT.equals(attribute)) { return this.bedHeight; } else if (SETTING_DELAY_RATE.equals(attribute)) { return this.delayRate; } else if (SETTING_POWER_RATE.equals(attribute)) { return this.powerRate; } else if (SETTING_TOOL.equals(attribute)) { return this.usedTool; } return null; } @Override public void setProperty(String attribute, Object value) { if (SETTING_HOSTNAME.equals(attribute)) { this.hostname = (String) value; } else if (SETTING_RASTER_WHITESPACE.equals(attribute)) { this.addSpacePerRasterLine = (Double) value; } else if (SETTING_BEDWIDTH.equals(attribute)) { this.bedWidth = (Double) value; } else if (SETTING_BEDHEIGHT.equals(attribute)) { this.bedHeight = (Double) value; } else if (SETTING_DELAY_RATE.equals(attribute)) { this.delayRate = (Integer) value; } else if (SETTING_POWER_RATE.equals(attribute)) { this.powerRate = (Integer) value; } else if (SETTING_TOOL.equals(attribute)) { this.usedTool = (String) value; } } }