4
votes

this will be my first post and I will do my best to be clear and concise. I've checked some of the other posts on this forum but was unable to find a satisfactory answer.

My question pertains to the use of JavaFX and the jSSC(java simple serial connection) library. I've designed a very simple GUI application that will host four different charts. Two of the charts will display readings from temperature and solar sensors for the past hour, while the other two display that data over an extended period -- a 14-hour period. Eventually I would like to make that more flexible and set the application to "sleep" when the readings become roughly zero (night).

How can I stream data to display this data in real time?

After referencing several sources online and from "JavaFX 8 Intro. by Example", I've been able to construct most of the serial connection class. I'm having trouble processing the data readings, so that it can be displayed on the chart.

public class SerialComm  implements SerialPortEventListener {
Date time = new Date();
SimpleDateFormat sdf = new SimpleDateFormat("mm");

boolean connected;
StringBuilder sb;
private SerialPort serialPort;

final StringProperty line = new SimpleStringProperty("");

//Not sure this is necessary
private static final String [] PORT_NAMES = {
    "/dev/tty.usbmodem1411", // Mac OS X
    "COM11", // Windows
};
//Baud rate of communication transfer with serial device
public static final int DATA_RATE = 9600;

//Create a connection with the serial device
public boolean connect() {
    String [] ports = SerialPortList.getPortNames();
    //First, Find an instance of serial port as set in PORT_NAMES.
    for (String port : ports) {
         System.out.print("Ports: " + port);
         serialPort = new SerialPort(port);
    }
    if (serialPort == null) {
        System.out.println("Could not find device.");
        return false;
    }

    //Operation to perform is port is found
    try {
        // open serial port
        if(serialPort.openPort()) {
             System.out.println("Connected");
        // set port parameters
        serialPort.setParams(DATA_RATE,
                SerialPort.DATABITS_8,
                SerialPort.STOPBITS_1,
                SerialPort.PARITY_NONE);
                serialPort.setEventsMask(SerialPort.MASK_RXCHAR);

        serialPort.addEventListener(event -> {
            if(event.isRXCHAR()) {
                try {
                    sb.append(serialPort.readString(event.getEventValue()));
                    String str = sb.toString();
                    if(str.endsWith("\r\n")) {
                            line.set(Long.toString(time.getTime()).concat(":").concat(
                                    str.substring(0, str.indexOf("\r\n"))));
                                                System.out.println("line" + line);

                        sb = new StringBuilder();
                    }
                } catch (SerialPortException ex) {
                    Logger.getLogger(SerialComm.class.getName()).log(Level.SEVERE, null, ex);                    }
            }            
        });
    }                          
} catch (Exception e) {
    System.out.println("ErrOr");
    e.printStackTrace();
        System.err.println(e.toString());
    }       
    return serialPort != null;
}

@Override
public void serialEvent(SerialPortEvent spe) {
    throw new UnsupportedOperationException("Not supported yet."); 
}

public StringProperty getLine() {
    return line;
}

}

Within the try block, I understand the port parameters, but the eventListener is where I am having difficulty. The significance of the stringbuilder is to append data the new data as it is read from the device. How will I account for the two sensor readings? Would I do that by creating separate data rates to differentiate between the incoming data from each sensor??

I hope that this is clear and that I've provided enough information but not too much. Thank you for any assistance.

-------------------------------UPDATE--------------------------

Since your reply Jose, I've started to make the additions to my code. Adding the listener within the JavaFX class, I'm running into some issues. I keep getting a NullPointerException, which I believe is the String[]data not being initialized by any data from the SerialCommunication class.

serialPort.addEventListener(event -> {
            if(event.isRXCHAR()) {
                try {
                    sb.append(serialPort.readString(event.getEventValue()));
                    String str = sb.toString();

                    if(str.endsWith("\r\n")) {
                            line.set(Long.toString(time.getTime()).concat(":").concat(
                                    str.substring(0, str.indexOf("\r\n"))));
                                                System.out.println("line" + line);
                        sb = new StringBuilder();
                    }
                } catch (SerialPortException ex) {
                    Logger.getLogger(SerialComm.class.getName()).log(Level.SEVERE, null, ex);
                }
            }            
        });
    }                          
} catch (Exception e) {
        System.err.println(e.toString());
    }       

