Как правильно общаться с 3D-принтером
Я должен написать Java-программу, которая получает команды G-кода по сети и отправляет их на 3D-принтер через последовательную связь. В принципе все в порядке, если принтеру требуется более 300 мсек для выполнения команды. Если время выполнения меньше этого, принтеру потребуется слишком много времени для получения следующей команды, и это приведет к задержке между выполнением команды (сопло принтера стоит около 100-200 мс). Это может стать проблемой в 3D-печати, поэтому я должен устранить эту задержку.
Для сравнения: такие программы, как Repetier Host или Cura, могут отправлять одни и те же команды через последовательный порт без какой-либо задержки между выполнением команд, поэтому это должно быть как-то возможно.
Я использую библиотеку jSerialComm для последовательной связи.
Это поток, который отправляет команды на принтер:
@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;
}
это printer.serialWrite: ("вдохновленный" 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
является объектом класса Printer
который реализует com.fazecast.jSerialComm.SerialPortDataListener
соответствующие функции принтера
@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
объявляется энергозависимым, я также пытался блокировать функции jserialcomm в другом потоке, тот же результат. Где мое узкое место? Есть ли в моем коде узкое место или jserialcomm создает слишком много накладных расходов?
Для тех, у кого нет опыта в 3d-печати: Когда принтер получает действительную команду, он помещает эту команду во внутренний буфер, чтобы минимизировать задержку. Пока во внутреннем буфере есть свободное место, он отвечает ok
, Когда буфер заполнен, ok
задерживается до тех пор, пока снова не будет свободного места. Поэтому в основном вам просто нужно отправить команду, дождаться подтверждения, немедленно отправить другую.
2 ответа
@Override
public void serialEvent(SerialPortEvent serialPortEvent) {
byte[] newData = new byte[comPort.bytesAvailable()];
int numRead = comPort.readBytes(newData, newData.length);
handleData(new String(newData));
}
Эта часть проблематична, событие могло быть запущено до того, как была прочитана полная строка, поэтому потенциально только половина ok
был получен еще. Прежде чем пытаться проанализировать это как полные сообщения, вам необходимо сначала создать буфер (по нескольким событиям) и повторно объединить его в сообщения.
В худшем случае это могло привести к полной потере показаний температуры или ok
сообщения, поскольку они были разорваны пополам.
Посмотрите пример InputStream и оберните его в BufferedReader
чтобы получить доступ к BufferedReader::readLine()
, С BufferedReader
на месте, вы можете просто использовать это для опроса непосредственно в главном потоке и синхронной обработки ответа.
try{Thread.sleep(5);} catch(Exception e){}
sleep(1);
Ты не хочешь спать. В зависимости от вашей системной среды (и я настоятельно полагаю, что это не работает на Windows на x86, а скорее на Linux на встроенной платформе), sleep
может быть намного дольше, чем ожидалось. До 30 мс или 100 мс, в зависимости от конфигурации ядра.
Сон перед записью не имеет большого смысла, во-первых, вы знаете, что последовательный порт готов к записи, как вы уже получили ok
подтверждение приема ранее отправленной команды.
Сон во время приема становится бессмысленным при использовании BufferedReader
,
comPort.setComPortTimeouts(SerialPort.TIMEOUT_SCANNER, 0, 500);
И это на самом деле вызывает ваши проблемы. SerialPort.TIMEOUT_SCANNER
активирует период ожидания при чтении. После получения первого байта он будет ждать, по крайней мере, еще 100 мс, чтобы увидеть, станет ли он частью сообщения. Так что после того, как он увидел ok
Затем он ждет 100 мс внутри на стороне ОС, прежде чем предположить, что это все, что есть.
Тебе нужно SerialPort.TIMEOUT_READ_SEMI_BLOCKING
для низкой задержки, но тогда проблема, предсказанная в первом параграфе, будет возникать, если не буферизована.
Установка многократно также вызывает еще одну проблему, потому что в спящем режиме 200 мс Serialport::setComPortTimeouts
внутренне. Установите один раз для последовательного соединения, не более того.
Проверьте руководство принтера (или сообщите нам модель), не уверен, что вам действительно нужно ждать ok
и, следовательно, вы можете читать / писать одновременно. Иногда есть аппаратное управление потоком, обрабатывающее это для вас, с достаточно большими буферами. Попробуйте просто отправить команды, не дожидаясь ok
посмотри что получится.
Если вы просто хотите передавать команды из сети в последовательный порт, вы можете использовать готовое решение, такое как socat. Например, запустив следующее:
socat TCP-LISTEN:8888,fork,reuseaddr FILE:/dev/ttyUSB0,b115200,raw
будет передавать все байты от клиентов, подключенных к порту 8888, напрямую к /dev/ttyUSB0
со скоростью 115200 бод (и наоборот).