QT QModbusTcpClient使用ModbusTcp协议与硬件通信实战例子

2023-12-25 12:10:53

? 1.需求

?? ?给了一个显示屏和显示屏的通信文档,用ModbusTcp协议与其通信,读取或者写入显示屏相应的内容,以满足项目需要
文档部分截图如下

屏幕如下图所示:

我需要写入改写其中的物料名称,待领料数量等,就是上位机与硬件通信

2.方案

1.常规方案,使用QTcpSocket

对于熟悉modbusTcp协议的,可以根据协议和通信文档完成信息的封装在发送给硬件即可

2.使用现有造好的轮子,QModbusTcpClient

需要熟悉QModbusTcpClient的接口,参数等,我们还没有用过
本来想请教项目组其他人的,他们让我先看下modbusTcp协议,熟悉一下,我看了好久,没有实际案例,有点抽象,他们也忙,没啥空,没时间了,还是用现有的轮子吧

然后我就决定采用QModbusTcpClient的方案进行吧

3.最后我决定采用QModbusTcpClient

采用QModbusTcpClient和QTcpSocket本质差不多,都是通过tcp进行通信,QTcpSocket需要自己封装相应的modbusTcp协议内容,发送给硬件服务器
QModbusTcpClient封装好了modbusTcp协议,你只需要理解好,然后填入相应的参数和调用相应的接口就行了,不需要理解modbusTcp协议,对于第一次解除的人来说,只是项目需要,不需要深入理解的,最合适不过了

4、实例代码解析

QModbusTcpClient像QTcpSocket一样,首先得连接到相应的服务器,通过setConnectionParameter函数设置要连接的ip和端口号,设置超时值,和服务器没有回应时的重发次数,最后连接上去即可,如下

 modbusClient = new QModbusTcpClient(this);
? ? //连接状态发生改变时处理函数(connect or discennect)
? ? modbusClient->setConnectionParameter(QModbusDevice::NetworkAddressParameter, ui->ip->text());
? ? modbusClient->setConnectionParameter(QModbusDevice::NetworkPortParameter, ui->port->text().toInt());
? ? modbusClient->setTimeout(500);
? ? modbusClient->setNumberOfRetries(3);//服务器没有回应的重发次数
? ? modbusClient->connectDevice();//


判断QModbusTcpClient有没有连接上服务器,使用它的信号判断即可

connect(modbusClient, &QModbusClient::stateChanged, this, &Widget::onStateChanged);
void Widget::onStateChanged(QModbusDevice::State newState)
{
? ? if (newState == QModbusDevice::ConnectedState) {
? ? ? ? qDebug()<<"QModbusDevice连接成功";
? ? ? ? ui->send->setDisabled(false);
? ? ? ?// 连接成功
? ?} else if (newState == QModbusDevice::UnconnectedState) {
? ? ? ?// 连接断开
? ? ? ? qDebug()<<"QModbusDevice连接断开";
? ? ? ? ui->send->setDisabled(true);
? ?}
}

接下来就是如何写入数据了,数据的封装采用QModbusDataUnit进行,然后将这个对象丢给QModbusClient的接口发送给服务器就行了
我这里使用QModbusDataUnit的默认构造函数初始化一个对象出来,第一个参数是寄存器类型,第二个参数是寄存器地址,第三个参数是寄存器数量
QModbusDataUnit(QModbusDataUnit::RegisterType type, int address, quint16 size)

例如我的例子中,寄存器的地址为10029,寄存器数量为14

QModbusDataUnit writeNameUnit = QModbusDataUnit(QModbusDataUnit::HoldingRegisters, ui->name_reg->text().toInt(), 14);

......

寄存器类型有如下选择,我这里只用到可以进行读写的保存寄存器

Constant

Value

Description

QModbusDataUnit::Invalid

0

由默认构造函数设置,不要使用

QModbusDataUnit::DiscreteInputs

1

离散输入:这种类型的数据可以由I/O系统提供

QModbusDataUnit::Coils

2

线圈:这种类型的数据可以通过应用程序进行更改

