/* * 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 * * Ami: Driver for LinuxCNC. Created 28/09/2012. Setup in machine.xml This is quick-n-dirty driver, * it works but lots of setup gui are not done. I'm relying on linuxCNC to do the hardware setup * (homing etc) and just when it's ready to run, then the OpenPNP can take over. */ /* * Below is a slightly edited E-mail from Ami on the OpenPNP mail list dated 27May2013 that * describes how to bring up the LinuxCNC driver: * * Step1: Run emc2 until it's configured well. Step2. Put emc2 in MDI mode (F5) (it won't respond to * external gcode if in jog mode) Step3: Run the remote shell (emcrsh) * * You're using ubuntu 10.04, so I guess it's the older version called emc2. The newer version is * called linuxcnc. * * Open a terminal window, You must go to the folder where you have the config of your cnc, for * example: /emc2/configs/mycnc/ There must be a file named emc.nml overthere. * * Then on the terminal window, type : emcrsh. * * This is the program that opens the port so we can control emc2 from a distance. (There is a way * to do this automatically, in mycnc.ini file but for starting up it's better to do it manually) * * Step4: in openpnp: on machine.xml you must have something like this: * * <driver class="org.openpnp.machine.reference.driver.LinuxCNC" server-ip="192.168.1.6" * port="5007"/> * * Port 5007 is the default used by linuxcnc. server-ip is the address of the machine where emc2 is * running. It doesn't have to be on the same machine where openpnp runs. * * Step5: Run openpnp, it should connect to the emc2, in emcrsh window it sould show : * "Connected to x" */ package org.openpnp.machine.reference.driver; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.io.PrintWriter; import java.net.InetSocketAddress; import java.net.Socket; import java.net.SocketAddress; import java.util.ArrayList; import java.util.List; import java.util.Locale; import java.util.Queue; import java.util.Scanner; import java.util.concurrent.ConcurrentLinkedQueue; import javax.swing.Action; import javax.swing.Icon; import org.openpnp.gui.support.PropertySheetWizardAdapter; import org.openpnp.gui.support.Wizard; import org.openpnp.machine.reference.ReferenceActuator; import org.openpnp.machine.reference.ReferenceDriver; import org.openpnp.machine.reference.ReferenceHead; import org.openpnp.machine.reference.ReferenceHeadMountable; import org.openpnp.machine.reference.ReferenceNozzle; import org.openpnp.machine.reference.ReferencePasteDispenser; 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. */ public class LinuxCNC implements ReferenceDriver, Runnable { private static final double minimumRequiredVersion = 0.81; @Attribute(required = false) private String serverIp = "127.0.0.1"; @Attribute(required = false) private int port = 502; @Attribute(required = false) private double feedRateMmPerMinute; private double x, y, z, c; private Socket socket; private InputStream input; private OutputStream output; private Thread readerThread; private boolean disconnectRequested; private Object commandLock = new Object(); private boolean connected; private double connectedVersion; private Queue<String> responseQueue = new ConcurrentLinkedQueue<>(); private final static int CONNECT_TIMOUT = 5; // 5 second time-out for // connection private static Scanner in; private static PrintWriter out; public LinuxCNC() {} @Override public void home(ReferenceHead head) throws Exception { sendCommand("set mdi G0 Z-20"); // SafeZ sendCommand("set mdi G0 X0 Y0"); sendCommand("set mdi G1 F200 Z0"); 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.2f ", x)); } if (!Double.isNaN(y) && y != this.y) { sb.append(String.format(Locale.US, "Y%2.2f ", y)); } if (!Double.isNaN(z) && z != this.z) { sb.append(String.format(Locale.US, "Z%2.2f ", z)); } if (!Double.isNaN(c) && c != this.c) { sb.append(String.format(Locale.US, "A%2.2f ", c)); } if (sb.length() > 0) { sb.append(String.format(Locale.US, "F%2.2f", feedRateMmPerMinute * speed)); sendCommand("set mdi 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("set mdi m3 s100"); dwell(); } @Override public void place(ReferenceNozzle nozzle) throws Exception { sendCommand("set mdi m5"); dwell(); } @Override public void actuate(ReferenceActuator actuator, boolean on) throws Exception { // if (index == 0) { // sendCommand(on ? "M8" : "M9"); // 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(serverIp, port); } if (connected) { sendCommand("set machine " + (enabled ? "on" : "off")); } } public synchronized void connect(String serverIp, int port) throws Exception { // disconnect(); Logger.debug("connect({}, {})", serverIp, port); SocketAddress sa = new InetSocketAddress(serverIp, port); socket = new Socket(); socket.connect(sa, CONNECT_TIMOUT * 1000); input = socket.getInputStream(); output = socket.getOutputStream(); 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 change to wait. readerThread = new Thread(this); readerThread.start(); // Wait up to 3 seconds for Grbl 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); } connected = true; responses = sendCommand("hello EMC x 1.1"); responses.addAll(sendCommand("set enable EMCTOO")); responses.addAll(sendCommand("set estop off")); responses.addAll(sendCommand("set mode mdi")); // set_wait done -- will respond after the commanded move is completed // The default behavior is to respond when received which causes // OpenPnP to spit out gcode full-bore. responses.addAll(sendCommand("set set_wait done")); responses.addAll(sendCommand("set echo off")); // verbose on -- all commands will be replied with ACK or NAK // This will be used later to determine the return status. responses.addAll(sendCommand("set verbose on")); processConnectionResponses(responses); if (!connected) { throw new Exception( "Unable to receive connection response from LinuxCNC ver 1.1. Check your server ip and port in machine.xml"); } if (!connected) { throw new Exception(String.format( "Unable to receive connection response from LinuxCNC. Check your server ip and port in machine.xml and that you are running at least version %f of LinuxCNCrsh", minimumRequiredVersion)); } if (connectedVersion < minimumRequiredVersion) { throw new Exception(String.format( "This driver requires LinuxCNCrsh 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); // Force into miillmeter mode: sendCommand("set mdi G21"); // Reset all axes to 0, in case the firmware was not reset on // connect. sendCommand("set mdi G92 X0 Y0 Z0 A0"); } private void processConnectionResponses(List<String> responses) { for (String response : responses) { if (response.startsWith("HELLO ACK EMCNETSVR 1.1")) { connectedVersion = 1.1; connected = true; Logger.debug( String.format("Connected to LinuxCNCrsh Version: %.2f", connectedVersion)); } } } public synchronized void disconnect() { disconnectRequested = true; connected = false; try { if (readerThread != null && readerThread.isAlive()) { readerThread.join(); } input.close(); output.close(); socket.close(); } catch (Exception e) { Logger.error("disconnect()", e); } disconnectRequested = false; } private 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("sendCommand({}, {})", command, timeout); output.write(command.getBytes()); output.write("\r\n".getBytes()); } if (timeout == -1) { commandLock.wait(); } else { commandLock.wait(timeout); } } List<String> responses = drainResponseQueue(); return responses; } public void run() { while (!disconnectRequested) { String line = readLine().trim(); Logger.debug(line); responseQueue.offer(line); synchronized (commandLock) { commandLock.notify(); } } } /** * Causes Grbl to block until all commands are complete. * * @throws Exception */ private void dwell() throws Exception { sendCommand("set mdi G4 P0"); } private List<String> drainResponseQueue() { List<String> responses = new ArrayList<>(); String response; while ((response = responseQueue.poll()) != null) { responses.add(response); } return responses; } private String readLine() { StringBuffer line = new StringBuffer(); try { while (true) { int ch = readChar(); if (ch == -1) { return null; } else if (ch == '\n' || ch == '\r') { if (line.length() > 0) { return line.toString(); } } else { line.append((char) ch); } } } catch (Exception e) { Logger.error("readLine()", e); } return null; } private int readChar() { try { int ch = -1; while (ch == -1 && !disconnectRequested) { ch = input.read(); } return ch; } catch (Exception e) { Logger.error("readChar()", e); return -1; } } @Override public void close() throws IOException { try { disconnect(); } catch (Exception e) { throw new IOException(e); } } @Override public Wizard getConfigurationWizard() { // TODO Auto-generated method stub return null; } @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; } @Override public Icon getPropertySheetHolderIcon() { // TODO Auto-generated method stub return null; } @Override public void dispense(ReferencePasteDispenser dispenser, Location startLocation, Location endLocation, long dispenseTimeMilliseconds) throws Exception { // TODO Auto-generated method stub } }