Search code examples
javaserial-portctsjssc

JSSC serial connection set write timeout


I need to write some byte to the serial connection. However I can not find something in JSSC library to set a write timeout. I need this timeout because if I set the hardware flowcontrol and I remove the cable my application is stuck waiting the CTS signal.

UPDATE

I tried this workaround with Future object:

    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)");
            }
        }

But it doesn't work very well, it take 4s to write 550byte via COM port 256000 baud...

Trying a direct write:

    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());
        }
    }

It took 0.5s as expected!

The problem seems to be the "syncronized" keyword in the main method, why?


Solution

  • I had the same problem. I solved it by launching two threads : one to write one to wait for a specific amount of time. Depending one the first one that finishes, the writing is a success or a timeout. Here are the different classes I used :

    ByteWriter : an interface for a generic byte writing (I wanted to be able to switch from JSSC to any other framework

    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 : an implementation of ByteWriter for 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 : the class to perform the writing timeout.

    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;
                }
            }
        }
    
    }
    

    And the TimeOutException

    package net.femtoparsec.jssc;
    
    import java.io.IOException;
    
    public class TimeoutException extends IOException {
    
        public TimeoutException(String message) {
            super(message);
        }
    }
    

    Then, simply create a JsscByteWriter and use the methods with the timeout parameter to write with a timeout.