QModbusDataUnit::InputRegisters

3

输入寄存器:这种类型的数据可以由I/O系统提供。

QModbusDataUnit::HoldingRegisters

4

保持寄存器:这种类型的数据可以通过应用程序进行更改

接下来就是怎么为QModbusDataUnit设值的问题了,用这个函数设值就行,第一个参数为寄存器的索引,第二个参数为对应的寄存器的值
?

void QModbusDataUnit::setValue(int index, quint16 value)


值得注意的是,从它的值定义就可以看到,quint16,就是个十进制整形,不能设置为16进制的
如果是16进制的,得先把16进制转化成十进制的才行,他会在内部自己封装成16进制的,这个我也有点奇怪的点
如果是设置数字的,就简单了,根据文档中对应的字节数和寄存器数量即可,一个寄存器一般存两个字节

QModbusDataUnit unit = QModbusDataUnit(QModbusDataUnit::HoldingRegisters, ui->name_reg->text().toInt(), 2);
uint count = unit.valueCount();
if(count==1) {
?? ?unit.setValue(0, static_cast<quint16>(data.toInt(&ok,10)));
}
else {
?? ?int num0 = data.toInt()/65536;
?? ?int num1 = data.toInt()%65536;
?? ?unit.setValue(0, static_cast<quint16>(num0));
?? ?unit.setValue(1, static_cast<quint16>(num1));
}

我看文档里面写整形的数据时,整形数据最多占用四个字节,即两个寄存器,所以要根据实际情况来

如果要写入汉字的话,一般要求的是?字库:GBK的编码,我们将Unicode转GBK,然后在转16进制即可,如果数据不够长,可以将没有数据的寄存器填充完,根据硬件文档来就行了,一般填充‘0’或者‘F’,然后在根据每个寄存器可容纳的字节数,将完整的16进制数据分割,用于填充寄存器,一般每个寄存器可容纳的字节数=4个字节,所以每四个字符用空格分割下即可

如果要写入不包含汉字的字符串,一般要求的是?字库:ASCII编码,这个我们直接将字符串中的每个字符转换为16进制的即可。

    uint count = unit.valueCount();    
    if(type == DataType::GBK) {
         QByteArray byteGB2312 = utf8ToGB2312(data);
         for(uint i=byteGB2312.length(); i<count*4; i++) {
             byteGB2312.append("0");
         }
         qDebug()<<"byteGB2312:"<<byteGB2312;
         m_data = byteGB2312;
         int size = byteGB2312.size()/4-1;
         //用空格两个每组分隔开
         for(int i=1; i<=size; i++) {
             byteGB2312.insert(i*4+i-1, " ");
         }
         QByteArrayList list = byteGB2312.split(' ');
         bool ok;
         qDebug()<<list;
         QVector<quint16> values;

         for(int i=0; i<list.size(); i++) {
             quint16 num = static_cast<quint16>(list.at(i).toInt(&ok, 16));
             unit.setValue(i, num);
         }
    }
    else if(type==DataType::ASCII) {
        QByteArray ASCII = stringToASCII(data);
       
        for(uint i=ASCII.length(); i<count*4; i++) {
            ASCII.append("F");
        }

        qDebug()<<"ASCII:"<<ASCII;
        int size = ASCII.size()/4-1;
        //用空格两个每组分隔开
        for(int i=1; i<=size; i++) {
            ASCII.insert(i*4+i-1, " ");
        }
        QByteArrayList list = ASCII.split(' ');
        bool ok;
        qDebug()<<list;
        for(int i=0; i<list.size(); i++) {
            quint16 num = static_cast<quint16>(list.at(i).toInt(&ok, 16));
            unit.setValue(i, num);
        }
    }

编码转换如下

