0

I have already create a Modbus server program in my PC with the Modbus library from Qt, using as example the code that is provided for the platform (I don't want a program with graphical interface so my program is not totally like the example) and I'm capable of read and write values in the holding registers using a simulator (I can see the exchange of information is ok in the simulation program).

My problem is that, once the simulator has written a new value in a holding register of my PC, I need to write this value in a file and for doing this I need to access the value that has been written in the ModbusDataUnit that I created.

I'm trying to do this using the method of the class QModbusServer.

bool QModbusServer::data(QModbusDataUnit::Register Type, quint16 address, quint16 *data) const

but the program crash when the signal DataWritten calls the slot that I defined in which I'm using the method. Can anyone tell me why this is not working and how to use this method? Thanks!!

I share the code (see myserver.cpp, the slot named showData please)

myserver.h

#ifndef MYSERVER_H
#define MYSERVER_H

#include <QObject>
#include <QModbusDevice>
#include <QModbusTcpServer>


class myServer: public QObject
{
    Q_OBJECT

public:
    explicit myServer(QObject *parent=0);
    ~myServer();
    void CreateServer();

signals:

    void error_signal(QString errorString);


public slots:

private:
    QModbusTcpServer * server;

private slots:

    void handleDeviceError(QModbusDevice::Error newError);

    void onStateChanged(int state);

    bool ShowData(QModbusDataUnit::RegisterType table, int address, quint16 size);


};

#endif // MYSERVER_H

myserver.cpp

#include "myserver.h"
#include <QModbusServer>
#include <QModbusDevice>
#include <QModbusTcpServer>
#include <QVariant>
#include <QModbusDataUnit>
#include <QDebug>
#include <QVector>
#include<iostream>

using namespace std;

myServer::myServer(QObject *parent) :            //Definimos el constructor de la clase
    QObject(parent), server(nullptr)             //El constructor directamente llama a la función para crear el servidor y ponerlo a la escucha
{
   CreateServer();
}

myServer::~myServer()          //Definimos el destructor de la clase
{
    if (server)
    {
        server->disconnectDevice();
    }
    delete server;
}

void myServer::CreateServer()  //Definimos la función para crear el servidor
{
   if (server)
   {
       server->disconnect();
       delete server;
       server=nullptr;
   }

   QModbusTcpServer* server;
   server= new QModbusTcpServer();

   if(!server)   //Comprobamos que se crea bien el servidor
   {
     qDebug()<<"Could not create modbus slave";
   }
   else
   {
       //Ahora hay que modelar unos registros de memoria, como si fueran los del plc
       QModbusDataUnitMap reg;  //Creamos la unidad de datos, se usa para las operaciones de lectura y escritura

       //Los tipos de registros soportados son los que aparecen en la enumeración, los insertamos en la unidad de memoria
       reg.insert(QModbusDataUnit::Coils, { QModbusDataUnit::Coils, 0, 10 });                     //This type of data can be alterable by an application program.
       reg.insert(QModbusDataUnit::DiscreteInputs, { QModbusDataUnit::DiscreteInputs, 0, 10 });   //This type of data can be provided by an I/O system.
       reg.insert(QModbusDataUnit::InputRegisters, { QModbusDataUnit::InputRegisters, 0, 10 });     //This type of data can be provided by an I/O system.
       reg.insert(QModbusDataUnit::HoldingRegisters, { QModbusDataUnit::HoldingRegisters, 0, 10 }); //This type of data can be alterable by an application program.

       //Sets the registered map structure for requests from other ModBus clients to map.
       //The register values are initialized with zero. Returns true on success; otherwise false
       //Calling this function discards any register value that was previously set.

       bool ok;
       ok=server->setMap(reg);

       if(!ok) //Comprobamos que el mapa de datos se ha creado correctamente
       {
           qDebug() << "SetMap error";
       }
       else
       {
           cout<<"Unit map created"<<endl;
       }

       //Conectamos  las señales con sus slots
       //Nota: los slots no se llaman, se activan solos si se emite la señal que los llama

       //Estado de la conexión. Asociamos la señal stateChanged con el slot onStateChanged.
       connect(server, &QModbusServer::stateChanged, this, &myServer::onStateChanged);

       // Error que ocurre: cuando ocurra un error (se emita la señal errorOccurred), se activa el slot y muestra por pantalla el error.
       connect(server, &QModbusServer::errorOccurred, this, &myServer::handleDeviceError);

       //Cada vez que se escribe un valor en la memoria se emite la señal dataWritten (que es una señal predefinida) Y lo que hacemos
       //es conectar esa señal con que aparezcan ciertos datos en pantalla
         connect(server, &QModbusServer::dataWritten, [&] (QModbusDataUnit::RegisterType table, int address, quint16 size)
           {
               qDebug() << "onDataWritten: table: " << table
                        << " | " << "address: " << address
                        << " | " << "size: " << size
                        << endl;
           }
          );

       connect(server, &QModbusServer::dataWritten,this, &myServer::ShowData);

       //Indicamos los parámetros de la conexión
       server->setConnectionParameter(QModbusDevice::NetworkAddressParameter,"192.168.0.121");  //Aquí hay que meter la ip del SERVIDOR (la nuestra)
       server->setConnectionParameter(QModbusDevice::NetworkPortParameter,502);                 //Puerto por el que se comunica
       server->setServerAddress(1);                                                             //Esta dirección es por si hay más servidores
       qDebug()<<"Modbus slave was created successfully";
   }

   //Esta función conecta el servidor, quiere decir que lo pone a la escucha. La función devuelve el estado de conexión por pantalla.
   server->connectDevice();
}

void myServer::handleDeviceError(QModbusDevice::Error newError)
{
    if(newError == QModbusDevice::NoError || !server)
    {
        emit error_signal(server->errorString());   //en el ejemplo no se emite ninguna señal
        qDebug() <<server->errorString();
        cout<<"Esta emitiendo señal de error"<<endl;
    }
}

//El slot onStateChanged se activa cuando se emite la señal stateChanged (que es una señal predefinida). Saca por pantalla el estado de la conexión.
void myServer::onStateChanged(int state)
{
    if(state == QModbusDevice::UnconnectedState)
    {
       // emit stateChanged_signal(0);  //NO HACE FALTA EMITIR NINGUNA SEÑAL, ADEMÁS QUE ÉSTA NO ESTÁ ASOCIADA CON NINGÚN SLOT
         qDebug()<<QString("OFF state");
       qDebug() <<QString(server->errorString()+"prueba");
    }
    else if(state == QModbusDevice::ConnectedState)
    {

        //emit stateChanged_signal(1);
        qDebug()<<QString("ON state");
    }
}


bool myServer::ShowData(QModbusDataUnit::RegisterType table, int address, quint16 size)
{

    for(int i=0; i<size; i++)
   {
       quint16 value;
       value=0;
       bool key_1;
       switch (table)
       {

       case 2:
           server->data(QModbusDataUnit::Coils, address+i, &value);
           qDebug()<<value<<endl;
       break;
       case 4:
           cout<<"Por lo menos sabe que es el caso 4"<<endl;
           key_1=server->data(QModbusDataUnit::HoldingRegisters, address+i, &value); //THIS FUNCION MAKES THE PROGRAM CRASH
           if(!key_1)
           {
               qDebug()<<"esto no funciona"<<endl;
           }

       break;
       default:
           qDebug()<<QString("Could not get value");
           break;
       }
    }
    return true;
}

main.cpp

#include <QCoreApplication>
#include <QLoggingCategory>
#include<iostream>
using namespace std;

#include <QObject>
#include <QDebug>
#include <QVector>
#include <QtSerialBus>
#include <QModbusClient>
#include <QModbusDataUnit>
#include <QModbusResponse>
#include <QModbusDevice>

#include "myclient.h"
#include "myserver.h"

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);
    //MyClient mClient;
    myServer mServer;
    return a.exec();
}

