给了一个显示屏和显示屏的通信文档,用ModbusTcp协议与其通信,读取或者写入显示屏相应的内容,以满足项目需要
文档部分截图如下
屏幕如下图所示:
我需要写入改写其中的物料名称,待领料数量等,就是上位机与硬件通信
对于熟悉modbusTcp协议的,可以根据协议和通信文档完成信息的封装在发送给硬件即可
需要熟悉QModbusTcpClient的接口,参数等,我们还没有用过
本来想请教项目组其他人的,他们让我先看下modbusTcp协议,熟悉一下,我看了好久,没有实际案例,有点抽象,他们也忙,没啥空,没时间了,还是用现有的轮子吧
然后我就决定采用QModbusTcpClient的方案进行吧
采用QModbusTcpClient和QTcpSocket本质差不多,都是通过tcp进行通信,QTcpSocket需要自己封装相应的modbusTcp协议内容,发送给硬件服务器
QModbusTcpClient封装好了modbusTcp协议,你只需要理解好,然后填入相应的参数和调用相应的接口就行了,不需要理解modbusTcp协议,对于第一次解除的人来说,只是项目需要,不需要深入理解的,最合适不过了
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(data.toInt(&ok,10)));
}
else {
int num0 = data.toInt()/65536;
int num1 = data.toInt()%65536;
unit.setValue(0, static_cast(num0));
unit.setValue(1, static_cast(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 values;
for(int i=0; i(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(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()<errorString());
}
else if (reply->error() != QModbusDevice::NoError)
{
qDebug()<errorString());
}
else
{
qDebug() << "写响应的数据: " << reply->result().values();
}
reply->deleteLater();
});
}
else
{
//广播消息 不需要返回响应
reply->deleteLater();
}
}
else
{
qDebug()<errorString());
}
}
我写的例子最终效果图如下:
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(data.toInt(&ok,10)));
}
else {
int num0 = data.toInt()/65536;
int num1 = data.toInt()%65536;
unit.setValue(0, static_cast(num0));
unit.setValue(1, static_cast(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()<errorString());
}
else if (reply->error() != QModbusDevice::NoError)
{
qDebug()<errorString());
}
else
{
qDebug() << "写响应的数据: " << reply->result().values();
}
reply->deleteLater();
});
}
else
{
//广播消息 不需要返回响应
reply->deleteLater();
}
}
else
{
qDebug()<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 values;
for(int i=0; i(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(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;
}