Fazecast / jSerialComm

Platform-independent serial port access for Java
GNU Lesser General Public License v3.0
1.35k stars 287 forks source link

Problem creating Thread for writing and reading in javafx project #485

Closed Brunosalata closed 1 year ago

Brunosalata commented 1 year ago

Hi, everyone! This is my first issue in my life. hahaha Thanks for any help.

I'm building a javafx project to communicate with arduino. JSerialComm did the communication well, but I don't know how to include a Thread to expose a realtime value in a label in the interface.

I'm doing it in Java. the arduino programming was made to return this value, if it receives the char "1". So, I need to send "1" via serial and read the return in parallel with the main routine of the software.

For the connection, I created a comboBox with the list of available ports and a "Connect" button. I select the port on the CB and connect to the port with that name (SerialPort.getCommPort).

The application starts exclusively by loading the comboBox, the rest happens by clicking the buttons. I don't know where to implement the Thread because it always results in <Cannot invoke "com.fazecast.jSerialComm.SerialPort.getOutputStream()" because "this.port" is null>

What option do I have to make it work?

class FXMLController



import java.io.*;
import com.fazecast.jSerialComm.*;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import java.net.URL;
import java.util.ResourceBundle;
import java.util.Scanner;

``` public class MainSceneController implements Initializable {

 @FXML
    private ComboBox cbPorts;
    @FXML
    private Button btnConnect;
    private SerialPort port;

    /**
         * Thread que faz a leitura da posição em tempo real
         */
//    RunnableThread RTthread = new RunnableThread("RealtimeDisplay"); //Realtime force and position thread
//    Thread rtthread = new Thread(RTthread);

    @Override
    public void initialize(URL url, ResourceBundle resourceBundle) {
        portConnectionList();

    }

    /**
     * Método de abertura e fechamento de conexão
     */

    @FXML
    private void connect() {

        //Método de abertura e fechamento de conexão serial
        if (btnConnect.getText().equals("Conectar")) {
            port = SerialPort.getCommPort(cbPorts.getSelectionModel().getSelectedItem().toString());
            if (port.openPort()) {
                btnConnect.setText("Desconectar");
                cbPorts.setDisable(true);
                port.setBaudRate(115200);
//                rtthread.start(); //start da thread RealtimeDisplay, configurada na classe RunnableThread, para leitura de valor de força e posição
            }
        } else {
            port.closePort();
            cbPorts.setDisable(false);
            btnConnect.setText("Conectar");
        }

    }
    /**
     * Método de listagem de portas Seriais disponíveis dentro do ComboBox (cbPorts)
     */
    private void portConnectionList() {

        SerialPort[] portNames = SerialPort.getCommPorts();
        for (SerialPort portName : portNames) {
            cbPorts.getItems().add(portName.getSystemPortName());
        }
    }
    /**
     * Método genérico para injeção do output, aplicável para os diferentes processos port.OutputStream() que requeiram uma string
     *
     * @param stg
     */
    private void outputInjection(String stg) {
        PrintWriter output = new PrintWriter(port.getOutputStream(), true);
        output.print(stg);
        output.flush();
    }
    /**
     * Método que recebe o valor real do input
     * @return String
     */
    private String inputValue() {
        Scanner s = new Scanner(port.getInputStream());
        return s.nextLine();
    }

}

Class RunnableThread

import com.fazecast.jSerialComm.SerialPort;
import javafx.scene.control.Label;

import java.io.PrintWriter;
import java.util.Scanner;

public class RunnableThread implements Runnable {

    public Label lbPositionView, lbForceView;
    public String name;
    public SerialPort port;

    public RunnableThread(String name) {
        this.name = name;
    }

    @Override
    public void run() {

        while(true) {

            try {
                Thread.sleep(1000);
                outputInjection("1");
                Thread.sleep(20);
                lbForceView.setText(inputValue());
                outputInjection("2");
                Thread.sleep(20);
                lbPositionView.setText(inputValue());
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
    }
    private void outputInjection(String stg) {
        PrintWriter output = new PrintWriter(port.getOutputStream(), true);
        output.print(stg);
        output.flush();
    }
    private String inputValue() {
        Scanner s = new Scanner(port.getInputStream());
        return s.nextLine();
    }
}
Soulsurfering commented 1 year ago

Hi Brunosalata, this doesn't seem to relate to a JavaFX problem. Your code uses the wrong string in the method: SerialPort.getCommPort(cbPorts.getSelectionModel().getSelectedItem().toString())

Of cause, the systemName of the port (getSystemPortName) is more practical in the combo box, but only the value of this method should work: getDescriptivePortName​() The API documentation states: "Gets a descriptive string representing this serial port or the device connected to it."

Your "connect" method might check, if your retrieve a port object, before calling port.openPort()

kind regards Torge

Brunosalata commented 1 year ago

Hi, Soulsurfering! Thanks so much for answer me.

I understood what you said, I tried changing the code in different ways to fit the method, but it didn't work. Most likely due to my own fault.

could you show me how i can make this change?

Something I did here was to start the "Initialize" method with 'port = SerialPort.getCommPort("0");' still Thread didn't work. But, again, certainly due to my lack of knowledge.

Do you think it makes sense?

What I see:

Soulsurfering commented 1 year ago

Hi Brunosalata,

I need to applogize: actually "getSystemPortName()" seems to be the right string to get a port instance, at least on my linux system.

Did you add the "null" check on the port variable?

I still don't understand, why you send "1" and "2" to the output stream and try to read 20ms later the input stream.

regards, soulsurfering

Brunosalata commented 1 year ago

Hi, Soulsurfering!

Yes, I added the null check like you sad. But I dont think it's the problema. Because the communication is working fine, but only on the main Thread.. I don't know how to create a structure to writing and reading data as the same time the application is running. Most time I try to create a Thread or Task, the application breaks with the error "this.port = null", because my Thread code is another class (RunnableThread) and I dont't know how to import the MainSceneController there, to recognise the port name or the end of my outputs

I think It was confusing, right? hahaha just let me know and I'll try again.

Just clarifying 1- I'm building an application that communicates with an arduino, which controls a traction device. When I send the string "1", the arduino returns me the value of the force of the equipment, when I send "2", it returns the displacement of the axis where the force is made. I need this data to be read constantly without crashing the application or making the buttons unfeasible (after openPort, sure.

2- Others outputs, like "3", "4", "5" are done from the press of a button, and it is going well (but I need buttons to do it). When I output "1" or "2" in a button, the arduino returns the right value, but I need to press all the time. It's not automatic.

Brunosalata commented 1 year ago

I think I'm almost there! I started portDescription as "0", just to don't crash, it won't be a problem propably.

After it, I inserted a simple Thread in the "connect()" method and I get the values!! But only printing in my console. If I try to setText() the respective label, it doesn't works..

PS: I had to change the output to "1x" and "2x" for some tests in arduino code (nothing else has changed) PS 2: I made a method to Input and Output the values, so, I just call inputValue() and outputInjection(String stg)

 @Override
     public void initialize(URL url, ResourceBundle resourceBundle) {
         port = SerialPort.getCommPort("0");
         portConnectionList();
         lbCurrentData.setText(String.valueOf(systemDate));
     }
     /**
      * Método que define o algorítmo da Thread que faz a leitura dinâmica da Força e da Posição
      */
     private void FPReadingThread() {
         try{
             Thread.sleep(1000);
             outputInjection("1x");
             Thread.sleep(20);
             //String impF = inputValue();
             System.out.println(inputValue());
             //lbForceView.setText(impF);
             outputInjection("2x");
             Thread.sleep(20);
             //String impP = inputValue();
             System.out.println(inputValue());
             //lbPositionView.setText(impP);
             Thread.sleep(1000);
         } catch (InterruptedException e) {
             throw new RuntimeException(e);
         }
     }
     /**
      * Método de abertura e fechamento de conexão
      */
     @FXML
     private void connect() {
         if (port != null) {
             //Método de abertura e fechamento de conexão serial
             if (btnConnect.getText().equals("Conectar")) {
                 port = SerialPort.getCommPort(cbPorts.getSelectionModel().getSelectedItem().toString());
                 if (port.openPort()) {
                     btnConnect.setText("Desconectar");
                     cbPorts.setDisable(true);
                     port.setComPortParameters(115200, 8, SerialPort.ONE_STOP_BIT, SerialPort.NO_PARITY);
                     port.setComPortTimeouts(SerialPort.TIMEOUT_READ_BLOCKING | SerialPort.TIMEOUT_WRITE_BLOCKING, 50, 50);
                     port.setFlowControl(SerialPort.FLOW_CONTROL_DISABLED);

                     Thread t = new Thread(() -> {
                         while (true) {
                             FPReadingThread();
                         }
                     });
                     t.start();
                 }
             } else {
                 port.closePort();
                 cbPorts.setDisable(false);
                 btnConnect.setText("Conectar");
             }
         } else{
             System.out.println("Nenhuma porta encontrada!");;
         }
     }
Brunosalata commented 1 year ago

Just to make this topic DONE, a final post.

I solved it inserting the Platform.runLater() in the FPReadingThread() method at the same I call this method in the connection() one.

So, if you are doing your JavaFX project to get data from serial port, it's a good alternative to you.

Just an observation: I implemented autoconnect getting the portName from database and send my serial data to database too, but not for this UI problem. I'll use it later.

My SceneController code becomes this

private void FPReadingThread() {

        try{
            Thread.sleep(2000);
            outputInjection("1x");
            Thread.sleep(20);
            String impF = inputValue();
//            System.out.println(impF);
            outputInjection("2x");
            Thread.sleep(20);
            String impP = inputValue();
//            System.out.println(impP);

            //Atualização da UI pela Thread
            Platform.runLater(() -> {
                lbForceView.setText(impF);
                lbPositionView.setText(impP);
            });

            SystemVariable sysVar = new SystemVariable(1,Double.valueOf(impF),Double.valueOf(impP));
            systemVariableDAO.update(sysVar);

        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * Método de conexão automática
     */
    @FXML
    private void autoConnect(){

        SystemParameter sysPar = systemParameterDAO.find();
        System.out.println("Conectado");
        if(sysPar==null){
            sysPar.setPortName(cbPorts.getSelectionModel().getSelectedItem().toString());
            sysPar.setSystemLanguage("pt");
            sysPar.setSoundOn("false");
            systemParameterDAO.create(sysPar);
        }
        port = SerialPort.getCommPort(sysPar.getPortName());
        System.out.println(port);
        if (port.openPort()) {
            btnConnect.setText("Conectado");
            cbPorts.setDisable(true);
            port.setComPortParameters(115200, 8, SerialPort.ONE_STOP_BIT, SerialPort.NO_PARITY);
            port.setComPortTimeouts(SerialPort.TIMEOUT_READ_BLOCKING | SerialPort.TIMEOUT_WRITE_BLOCKING, 50, 50);
            port.setFlowControl(SerialPort.FLOW_CONTROL_DISABLED);

            // Thread para solicitar Posição e Força e atualizar lbForceView e lbPositionView
            Thread t = new Thread(() -> {

                while (true) {
                    FPReadingThread();
                }
            });
            t.start();

        } else {
            port.closePort();
            btnConnect.setText("Conectar");
        }
    }

Thanks so much for any help!