I'm trying to use the QModbusServer::data method as in the Qt ModbusServer example but the example is to large and It's contain a lot of code about Widgets. I'm not an expert and I get loss when trying to diferenciate the widgets part from the modbus part.

YScharf
  • 1,638
  • 15
  • 20
  • 1
    Why do you declare a locally scoped `QModbusTcpServer* server;` in `myServer::CreateServer` -- that will shadow the member variable of the same name? Are you sure you're not dereferencing a null pointer `server` somewhere? Haven't looked through the entire code so I may have missed something. – G.M. Dec 15 '22 at 12:03
  • At the beginning, you check the `server` value and it it's not zero you delete it first. But the value was never initialized so you probably try to delete `server` pointing at some random memory address that never have been allocated first. – Fareanor Dec 15 '22 at 12:15
  • Thanks for the comments, both of them have been helpfull. – lidia la cal Dec 19 '22 at 16:34
  • bool QModbusServer::data method was not the problem, It's just that I made some mistakes in defining the class (I declare twice the atribute and have some problems initializing the server). I share the functional code with the corrections in the answer – lidia la cal Dec 19 '22 at 16:38

1 Answers1

0

I share the functional code for a tcp modbus server. Once that a value has been written in the server the value is dumped in a file.

myserver.h

#ifndef MYSERVER_H
#define MYSERVER_H

