QT QModbusTcpClient使用ModbusTcp协议与硬件通信实战例子
? 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;
}
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。 如若内容造成侵权/违法违规/事实不符,请联系我的编程经验分享网邮箱:veading@qq.com进行投诉反馈,一经查实,立即删除!