QByteArray Widget::utf8ToGB2312(QString utf8Data)
{

    //原始UTF8数据
    QString strOrgData(utf8Data);
    QTextCodec *utf8 = QTextCodec::codecForName("UTF-8");
    QTextCodec::setCodecForLocale(utf8);

    //1.UTF8 ---> GBK
    //UTF8转unicode
    QString strUnicode= utf8->toUnicode(strOrgData.toLocal8Bit().data());

    //Unicode转GBK
    QTextCodec *gbk = QTextCodec::codecForName("gbk");
    //转化为16进制
    QByteArray bytegbkHex = gbk->fromUnicode(strUnicode).toHex();
    return bytegbkHex;
}

QByteArray Widget::stringToASCII(QString data)
{
    //QByteArray byte = data.toUtf8();
    QByteArray byte = data.toUtf8();
    QByteArray byteASCII;
    for(char c: byte) {
        byteASCII += QString::number(int(c), 16);
    }
    return byteASCII;
}

最后发送给服务端即可,使用的函数为

QModbusReply *QModbusClient::sendWriteRequest(const QModbusDataUnit &write, int serverAddress)

第一个参数为已封装的数据unit,第二个参数为设备地址,根据文档或者询问厂家即可

void Widget::sendWriteRequest(QModbusDataUnit &unit, int address)
{
    qDebug() << "写数据内容为:" << unit.values();
    auto *reply = modbusClient->sendWriteRequest(unit, address);
    if (reply)
    {
        if (!reply->isFinished())
        {
            //完毕之后 自动触发槽函数
            connect(reply, &QModbusReply::finished, this, [this, reply]{
                if (reply->error() == QModbusDevice::ProtocolError)
                {
                    qDebug()<<QString("Write Protocaol response error: %1").arg(reply->errorString());
                }
                else if (reply->error() != QModbusDevice::NoError)
                {
                    qDebug()<<QString("Write response error: %1").arg(reply->errorString());
                }
                else
                {
                    qDebug() << "写响应的数据: " << reply->result().values();
                }
                reply->deleteLater();

            });
        }
        else
        {
            //广播消息  不需要返回响应
            reply->deleteLater();
        }
    }
    else
    {
        qDebug()<<QString(("Write Error: ") + modbusClient->errorString());
    }


}

我写的例子最终效果图如下:

5、QModbusTcpClient的缺点

1. 只能看到封装发送的QVector,不能看到实时发送的报文,接收响应的报文也是如此

2. 内部方法中也没有相应的函数,可以查看发送的或者接收的真正报文

----写入物料名称----
byteGB2312: "c4e3bac33132333435c4e3bac3000000000000000000000000000000"
("c4e3", "bac3", "3132", "3334", "35c4", "e3ba", "c300", "0000", "0000", "0000", "0000", "0000", "0000", "0000")
写数据内容为: QVector(50403, 47811, 12594, 13108, 13764, 58298, 49920, 0, 0, 0, 0, 0, 0, 0)
----写入物料信息----
ASCII: "4231323334353600FFFFFFFF"
("4231", "3233", "3435", "3600", "FFFF", "FFFF")
写数据内容为: QVector(16945, 12851, 13365, 13824, 65535, 65535)
----写入物料编码----
ASCII: "3132333435363700FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"
("3132", "3334", "3536", "3700", "FFFF", "FFFF", "FFFF", "FFFF", "FFFF", "FFFF", "FFFF", "FFFF", "FFFF", "FFFF")
写数据内容为: QVector(12594, 13108, 13622, 14080, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535)
----写入存量数----
写数据内容为: QVector(30)
----写入待领料数量----
写数据内容为: QVector(0, 2)
----------------------写入完成----------------
写响应的数据:  QVector(50403, 47811, 12594, 13108, 13764, 58298, 49920, 0, 0, 0, 0, 0, 0, 0)
写响应的数据:  QVector(16945, 12851, 13365, 13824, 65535, 65535)
写响应的数据:  QVector(12594, 13108, 13622, 14080, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535)
写响应的数据:  QVector(30)
写响应的数据:  QVector(0, 2)

这个是源码编写时忘记写了嘛?那我就找别的途径了吧

解决方案:自己写一个服务端,然后我们连接上,看接收的消息即可

核心代码如下:

