Тайм-аут записи набора последовательных соединений JSSC
Мне нужно записать байт для последовательного соединения. Однако я не могу найти что-то в библиотеке JSSC, чтобы установить время ожидания записи. Мне нужен этот тайм-аут, потому что, если я установлю аппаратное управление потоком и отсоединю кабель, мое приложение застрянет в ожидании сигнала CTS.
ОБНОВИТЬ
Я пробовал этот обходной путь с объектом Future:
ExecutorService executor = Executors.newSingleThreadExecutor();
...
public synchronized void write(byte[] content, int timeout) throws InterruptedException, SerialPortException{
long starttime = System.currentTimeMillis();
Future<Boolean> future = executor.submit(new Callable<Boolean>() {
public Boolean call() throws Exception {
serialPort.writeBytes(content);
return new Boolean(true);
}
});
try {
future.get(timeout, TimeUnit.MILLISECONDS);
log.debug("Duration: {}",DurationFormatUtils.formatDuration(System.currentTimeMillis() - starttime, "mm:ss.SS"));
} catch (ExecutionException e) {
throw new HardwareException(e.getMessage());
} catch (TimeoutException e) {
throw new HardwareException("Impossibile scrivere nella porta seriale (timeout)");
}
}
Но это не очень хорошо работает, для записи 550 байтов через COM-порт 256000 бод требуется 4 секунды...
Попытка прямой записи:
public synchronized void write(byte[] content, int timeout) throws InterruptedException, SerialPortException{
try {
long starttime = System.currentTimeMillis();
serialPort.writeBytes(content);
log.debug("Duration: {}",DurationFormatUtils.formatDuration(System.currentTimeMillis() - starttime, "mm:ss.SS"));
} catch (SerialPortException e) {
throw new HardwareException(e.getMessage());
}
}
Прошло 0.5 с, как и ожидалось!
Похоже, проблема заключается в ключевом слове syncronized в методе main. Почему?
2 ответа
У меня такая же проблема. Я решил эту проблему, запустив два потока: один для записи, другой для ожидания определенного количества времени. В зависимости от того, кто первым заканчивает, запись - это успех или тайм-аут. Вот разные классы, которые я использовал:
ByteWriter: интерфейс для написания общих байтов (я хотел иметь возможность переключаться с JSSC на любой другой фреймворк
package net.femtoparsec.jssc;
import java.io.IOException;
public interface ByteWriter {
void write(byte[] bytes) throws IOException;
void write(byte oneByte) throws IOException;
void write(byte[] bytes, long timeout) throws IOException, InterruptedException;
void write(byte oneByte, long timeout) throws IOException, InterruptedException;
void cancelWrite() throws IOException;
}
JsscByteWriter: реализация ByteWriter для Jssc
package net.femtoparsec.jssc;
import jssc.SerialPort;
import jssc.SerialPortException;
import java.io.IOException;
public class JsscByteWriter implements ByteWriter {
private final SerialPort serialPort;
public JsscByteWriter(SerialPort serialPort) {
this.serialPort = serialPort;
}
@Override
public void cancelWrite() throws IOException {
try {
serialPort.purgePort(SerialPort.PURGE_TXABORT);
serialPort.purgePort(SerialPort.PURGE_TXCLEAR);
} catch (SerialPortException e) {
throw new IOException(e);
}
}
@Override
public void write(byte[] bytes) throws IOException {
try {
serialPort.writeBytes(bytes);
} catch (SerialPortException e) {
throw new IOException(e);
}
}
@Override
public void write(byte oneByte) throws IOException {
try {
serialPort.writeByte(oneByte);
} catch (SerialPortException e) {
throw new IOException(e);
}
}
@Override
public void write(byte[] bytes, long timeout) throws IOException, InterruptedException {
if (timeout <= 0) {
this.write(bytes);
}
else {
new TimedOutByteWriting(this, bytes, timeout).write();
}
}
@Override
public void write(byte oneByte, long timeout) throws IOException, InterruptedException {
if (timeout <= 0) {
this.write(oneByte);
}
else {
new TimedOutByteWriting(this, oneByte, timeout).write();
}
}
}
TimedOutByteWriting: класс для выполнения тайм-аута записи.
package net.femtoparsec.jssc;
import java.io.IOException;
import java.util.Objects;
import java.util.concurrent.*;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
class TimedOutByteWriting {
private final ByteWriter byteWriter;
private final boolean onlyOneByte;
private final byte oneByte;
private final byte[] bytes;
private final long timeout;
private static final ExecutorService EXECUTOR_SERVICE = Executors.newCachedThreadPool(r -> {
Thread t = new Thread(r, "TimedOutByteWriting Thread");
t.setDaemon(true);
return t;
});
TimedOutByteWriting(ByteWriter byteWriter, byte oneByte, long timeout) {
if (timeout <= 0) {
throw new IllegalArgumentException("Invalid time out value : "+timeout+". Must be greater than 0");
}
this.byteWriter = Objects.requireNonNull(byteWriter, "byteWriter");
this.bytes = null;
this.oneByte = oneByte;
this.timeout = timeout;
this.onlyOneByte = true;
}
TimedOutByteWriting(ByteWriter byteWriter, byte[] bytes, long timeout) {
if (timeout <= 0) {
throw new IllegalArgumentException("Invalid time out value : "+timeout+". Must be greater than 0");
}
this.byteWriter = Objects.requireNonNull(byteWriter, "byteWriter");
this.bytes = Objects.requireNonNull(bytes, "bytes");
this.timeout = timeout;
this.oneByte = 0;
this.onlyOneByte = false;
}
void write() throws IOException, InterruptedException {
final Lock lock = new ReentrantLock();
final Condition condition = lock.newCondition();
final Result result = new Result();
final Future<?> writeThread = EXECUTOR_SERVICE.submit(new WriteRunnable(result, lock, condition));
final Future<?> timeoutThread = EXECUTOR_SERVICE.submit(new TimeoutRunnable(result, lock, condition));
lock.lock();
try {
if (!result.timedout && !result.writeDone) {
try {
condition.await();
} catch (InterruptedException e) {
writeThread.cancel(true);
timeoutThread.cancel(true);
throw e;
}
}
if (!result.writeDone) {
byteWriter.cancelWrite();
}
else {
timeoutThread.cancel(true);
}
}
finally {
lock.unlock();
}
result.handleResult();
}
private abstract class TimedOutByteWritingRunnable implements Runnable {
protected final Result result;
final Lock lock;
final Condition condition;
TimedOutByteWritingRunnable(Result result, Lock lock, Condition condition) {
this.result = result;
this.lock = lock;
this.condition = condition;
}
}
private class WriteRunnable extends TimedOutByteWritingRunnable {
private WriteRunnable(Result result, Lock lock, Condition condition) {
super(result, lock, condition);
}
@Override
public void run() {
IOException exception;
try {
if (onlyOneByte) {
byteWriter.write(oneByte);
} else {
byteWriter.write(bytes);
}
exception = null;
} catch (IOException e) {
exception = e;
}
lock.lock();
try {
result.writeException = exception;
result.writeDone = exception == null;
condition.signalAll();
} finally {
lock.unlock();
}
}
}
private class TimeoutRunnable extends TimedOutByteWritingRunnable {
private TimeoutRunnable(Result result, Lock lock, Condition condition) {
super(result, lock, condition);
}
@Override
public void run() {
boolean interrupted;
try {
TimeUnit.MILLISECONDS.sleep(timeout);
interrupted = false;
} catch (InterruptedException e) {
interrupted = true;
}
lock.lock();
try {
result.timedout = !interrupted;
condition.signalAll();
} finally {
lock.unlock();
}
}
}
private static class Result {
IOException writeException;
boolean writeDone = false;
boolean timedout = false;
void handleResult() throws IOException {
if (writeDone) {
return;
}
if (timedout) {
throw new TimeoutException("Write timed out");
}
else if (writeException != null) {
throw writeException;
}
}
}
}
И TimeOutException
package net.femtoparsec.jssc;
import java.io.IOException;
public class TimeoutException extends IOException {
public TimeoutException(String message) {
super(message);
}
}
Затем просто создайте JsscByteWriter и используйте методы с параметром timeout для записи с таймаутом.
При использовании управления потоком запись будет блокироваться при достижении порога, чтобы предотвратить переполнение буфера. Например, если был получен символ XOFF, драйвер или ОС не позволят последовательному порту отправлять данные на удаленный конец. Вышеупомянутый подход, такой как отмена потока, может оставить работу последовательного порта в несогласованном состоянии, если используется перекрывающийся IO (окна). Мы манипулируем вещами в слое Java, но как насчет нативного слоя. Пожалуйста, поправьте меня, если я что-то пропустил.
Подумайте об использовании другой библиотеки последовательных портов, например SCM, или измените собственный код jssc для обработки таких ситуаций.