Question

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?

Was it helpful?

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.

OTHER TIPS

When using flow control write will block if threshold is reached to prevent buffer overflow. For example if XOFF character has been received then driver or OS will not allow serial port to send data to remote end. The above approach like canceling the thread may leave serial port operation in inconsistent state if overlapped IO (windows) is used. We are manipulating things in java layer but what about native layer. Please correct me if I missed something.

Consider using other serial port library like SCM or modify the jssc's native code to handle such situations.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top