#include <QObject>
#include <QModbusDevice>
#include <QModbusTcpServer>
#include<iostream>

using namespace std;

class myServer: public QObject
{
    Q_OBJECT

public:  //lo público lo puedo llamar desde el main
    explicit myServer()
    {
        server= new QModbusTcpServer();
    };

    void CreateServer();

signals:

    void error_signal(QString errorString);


public slots:

private:  //lo privado solo se puede llamar desde la clase
    QModbusTcpServer *server;  //Definimos el atributo de la clase
    string currentDateTime();

private slots:

    void handleDeviceError(QModbusDevice::Error newError);

    void onStateChanged(int state);

    bool ShowData(QModbusDataUnit::RegisterType table, int address, quint16 size);


};

#endif // MYSERVER_H

myserver.cpp

#include "myserver.h"
#include <QModbusServer>
#include <QModbusDevice>
#include <QModbusTcpServer>
#include <QVariant>
#include <QModbusDataUnit>
#include <QDebug>
#include <QVector>
#include<iostream>
#include<fstream>
#include <time.h>

using namespace std;

void myServer::CreateServer()  //Definimos la función para crear el servidor, es una función privada, solo podemos llamarla desde dentro de la clase
{

   if(!server)   //Comprobamos que se crea bien el servidor
   {
     qDebug()<<"Could not create modbus slave";
   }
   else
   {
       //Ahora hay que modelar unos registros de memoria, como si fueran los del plc
       QModbusDataUnitMap reg;  //Creamos la unidad de datos, se usa para las operaciones de lectura y escritura

       //Los tipos de registros soportados son los que aparecen en la enumeración, los insertamos en la unidad de memoria
       reg.insert(QModbusDataUnit::Coils, { QModbusDataUnit::Coils, 0, 10 });                     //This type of data can be alterable by an application program.
       reg.insert(QModbusDataUnit::DiscreteInputs, { QModbusDataUnit::DiscreteInputs, 0, 10 });   //This type of data can be provided by an I/O system.
       reg.insert(QModbusDataUnit::InputRegisters, { QModbusDataUnit::InputRegisters, 0, 10 });     //This type of data can be provided by an I/O system.
       reg.insert(QModbusDataUnit::HoldingRegisters, { QModbusDataUnit::HoldingRegisters, 0, 10 }); //This type of data can be alterable by an application program.

       //Sets the registered map structure for requests from other ModBus clients to map.
       //The register values are initialized with zero. Returns true on success; otherwise false
       //Calling this function discards any register value that was previously set.


       bool ok;
       ok=server->setMap(reg);

       if(!ok) //Comprobamos que el mapa de datos se ha creado correctamente
       {
           qDebug() << "SetMap error";
       }
       else
       {
           cout<<"Unit map created"<<endl;
       }

       //Conectamos  las señales con sus slots

       //Estado de la conexión. Asociamos la señal stateChanged con el slot onStateChanged.
       connect(server, &QModbusServer::stateChanged, this, &myServer::onStateChanged);

       // Error que ocurre: cuando ocurra un error (se emita la señal errorOccurred), se activa el slot y muestra por pantalla el error.
       connect(server, &QModbusServer::errorOccurred, this, &myServer::handleDeviceError);

       //Cada vez que se escribe un valor en la memoria se emite la señal dataWritten (que es una señal predefinida) Y lo que hacemos
       //es conectar esa señal con que aparezcan ciertos datos en pantalla
         connect(server, &QModbusServer::dataWritten, [&] (QModbusDataUnit::RegisterType table, int address, quint16 size)
           {
             /*
               qDebug() << "onDataWritten: table: " << table
                        << " | " << "address: " << address
                        << " | " << "size: " << size
                        << endl;
                        */
           }
          );

       connect(server, &QModbusServer::dataWritten,this, &myServer::ShowData);

       //Indicamos los parámetros de la conexión
       server->setConnectionParameter(QModbusDevice::NetworkAddressParameter,"192.168.0.121");  //Aquí hay que meter la ip del SERVIDOR (la nuestra)
       server->setConnectionParameter(QModbusDevice::NetworkPortParameter,502);                 //Puerto por el que se comunica
       server->setServerAddress(1);                                                             //Esta dirección es por si hay más servidores
       qDebug()<<"Modbus slave was created successfully";

   }

   //Esta función conecta el servidor, quiere decir que lo pone a la escucha. La función devuelve el estado de conexión por pantalla.
   server->connectDevice();
}