void Widget::writeDate()
{
    if(modbusClient->state() == QModbusDevice::UnconnectedState) {
        qDebug()<<"Device is not connected";
        return;
    }

    QString code = ui->code->text();
    QString name = ui->name->text();
    QString stock = ui->stock->text();
    QString amount = ui->getAmount->text();
    QString matInfo = ui->matInfo->text();
    QString minStock = ui->minStock->text();
    if(!name.isEmpty()) {
        qDebug()<<"----写入物料名称----";
        QModbusDataUnit writeNameUnit = QModbusDataUnit(QModbusDataUnit::HoldingRegisters, ui->name_reg->text().toInt(), 14);
        createModbusDataUnit(writeNameUnit, name, DataType::GBK);
        sendWriteRequest(writeNameUnit, 1);
    }
    QThread::msleep(100);
    if(!matInfo.isEmpty()) {
        qDebug()<<"----写入物料信息----";
        QModbusDataUnit writeMatInfoUnit = QModbusDataUnit(QModbusDataUnit::HoldingRegisters, ui->matInfo_reg->text().toInt(), 6);
        createModbusDataUnit(writeMatInfoUnit, matInfo, DataType::ASCII);
        sendWriteRequest(writeMatInfoUnit, 1);
        QThread::msleep(100);
    }
    if(!code.isEmpty()) {
        qDebug()<<"----写入物料编码----";
        QModbusDataUnit writeCodeUnit = QModbusDataUnit(QModbusDataUnit::HoldingRegisters, ui->code_reg->text().toInt(), 14);
        createModbusDataUnit(writeCodeUnit, code, DataType::ASCII);
        sendWriteRequest(writeCodeUnit, 1);
        QThread::msleep(100);
    }
    if(!stock.isEmpty()) {
        qDebug()<<"----写入存量数----";
        QModbusDataUnit writeStockUnit = QModbusDataUnit(QModbusDataUnit::HoldingRegisters, ui->stock_reg->text().toInt(), 1);
        createModbusDataUnit(writeStockUnit, stock, DataType::INT);
        sendWriteRequest(writeStockUnit, 1);
        QThread::msleep(100);
    }
    if(!amount.isEmpty()) {
        qDebug()<<"----写入待领料数量----";
        QModbusDataUnit writeAmountUnit = QModbusDataUnit(QModbusDataUnit::HoldingRegisters, ui->getAmount_reg->text().toInt(), 2);
        createModbusDataUnit(writeAmountUnit, amount, DataType::INT);
        sendWriteRequest(writeAmountUnit, 1);
        QThread::msleep(100);
    }
    if(!minStock.isEmpty()) {
        qDebug()<<"----写入最低存量----";
        QModbusDataUnit writeminStockUnit = QModbusDataUnit(QModbusDataUnit::HoldingRegisters, ui->minStock_reg->text().toInt(), 2);
        createModbusDataUnit(writeminStockUnit, minStock, DataType::INT);
        sendWriteRequest(writeminStockUnit, 1);
        QThread::msleep(100);
    }
    qDebug()<<"----------------------写入完成----------------";
}

void Widget::onStateChanged(QModbusDevice::State newState)
{
    if (newState == QModbusDevice::ConnectedState) {
        qDebug()<<"QModbusDevice连接成功";
        ui->send->setDisabled(false);
       // 连接成功
   } else if (newState == QModbusDevice::UnconnectedState) {
       // 连接断开
        qDebug()<<"QModbusDevice连接断开";
        ui->send->setDisabled(true);
   }

}


void Widget::createModbusDataUnit(QModbusDataUnit &unit, QString &data,  DataType type)
{
    switch (type) {
        case DataType::INT: {
            bool ok;
            uint count = unit.valueCount();
            if(count==1) {
                unit.setValue(0, static_cast<quint16>(data.toInt(&ok,10)));
            }
            else {
                int num0 = data.toInt()/65536;
                int num1 = data.toInt()%65536;
                unit.setValue(0, static_cast<quint16>(num0));
                unit.setValue(1, static_cast<quint16>(num1));
            }
            break;
        }
        case DataType::GBK:
        case DataType::ASCII:
        {
            packDataUnit(unit, data, type);
            break;
        }
    }


}

