Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to correctly communicate with 3D Printer

I have to write a java program that receives G-Code commands via network and sends them to a 3D printer via serial communication. In principle everything seems to be okay, as long as the printer needs more than 300ms to execute a command. If execution time is shorter than that, it takes too much time for the printer to receive the next command and that results in a delay between command execution (printer nozzle standing still for about 100-200ms). This can become a problem in 3d printing so i have to eliminate that delay.

For comparison: Software like Repetier Host or Cura can send the same commands via seial without any delay between command execution, so it has to be possible somehow.

I use jSerialComm library for serial communication.

This is the Thread that sends commands to the printer:

@Override
public void run() {
if(printer == null) return;
    log("Printer Thread started!");
    //wait just in case
    Main.sleep(3000);

    long last = 0;
    while(true) {

        String cmd = printer.cmdQueue.poll();
        if (cmd != null && !cmd.equals("") && !cmd.equals("\n")) {
            log(cmd+" last: "+(System.currentTimeMillis()-last)+"ms");
            last = System.currentTimeMillis();
            send(cmd  + "\n", 0);
        }

    }
}

private void send(String cmd, int timeout) {
    printer.serialWrite(cmd);
    waitForBuffer(timeout);
}

private void waitForBuffer(int timeout) {
    if(!blockForOK(timeout))
        log("OK Timeout ("+timeout+"ms)");
}

public boolean blockForOK(int timeoutMillis) {
    long millis = System.currentTimeMillis();
    while(!printer.bufferAvailable) {
        if(timeoutMillis != 0)
            if(millis + timeoutMillis < System.currentTimeMillis()) return false;
        try {
            sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
    printer.bufferAvailable = false;
    return true;
}

this is printer.serialWrite: ("Inspired" by Arduino Java Lib)

public void serialWrite(String s){
    comPort.setComPortTimeouts(SerialPort.TIMEOUT_SCANNER, 0, 500);
    try{Thread.sleep(5);} catch(Exception e){}

    PrintWriter pout = new PrintWriter(comPort.getOutputStream());
    pout.print(s);
    pout.flush();

}

printer is an Object of class Printer which implements com.fazecast.jSerialComm.SerialPortDataListener

relevant functions of Printer

@Override
public int getListeningEvents() {
    return SerialPort.LISTENING_EVENT_DATA_AVAILABLE;

}

@Override
public void serialEvent(SerialPortEvent serialPortEvent) {
    byte[] newData = new byte[comPort.bytesAvailable()];
    int numRead = comPort.readBytes(newData, newData.length);
    handleData(new String(newData));
}

private void handleData(String line) {
    //log("RX: "+line);
    if(line.contains("ok")) {
        bufferAvailable = true;
    }
    if(line.contains("T:")) {
        printerThread.printer.temperature[0] = Utils.readFloat(line.substring(line.indexOf("T:")+2));
    }
    if(line.contains("T0:")) {
        printerThread.printer.temperature[0] = Utils.readFloat(line.substring(line.indexOf("T0:")+3));
    }
    if(line.contains("T1:")) {
        printerThread.printer.temperature[1] = Utils.readFloat(line.substring(line.indexOf("T1:")+3));
    }
    if(line.contains("T2:")) {
        printerThread.printer.temperature[2] = Utils.readFloat(line.substring(line.indexOf("T2:")+3));
    }
}

Printer.bufferAvailable is declared volatile I also tried blocking functions of jserialcomm in another thread, same result. Where is my bottleneck? Is there a bottleneck in my code at all or does jserialcomm produce too much overhead?

For those who do not have experience in 3d-printing: When the printer receives a valid command, it will put that command into an internal buffer to minimize delay. As long as there is free space in the internal buffer it replies with ok. When the buffer is full, the ok is delayed until there is free space again. So basicly you just have to send a command, wait for the ok, send another one immediately.

like image 753
theCNG27 Avatar asked Apr 08 '18 16:04

theCNG27


People also ask

How do 3D printers communicate?

Most 3D printers can be connected via USB to your computer. Just use the cable that comes with your printer and always use it at the same USB port when possible. Make sure this USB port is working properly so no later communication problems arise.

What is the best way to connect 3D prints?

Superglue. For most jobs, cyanoacrylate, or superglue, is the best option for gluing 3D printed parts together. It's an easy-to-use glue that cures quickly. You can get excellent results, a strong bond, and an almost invisible seam.


2 Answers

@Override
public void serialEvent(SerialPortEvent serialPortEvent) {
    byte[] newData = new byte[comPort.bytesAvailable()];
    int numRead = comPort.readBytes(newData, newData.length);
    handleData(new String(newData));
}

This part is problematic, the event may have been triggered before a full line was read, so potentially only half an ok has been received yet. You need to buffer (over multiple events) and reassamble into messages first before attempting to parse this as full messages.

Worst case, this may have resulted in entirely loosing temperature readings or ok messages as they have been ripped in half.

See the InputStream example and wrap it in a BufferedReader to get access to BufferedReader::readLine(). With the BufferedReader in place, you can that just use that to poll directly in the main thread and process the response synchronously.


try{Thread.sleep(5);} catch(Exception e){}
sleep(1);

You don't want to sleep. Depending on your system environment (and I strongly assume that this isn't running on Windows on x86, but rather Linux on an embedded platform), a sleep can be much longer than anticipated. Up to 30ms or 100ms, depending on the Kernel configuration.

The sleep before write doesn't make much sense in the first place, you know that the serial port is ready to write as you already had received an ok confirming reception of the previously sent command.

The sleep during receive becomes pointless when using the BufferedReader.


comPort.setComPortTimeouts(SerialPort.TIMEOUT_SCANNER, 0, 500);

And this is actually causing your problems. SerialPort.TIMEOUT_SCANNER activates a wait period on read. After receiving the first byte it will wait at least for another 100ms to see if it will become part of a message. So after it has seen the ok it then waits 100ms internally on the OS side before it assumes that this was all there is.

You need SerialPort.TIMEOUT_READ_SEMI_BLOCKING for low latency, but then the problem predicted in the first paragraph will occur unless buffered.

Setting repeatedly also causes yet another problem, because there is a 200ms sleep in Serialport::setComPortTimeouts internally. Set it per serial connection once, no more than that.

like image 63
Ext3h Avatar answered Sep 22 '22 12:09

Ext3h


Check the manual of the printer (or tell us the model) not sure you actually need to wait for the ok, and therefore you can read/write concurrently. Some of the time there's a hardware flow control handling this stuff for you, with large enough buffers. Try just send the commands without waiting for ok, see what happens.

If you just want to pipe commands from the network to serial port, you can use ready-made solution like socat. For example running the following:

socat TCP-LISTEN:8888,fork,reuseaddr FILE:/dev/ttyUSB0,b115200,raw

would pipe all bytes coming from clients connected to the 8888 port directly to the /dev/ttyUSB0 at baud rate of 115200 (and vice-versa).

like image 33
idanp Avatar answered Sep 21 '22 12:09

idanp