Search code examples
javaserial-portraspberry-piuartrxtx

Transmitting data from another thread, slow serial link with Java and RXTX library


Alright, I'll try to be as clear as possible with my problem. I'm transmitting serial data over a veeeeeeery slow radio link (using the UART-controller on the raspberry pi and a home-built radio). It's for a very specific project where the requirement is spelled long range and speed is of less importance. The program (Radio.java) is running two threads. One thread (Receiver) receives telemetry data from another program using a TCP-socket (which is very high speed, actually 100mbit). This thread continuously saves the data it receives on the TCP-socket in an ArrayBlockingQueue (with size = 1) so that the other thread (Transmitter) can reach this data. The rate at which the Receiver-thread receives data is pretty high. Now, I want Transmitter-thread to transmit the data and when it's finished I want it to again get the latest data from Receiver-thread and transmit it again over the slow-radio-link. So in transmitter-thread I want it to work like this:

  1. Get latest data from Receiver-thread

  2. Transmit data over radio link (using the serialport)

  3. Don't do ANYTHING until the data is actually transmitted.

  4. repeat.

Now, when I'm running the program everything regarding the Receiver-thread is working just fine. But inside the transmitter-thread the line "this.out.write(output.getBytes());" just puts everything inside the OutputStream in a couple of milliseconds, and then again does the same thing. The data has no chance in being transmitted!

I've tried the example (only using the "SerialWriter"-thread) here: http://rxtx.qbang.org/wiki/index.php/Two_way_communcation_with_the_serial_port

And using a long "Lirum Ipsum"-text everything worked just fine transmitting in 50baud. So basically, I want the same behaviour in my program as using System.in.read > -1... (which I guess is blocking, the reason it works???).

What should I do?

2015-01-01 edit BEGIN

I've found the problem! SRobertz lead me into the right direction! The problem is actually not the writespeed to the UART-buffer. The difference between running the "TwoWayComm"-example and my own code is that I'm running a GPS connected to UART-RX-port of the Raspberry Pi. To read data from the GPS is use the "GPSD"-software (which outputs data in a JSON-format). The GPSD-software connects to the GPS with 9600baud (specifically for this GPS-unit), while I switch to 50 baud on the same port (without closing the open connection that GPSD is running)! Trying to open UART with two different baud-rates is what is messing everything up. I've rewritten the code so that I:

  1. Open UART on 9600 baud
  2. Read GPS data
  3. Close the UART
  4. Open UART on 50 baud
  5. Transmit telemetry data to UART
  6. Close UART
  7. Repeat

And now everything works like a charm...

2015-01-01 edit END

So ... here is the code:

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.concurrent.ArrayBlockingQueue;

import gnu.io.CommPort;
import gnu.io.CommPortIdentifier;
import gnu.io.SerialPort;

public class RADIO {
    ArrayBlockingQueue<String> queue = new ArrayBlockingQueue<String>(1);

    void connect(String portName) throws Exception {

        CommPortIdentifier portIdentifier = CommPortIdentifier
                .getPortIdentifier(portName);
        if (portIdentifier.isCurrentlyOwned()) {
            System.out.println("Error: Port is currently in use");
        } else {
            int timeout = 2000;
            CommPort commPort = portIdentifier.open(this.getClass().getName(),
                    timeout);

            if (commPort instanceof SerialPort) {
                SerialPort serialPort = (SerialPort) commPort;
                serialPort.setSerialPortParams(50, SerialPort.DATABITS_7,
                        SerialPort.STOPBITS_2, SerialPort.PARITY_NONE);

                // Open outputstream to write to the serial port
                OutputStream out = serialPort.getOutputStream();

                (new Thread(new Receiver(queue))).start();
                (new Thread(new Transmitter(out, queue))).start();

            } else {
                System.err.println("Error: Not serial port.");
            }
        }
    }

    public static class Receiver implements Runnable {
        OutputStream out;
        protected ArrayBlockingQueue<String> queue = null;

        public Receiver(ArrayBlockingQueue<String> queue) {
            this.queue = queue;
        }

        public void run() {
            // Open TCP-connection
            try {
                ServerSocket serverSocket = new ServerSocket(1002);

                Socket clientSocket = serverSocket.accept(); // Wait for the client to start up
                BufferedReader in = new BufferedReader(new InputStreamReader(
                        clientSocket.getInputStream()));
                String inputLine, outputLine;

                while ((inputLine = in.readLine()) != null) {
                    queue.clear();
                    queue.put(inputLine);
                }
            } catch (IOException e) {
                e.printStackTrace();
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }

        }
    }

    public static class Transmitter implements Runnable {
        OutputStream out;
        protected ArrayBlockingQueue<String> queue = null;
        String output = "";

        public Transmitter(OutputStream out, ArrayBlockingQueue<String> queue) {
            this.out = out;
            this.queue = queue;
        }

        public void run() {
            try {
                while (true) {
                    output = queue.take();
                    this.out.write(output.getBytes());
                }

            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            } catch (IOException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }

    }

    public static void main(String[] args) {
        try {
            (new RADIO()).connect("/dev/ttyAMA0");
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

Solution

  • (a small caveat: I haven't used gnu.io on the raspberry pi)

    First, to isolate the problem, I would put a fairly long sleep in the transmitter thread, after this.out.write... to verify that the problem is not waiting for the serial port to finish the transmission.

    If that works, then you can try waiting for OUTPUT_BUFFER_EMPTY, by adding a SerialPortEventListener and setting notifyOnOutputEmpty(true), making your SerialPortEventListener a monitor along the lines of

    class ExampleMonitor implements SerialPortEventListener {
      boolean condition;
    
      public synchronized serialEvent(SerialPortEvent ev) {
        condition = true;
        notifyAll();
      }
    
      public synchronized void awaitCondition() throws InterruptedException {
        while(!condition) wait();
        condition = false;
      }
    

    and then do

    myExampleMonitor.awaitCondition() instead of the sleep in the transmit thread.

    See http://rxtx.qbang.org/wiki/index.php/Event_based_two_way_Communication for the inverse use of events (note that there, there is no monitor and no waiting; instead, the work is done in the listener/callback.)