QSerialPort closed when access from another class



  • Hi,
    In a Qt 5 application I need to access the QSerialPort from a class/form different from the one in which the serialport is declared. In other words, In my MainWindow I have:
    mainwindow.h:

    public:
        explicit MainWindow(QWidget *parent = 0);
        ~MainWindow();
       QSerialPort *serialport;   // Is this the right place for the declaration?
    
    

    mainwindow.cpp:

    MainWindow::MainWindow(QWidget *parent) :
        QMainWindow(parent),
        ui(new Ui::MainWindow)
    {
        serialport = new QSerialPort(this);
        connect(serialport, SIGNAL(readyRead()), this, SLOT(main_readData()));
    ...
    

    Now, I need to write data through the serialport from another class:
    serial.cpp:

    QByteArray ba;
    ba.resize(50);
    ...
    MainWindow m;
    const qint64 bytesWritten = m.serialport->write(ba);
    

    The problem is: no errors during the build, but when the "write" function is called, the following error appears:
    "Permission error while locking the device", and the output console says: "QIODevice::write (QSerialPort): device not open".
    It seems like the serialport is close when it is called from a class or form that is not the MainWindow.
    How can I have the serialport accessible from everywhere?
    How can I fix it? Anyone could give any advice?
    Thank you.


  • Lifetime Qt Champion

    Hi,

    How are you passing the data through to the serial port ?

    What OS are you on?



  • @douglas not having the whole source code available, it looks like from your snippets that you're creating 2 MainWindow objects, so that's the reason of your serial port conflict.


  • Moderators

    @douglas Agree with @Pablo-J-Rogina and want to add:

    MainWindow m; // Are you aware that you are creating here a new temporary MainWindow instance?!
    const qint64 bytesWritten = m.serialport->write(ba);
    

    From design point of view this is really really ugly code!
    Using MainWindow to access serial port like this is a bad idea. First: serialport should not be public member of MainWindow (define public interface instead). Second: MainWindow should not be the one managing serial port as it is a UI class.



  • @jsulm , @Pablo-J-Rogina thanks for your answer! I am understanding that QSerialPort has not to be a public member of MainWindow. Where should I declare it correctly in order to be accessible from the whole application? @jsulm what do you mean for "define a public interface instead"?
    When I did:

    MainWindow m; // Are you aware that you are creating here a new temporary MainWindow instance?!
    const qint64 bytesWritten = m.serialport->write(ba);
    

    was because I couldn't find the way to have access to functions or serial port module declared in another class.
    Which should be the way to manage this? I am sorry, I am a beginner.
    I analized the "terminal example" from official documentation, but in that example the serial port is used from one module only, it is not my case.
    I did not mentioned before: I am cross-compiling for my device, and the host machine runs Ubuntu.
    Thanks again!


  • Moderators

    @douglas With public API I mean something like (in MainWindow):

    public:
        void send(QByteArray &data);
    

    So, you would not use QSerialPort directly outside of MainWindow, but instead call its send method.

    Where to put your serial port depends on your architecture. You could implement a manager class which hides the QSerialPort and instead provides a simple to use API to send and receive data over serial port.



  • @jsulm I change my code in the following way:

    mainwindow.h:

    class MainWindow : public QMainWindow
    {
        Q_OBJECT
    public:
        explicit MainWindow(QWidget *parent = 0);
        ~MainWindow();
        void send_serialport(QByteArray &data);
    ...
    private:
        QSerialPort *serialport;
    

    mainwindow.cpp:

    void MainWindow::send_serialport(QByteArray &data)
    {
        const qint64 bytesWritten = serialport->write(data);
    }
    

    class seriale.cpp:

    void seriale::function(void)
    {
    QByteArray ba;
        ba.resize(50);
    // fill 'ba'
    MainWindow m;
    m.send_serialport(ba);
    

    I build without errors, but when the "function" calls "send_serialport(ba)" a messagebox error appears in the UI saying:

    "Permission error while locking the device"
    

    and when I click "OK" on it, the console shows:

    QIODevice::write (QSerialPort): device not open
    

    Are there any other solutions I could try?
    Thank you!


  • Qt Champions 2017

    Hi @douglas,

    you should study http://doc.qt.io/qt-5/qtserialport-terminal-example.html

    This example is also available in Creator.


  • Moderators

    @douglas You are still creating a LOCAL MainWindow instance!

    void seriale::function(void)
    {
    QByteArray ba;
        ba.resize(50);
    // fill 'ba'
    MainWindow m; // !!! You again create a new MainWindow instance!
    m.send_serialport(ba);
    

    Your seriale class should not know ANYTHING about MainWindow - this is simply bad design. And a UI class like MainWindow should not be the manager for serial port communication. Put your serial port in another class.



  • @douglas said in QSerialPort closed when access from another class:

    open

    Hi @douglas ,

    I am going to sound presumptuous here because I do not know if that is your exact code in the snippet but I do not see serialport->open (...) anywhere. I would think that if you are getting the "device not open" error that could be the case.
    I use QSerialPort and QCanBusDevice in an extreme multi-threaded environment and commands can be written to the port from anywhere and the input is processed on the threads to keep the user interface free without issue.



  • @Buckwheat thank you for your reply. The "open" function is actually present in my code: for some reasons i forgot to paste it in the snipped I reported above, sorry!
    I changed a little my code in order to follow your suggestion, but I did not succeded yet.
    Now, I created a class "test_class" in which a declared the Serialport and other functions related to it:
    "test_class.h":

    class test_class : public QObject
    {
        Q_OBJECT
    
    public:
        explicit test_class(QObject *parent = 0);
        void serialport_init(void);
        void init_timer_RTS(void);
        void init_timer_gestione_seriale(void);
        void init_timer_lettura_s1(void);
        void InviaSuSeriale(QByteArray &data);
    
        QTimer *timer_disattivazione_RTS;
        QTimer *timer_seriale;
        QTimer *timer_lettura_S1;
    
    private:
        QSerialPort *serialport;   // serial port declaration
    ...
    

    "test_class.cpp":

    test_class::test_class(QObject *parent) : QObject(parent)
    {
    
    }
    
    
    
    /**
     * @brief serialport_manager::serialport_init
     */
    void test_class::serialport_init(void)
    {
    // this is called from MainWindow.cpp at the startup
        serialport = new QSerialPort(this);
        connect(serialport, SIGNAL(readyRead()), this, SLOT(readData()));
    
        serialport->setPortName("/dev/ttymxc2");
        serialport->setBaudRate(QSerialPort::Baud9600);
        serialport->setDataBits(QSerialPort::Data8);
        serialport->setParity(QSerialPort::NoParity);
        serialport->setStopBits(QSerialPort::OneStop);
        serialport->setFlowControl(QSerialPort::NoFlowControl);
        if (serialport->open(QIODevice::ReadWrite))   // open serialport
        {
            //showStatusMessage(tr("system status: OK"));
        }
        else
        {
            //showStatusMessage(tr("system status: COM error"));
        }
    
    /**
     * @brief serialport_manager::InviaSuSeriale
     * @param data
     */
    void test_class::InviaSuSeriale(QByteArray &data)
    {
        const qint64 bytesWritten = serialport->write(data);
    
        timer_disattivazione_RTS->start(bytesWritten);
    }
    

    In my MainWindow class I call the serialport_init function defined in the "test_class". For debug purposes, I added a pushButton that calls a function (defined always in test_class) that write through the serial port:
    MainWindow.cpp:

    void MainWindow::on_pushButton1_clicked()
    {
    QByteArray ba;
    ba.resize(50);
    /* here I fill the ba QByteArray */
    test_class tc;
    tc.InviaSuSeriale(ba);   // defined in the "test_class"
    }
    

    When I push the pushButton, nothing is sent to the serialport and the application is killed (segmentation fault error)!
    Anyone could give some advice in order to solve the issue ore give an example to analize? I think there is something wrong in the basic structure of the program.
    Thanks a lot!



  • @douglas to quote jsulm :

    @jsulm said in QSerialPort closed when access from another class:

    @douglas You are still creating a LOCAL [...] instance!

    void MainWindow::on_pushButton1_clicked()
    {
    QByteArray ba;
    ba.resize(50);
    /* here I fill the ba QByteArray */
    test_class tc;
    tc.InviaSuSeriale(ba);   // defined in the "test_class"
    }
    

    test_class tc; is local and therefore your init was not called for that instance of your class. -> Qserialport pointer to nothingness -> segfault error



  • @J.Hilk I see. The functions in test_class are declared as "public" in .h. Which is the correct way to call them from another class without falling in my previous error? Should I simply declare "test_class tc" out of the calling function (global declaration)?



  • @douglas the usual way to do this, would be to make test_class tc a memeber of your class (MainWindow in this case), that allows you to access it throught your MainWindow class without the need to create a new instance of it.



  • @J.Hilk said in QSerialPort closed when access from another class:

    @douglas the usual way to do this, would be to make test_class tc a memeber of your class (MainWindow in this case), that allows you to access it throught your MainWindow class without the need to create a new instance of it.

    Can you give a very basic example on how to implement this?
    Thanks!



  • @douglas

    //.h
    #include "test_class.h"
    class MainWindow : public QMainWindow
    {
        Q_OBJECT
    ...
    ...
    
    private:
       test_class tc;
    }
    
    //.cpp 
    MainWindow::MainWindow(QWidget *parent) :
        QMainWindow(parent),
        ui(new Ui::MainWindow)
    {
        ....
        tc.serialport_init();
    }
    
    
    void MainWindow::on_pushButton1_clicked()
    {
        QByteArray ba;
        ba.resize(50);
    /* here I fill the ba QByteArray */
        
        tc.InviaSuSeriale(ba); 
    }
    


  • @J.Hilk thanks a lot. If a "test_class" should be "visible" from 2 or more classes (say MainWindow and Class2) have I to declare

    private:
       test_class tc;
    

    in both MainWindow.h and Class2.h?



  • @douglas
    ok, now it's getting tricky

    here's what I usually end up doing

    Let's asume 3 classes,

    • class1 = your Mainwindow
    • class2 = your Serialport communication
    • class3 = some other class who wants to send data via serial port

    If class 2 and class 3 objects are both members of class1, than I would suggest SIGNAL/SLOT mechanism to connect class3 Signals to class 2 slots, the connect statement is to be made in class1

    if class3 and class1 are siblings, hirachily, than connect class1 to class 3 via signal slot or bump class2 up and make it also a sibling and connet class1 to class 2 via SignalSlot and class3 to class2 via SignalSlot

    There are many, many other ways of course,

    However do not create 2 seperate objects of class2, normaly that would be fine, but you're trying to access hardware at the end of the line. -> only the instance that is first to be created/connected will actually be able to communictae with the hardware.


Log in to reply
 

Looks like your connection to Qt Forum was lost, please wait while we try to reconnect.