I'm adding the time to the data being read. As Jose mentioned below, I've added tags to the data variables within the arduino code, I'm using: Serial.print("Solar:"); Serial.println(solarData);

Rough code of the JavaFx listener:

serialPort.getLine().addListener((ov, t, t1) -> {
        Platform.runLater(()-> {
            String [] data = t1.split(":");

            try {
                 //data[0] is the timestamp
                //data[1] will contain the label printed by arduino "Solar: data"

                switch (data[1]) {
                    case "Solar":
                        data[0].replace("Solar:" , "");
                        solarSeries.getData().add(new XYChart.Data(data[0], data[1]));
                        break;
                    case "Temperature":
                        temperatureSeries.getData().add(new XYChart.Data(data[0], data[1]));
                        break;
                }

Is the reason this code has NullPointerException a result of the String [] data array being uninitialized?

Exception Error

Ports: /dev/tty.usbmodem1411Connected Exception in thread "EventThread /dev/tty.usbmodem1411" java.lang.NullPointerException at SerialComm.lambda$connect$0(SerialComm.java:61) at SerialComm$$Lambda$1/1661773475.serialEvent(Unknown Source) at jssc.SerialPort$LinuxEventThread.run(SerialPort.java:1299)

2
Could you show the stacktrace of the exception?José Pereda
Please, edit your post and add the full exception there.José Pereda
The full stacktrace contains more information, and that would help tracking the error. Which line is 1299?José Pereda
That is why I'm having trouble tracking down the error -- there is no line 1299. Also, I'm not sure how to expand on the exception thrown. I checked some other forums on how to do that...I will keep looking. sry : /wellington
SerialPort is from jssc library: eventListener.serialEvent(new SerialPortEvent(portName, eventType, eventValue));. SerialComm is your class? Line 61? You could add some System.out.println() to your connect() method to check in which line you have the NPE.José Pereda

2 Answers

2
votes

The SerialPortEventListener defined in the jssc library allows listening for serial port events. One of those events is the RXCHAR event, that occurs when the Arduino board is sending some data and some bytes are on the input buffer.

event.getEventValue() returns an int with the byte count, and serialPort.readString(event.getEventValue()) get the String format from those bytes.

Note that this method does not return a full line, so you need to listen to carriage return and line feed characters. Once you find "\r\n", you can get the line, and reset the StringBuilder for the next one:

sb.append(serialPort.readString(event.getEventValue()));
String str=sb.toString();
if(str.endsWith("\r\n")){
    line.set(str.substring(0,str.indexOf("\r\n")));
    sb=new StringBuilder();
}

where line is an observable String:

final StringProperty line=new SimpleStringProperty("");

On the Arduino side, if you want to send values from different sensors at different rates, I suggest you define on the Arduino sketch some identification string for each sensor, and you print for each value the id of its sensor.

For instance, these will be the readings you will get with the serial event listener:

ID1,val1
ID1,val2
ID2,val3
ID1,val4
ID3,val5
...

Finally, on the JavaFX thread, define a listener to changes in line and process the String to get the sensor and the value. Something like this:

serial.getLine().addListener(
    (ObservableValue<? extends String> observable, String oldValue, String newValue) -> {
    Platform.runLater(()->{
        String[] data=newValue.split("\\,");
        if(data[0].equals("ID1"){
            // add to chart from sensor 1, value data[1]; 
        } else if(data[0].equals("ID2"){
            // add to chart from sensor 2, value data[1]; 
        } else if(data[0].equals("ID3"){
            // add to chart from sensor 3, value data[1]; 
        }
    });        
});

Note you need to add Platform.runLater(), since the thread that gets the data from serial port and updates line is not on the JavaFX thread.

0
votes

From my experience, on the Arduino side, add a comma or something to separate the different values when you print and when you receive that string in Java simply split that string by commas.

String[] stringSeparate = str.split(",");