void Widget::sendWriteRequest(QModbusDataUnit &unit, int address)
{
    qDebug() << "写数据内容为:" << unit.values();
    auto *reply = modbusClient->sendWriteRequest(unit, address);
    if (reply)
    {
        if (!reply->isFinished())
        {
            //完毕之后 自动触发槽函数
            connect(reply, &QModbusReply::finished, this, [this, reply]{
                if (reply->error() == QModbusDevice::ProtocolError)
                {
                    qDebug()<<QString("Write Protocaol response error: %1").arg(reply->errorString());
                }
                else if (reply->error() != QModbusDevice::NoError)
                {
                    qDebug()<<QString("Write response error: %1").arg(reply->errorString());
                }
                else
                {
                    qDebug() << "写响应的数据: " << reply->result().values();
                }
                reply->deleteLater();

            });
        }
        else
        {
            //广播消息  不需要返回响应
            reply->deleteLater();
        }
    }
    else
    {
        qDebug()<<QString(("Write Error: ") + modbusClient->errorString());
    }


}

void Widget::packDataUnit(QModbusDataUnit &unit, QString &data, DataType type)
{
    uint count = unit.valueCount();
    if(type == DataType::GBK) {
         QByteArray byteGB2312 = utf8ToGB2312(data);
         for(uint i=byteGB2312.length(); i<count*4; i++) {
             byteGB2312.append("0");
         }
         qDebug()<<"byteGB2312:"<<byteGB2312;
         m_data = byteGB2312;
         int size = byteGB2312.size()/4-1;
         //用空格两个每组分隔开
         for(int i=1; i<=size; i++) {
             byteGB2312.insert(i*4+i-1, " ");
         }
         QByteArrayList list = byteGB2312.split(' ');
         bool ok;
         qDebug()<<list;
         QVector<quint16> values;

         for(int i=0; i<list.size(); i++) {
             quint16 num = static_cast<quint16>(list.at(i).toInt(&ok, 16));
             unit.setValue(i, num);
         }
    }
    else if(type==DataType::ASCII) {
        QByteArray ASCII = stringToASCII(data);
        while(ASCII.length()%4!=0) {
            ASCII.append("0");
        }
        for(uint i=ASCII.length(); i<count*4; i++) {
            ASCII.append("F");
        }

        qDebug()<<"ASCII:"<<ASCII;
        int size = ASCII.size()/4-1;
        //用空格两个每组分隔开
        for(int i=1; i<=size; i++) {
            ASCII.insert(i*4+i-1, " ");
        }
        QByteArrayList list = ASCII.split(' ');
        bool ok;
        qDebug()<<list;
        for(int i=0; i<list.size(); i++) {
            quint16 num = static_cast<quint16>(list.at(i).toInt(&ok, 16));
            unit.setValue(i, num);
        }
    }
}

QByteArray Widget::utf8ToGB2312(QString utf8Data)
{

    //原始UTF8数据
    QString strOrgData(utf8Data);
    QTextCodec *utf8 = QTextCodec::codecForName("UTF-8");
    QTextCodec::setCodecForLocale(utf8);

    //1.UTF8 ---> GBK
    //UTF8转unicode
    QString strUnicode= utf8->toUnicode(strOrgData.toLocal8Bit().data());

    //Unicode转GBK
    QTextCodec *gbk = QTextCodec::codecForName("gbk");
    //转化为16进制
    QByteArray bytegbkHex = gbk->fromUnicode(strUnicode).toHex();
    return bytegbkHex;
}

QByteArray Widget::stringToASCII(QString data)
{
    //QByteArray byte = data.toUtf8();
    QByteArray byte = data.toUtf8();
    QByteArray byteASCII;
    for(char c: byte) {
        byteASCII += QString::number(int(c), 16);
    }
    return byteASCII;
}

文章来源:https://blog.csdn.net/qq_44667165/article/details/135190691
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。