void myServer::handleDeviceError(QModbusDevice::Error newError)
{
    if(newError == QModbusDevice::NoError || !server)
    {
        emit error_signal(server->errorString());   //en el ejemplo no se emite ninguna señal
        qDebug() <<server->errorString();
        cout<<"Esta emitiendo señal de error"<<endl;
    }
}

//El slot onStateChanged se activa cuando se emite la señal stateChanged (que es una señal predefinida). Saca por pantalla el estado de la conexión.
void myServer::onStateChanged(int state)
{
    if(state == QModbusDevice::UnconnectedState)
    {
       qDebug()<<QString("OFF state");
       qDebug() <<QString(server->errorString()+"prueba");
    }
    else if(state == QModbusDevice::ConnectedState)
    {
        qDebug()<<QString("ON state");
    }
}


bool myServer::ShowData(QModbusDataUnit::RegisterType table, int address, quint16 size)
{
   string date;
   date=currentDateTime();
   ofstream data_file;
   data_file.open("data_file.txt", ios::app);

    for(int i=0; i<size; i++)
   {
       quint16 value;
       value=0;
       bool key_1;
       switch (table)
       {
       case 2:
           server->data(QModbusDataUnit::Coils, address+i, &value);
           qDebug()<<value<<endl;
       break;
       case 4:
           key_1=server->data(QModbusDataUnit::HoldingRegisters, address+i, &value);
           if(!key_1)
           {
               qDebug()<<"esto no funciona"<<endl;
           }
           else
           {
               qDebug()<< "The value is "<<value<<endl;
               data_file<<value<<";"<<date<<";"<<endl;  //Aqui está sobre escribiendo valores
           }

       break;
       default:
           qDebug()<<QString("Could not get value");
           break;
       }
    }
    data_file.close();
    return true;
}

string myServer::currentDateTime()
{
    time_t     now = time(0);
    struct tm  tstruct;
    char       buf[80];
    tstruct = *localtime(&now);
    // Visit http://en.cppreference.com/w/cpp/chrono/c/strftime
    // for more information about date/time format
    strftime(buf, sizeof(buf), "%Y-%m-%d.%X", &tstruct);

    return buf;
}

main.cpp

#include <QCoreApplication>
#include <QLoggingCategory>
#include<iostream>
using namespace std;

#include <QObject>
#include <QDebug>
#include <QVector>
#include <QtSerialBus>
#include <QModbusClient>
#include <QModbusDataUnit>
#include <QModbusResponse>
#include <QModbusDevice>

#include "myclient.h"
#include "myserver.h"

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);
    //MyClient mClient;
    myServer mServer;
    mServer.CreateServer();
    return a.exec(); //Usamos a.exec() en vez de 0 para que el programa no termine si no que siga corriendo.
}