当前位置: 首页 > news >正文

【QT】基于UDP/TCP/串口 的Ymodom通讯协议客户端

【QT】基于UDP/TCP/串口的Ymodom通讯协议客户端

  • 前言
  • Ymodom实现
  • QT实现
    • 开源库的二次开发-1
    • 开源库的二次开发-2
  • 串口方式实现
  • TCP方式实现
  • UDP方式实现
  • 补充:文件读取
  • 补充:QT 封装成EXE

前言

Qt 运行环境 Desktop_Qt_5_11_2_MSVC2015_64bit ,基于Ymodom通讯协议,开发客户端实现与设备的UDP /TCP /串口通讯。在前期测试过程中,主要用了网络调试助手、串口调试助手、Virtual Serial Port Driver虚拟串口

对Ymodom的了解过程中,主要学习了博文Ymodem协议详解 、【嵌入式——QT】QT集成Ymodem协议使用UDP进行传输、qt随手记——ymodem协议使用,里面对协议的规则进行了详细的讲述,方便理解Ymodom 是什么。

在没有设备的情况下,用虚拟设备进行测试,发一个文件对应的指令如下:

43     ( C)
--
06   ( Ack)
43     ( C)
......
06   ( Ack)
----
04    (收 Eot)
15NAK04    (收 Eot)
06   ( Ack)
43     ( C)
--
06   ( Ack)

Ymodom实现

该协议包括起始帧、数据帧、结束帧,状态变量流转的方向如下:

YmodemFileTransmit.h
status : StatusEstablish->StatusTransmit ->StatusFinishymodem.h
stage:  StageNone-> StageEstablishing -> StageEstablished -> StageTransmitting->StageFinishing->StageFinished ->StageNone
code:  CodeNone

里面数据帧要注意,传1024或128规则如下:

  • 数据大于128,则按1024传;
  • 数据小于128,则按128传。

关于数据填充,看网上说是,以0x1A填充,但实际测试发现,是按照00填充的。

整个指令流程如下:
在这里插入图片描述

  • 接收方先发 43(C)
  • 发送方发 文件名+文件大小
  • 接收方发 06(Ack)
  • 接收方发 43(C)
  • 发送方开始一包包数据的发送,每发一包得等接收方回复 06(Ack)后再开始下一包
  • 当发完最后一包数据后,发送方发 04 (Eot)
  • 接收方发 15( NAK)
  • 发送方再发 04 (Eot)
  • 接收方发 06 ( Ack)
  • 接收方发 43(C),开始下一个文件传输
  • 如果不在发文件,则发送方发一包00数据
  • 接收方发 06(Ack),结束传输。

在传输中,使用的指令主要如下:

        CodeNone = 0x00,CodeSoh  = 0x01, //128字节数据包;CodeStx  = 0x02, //1024字节数据包;CodeEot  = 0x04, //文件传输结束指令;CodeAck  = 0x06, //接收正确指令;CodeNak  = 0x15, //重传当前数据包请求指令;CodeCan  = 0x18, //取消传输指令,连续发送5个该命令,终止传输;CodeC    = 0x43, //请求数据包CodeA1   = 0x41,CodeA2   = 0x61

QT实现

主要用得是Ymodem的开源库函数,然后对其进行二次开发。

开源库的二次开发-1

里面最主要的一个变更是,在传输完成进行二次回复确定时,接收方会发一个Ack过来,此时会调用transmitStageFinishing(),不难发现里面并没有关于Ack 的处理,此时会调用default: 处理:

如果一直没有发C指令,会不断累加定时器调用次数,由于设置一次定时器10ms,间隔5s后会重发04 (Eot)指令;如果超过设置的最大响应时间25s,会写取消传输指令。当然正常是不会有问题的,但是在测试时候,由于输入需要时间,时而会出现重发的情况,而且要注意这里的5s,是从第二次发完 EOT 后开始计算的。因此,对transmitStageFinishing() 增加Ack 处理 :

case CodeAck://sht-240813 add :避免发完ACK后C回复不及时,导致多发EOT指令{timeCount  = 0;errorCount = 0;dataCount  = 0;break;}

开源库的二次开发-2

第二个最大变更是,关于读取回复指令的长度设置,在部分的设备中,会存在回复指令加 0D 0A 的情况,用于分隔指令,因为接收方回复指令中存在连续发2个指令的情况,如果有了0D 0A 的加入,可以直接 以 06 0D 0A 43 0D 0A 方式发指令,当然也可以不用 0D 0A ,单纯只是用 06 43 或者间隔一下时间分别发,都可以。

既然出现了加 0D 0A 情况,那就要对读取进行二次处理,在receivePacket() 中将read(&(rxBuffer[0]), 1)修改为read(&(rxBuffer[0]), 3)

串口方式实现

最主要的就是串口收发一定要写好,Ymodom 部分主要就是进行虚函数复写就行。

QT += serialport
#include <QSerialPort>
QSerialPort * serialPort;
    if (serialPort->open(QSerialPort::ReadWrite) == true){//成功打开串口return true;}else{//串口打开失败return false;}
//读写指定长度len,存入buff
uint32_t YmodemFileTransmitSerial::read(uint8_t* buff, uint32_t len)
{return serialPort->read((char*)buff, len);
}uint32_t YmodemFileTransmitSerial::write(uint8_t* buff, uint32_t len)
{return serialPort->write((char*)buff, len);
}

TCP方式实现

QT       +=network
#include <QTcpSocket>
QTcpSocket * tcpClient;

这里一定要注意,平常会有信号触发的方式进行连接成功的判断,但为了减少跳转,以及代码逻辑的统一,这边采用了waitForConnected去进行连接成功与否的判断,设置的30000为等待连接时间,超过了则返回false。

tcpClient->connectToHost(targetAddr,serverPort);if(tcpClient->waitForConnected(30000)){//连接成功return true;}else{return false;}

这里也一定要注意,平常进行数据接收我们一般也是采用信号触发的方式,但这边不是,用得readwrite,传参分别是存储信息的地址和读取长度,返回实际读取长度。当发来一共10个字节,然后读了3个,后面7个字节会缓存,可以下次读,因此针对开源库的二次开发-2主要就是影响这里,加了0D 0A,在读1字节,就会有问题。

//-----------虚函数实现,读取内容----
uint32_t YmodemFileTransmitTcp::read(uint8_t *buff, uint32_t len)
{QByteArray array = tcpClient->read(len);uint32_t lenArray = array.size();uint32_t lenBuff  = len;uint32_t length = qMin(lenArray, lenBuff);memcpy(buff, array, length);return length;
}
//-----------虚函数实现,写内容----
uint32_t YmodemFileTransmitTcp::write(uint8_t *buff, uint32_t len)
{int ret = tcpClient->write((char*)buff, len);return ret;
}

UDP方式实现

QT       +=network
#include <QUdpSocket>
QUdpSocket* udpClient;

UDP也是一样的情况,由于不连接通讯,倒是不用增加连接步骤,但是接收信息不用常用的信号触发实现,也是直接用readwrite,其目的其实都是为了方便代码编写,更好使用Ymodom 库,确保三种方式逻辑编写规则统一。

//-----------虚函数实现,读取内容----
uint32_t YmodemFileTransmit::read(uint8_t* buff, uint32_t len)
{QNetworkDatagram datagram =udpClient->receiveDatagram(len);QByteArray array = datagram.data();uint32_t lenArray = array.size();uint32_t lenBuff  = len;uint32_t length = qMin(lenArray, lenBuff);memcpy(buff, array, length);return length;
}
//-----------虚函数实现,写内容----
uint32_t YmodemFileTransmit::write(uint8_t* buff, uint32_t len)
{QHostAddress targetAddr(serverIp);int ret = udpClient->writeDatagram((char*)buff, len, targetAddr, serverPort);return ret;
}

补充:文件读取

在开发中,需要涉及到文件的读取,为了方便后续的复用,这边也做一个整理

  • 找文件,存文件路径
#include <QFileDialog>
#include <QMessageBox>void BootLoader::on_pushButtonBrowse_clicked()
{QString curPath = QDir::currentPath();ui->lineEditFilePath->setText(QFileDialog::getOpenFileName(this, u8"打开文件", curPath, u8"任意文件 (*.*)"));
}
  • 读文件
#include <QFile>
QFile*       file;
YmodemFileTransmit::YmodemFileTransmit(QObject* parent) :QObject(parent),file(new QFile)
{
}
//-------------设置读取文件名--------
void YmodemFileTransmit::setFileName(const QString& name)
{file->setFileName(name);
}
//获取文件名 +文件大小
if(file->open(QFile::ReadOnly) == true) {QFileInfo fileInfo(*file);fileSize  = fileInfo.size();fileCount = 0;//将文件名fileInfo.fileName().toLocal8Bit().data()存buffstrcpy((char*)buff, fileInfo.fileName().toLocal8Bit().data());//将文件大小QByteArray::number(fileInfo.size()).data())存buffstrcpy((char*)buff + fileInfo.fileName().toLocal8Bit().size() + 1, QByteArray::number(fileInfo.size()).data());} 
//读YMODEM_PACKET_1K_SIZE最大长度的内容存buff,返回读取的实际长度。
//而且只要没有file->close();,file->read 会移动文件游标,读完一次后面接着读fileCount += file->read((char*)buff, YMODEM_PACKET_1K_SIZE);

补充:QT 封装成EXE

可以查看大神的博文【QT中如何生成导出.exe可执行文件并打包给其他人使用】,里面讲得很清晰。

相关文章:

  • 北京网站建设多少钱?
  • 辽宁网页制作哪家好_网站建设
  • 高端品牌网站建设_汉中网站制作
  • Token 设计
  • SpringBoot响应式编程(2)WebFlux入门
  • 文心快码(Baidu Comate)快速创建数据可视化图表
  • CSS的:host伪类:精确定位于Web组件的指南
  • 00_remipi_软件评估记录
  • 计算机基础知识复习8.13
  • 基于 HTTP构建 YUM网络源
  • 使用ITextRenderer导出PDF后无法打开问题,提示‘无法打开此文件‘
  • JVM性能监控工具
  • 实现异形(拱形)轮播图
  • 高性能内存对象缓存
  • 数据中心存储市场迎强劲反弹
  • 网络如何发送一个数据包
  • Ubuntu安装cuda
  • 【Qt开发】创建并打开子窗口(QWidget)的注意事项 禁止其他窗口点击、隐藏窗口、子窗口不退出的配置和解决方案
  • [Vue CLI 3] 配置解析之 css.extract
  • 【跃迁之路】【699天】程序员高效学习方法论探索系列(实验阶段456-2019.1.19)...
  • 4个实用的微服务测试策略
  • AHK 中 = 和 == 等比较运算符的用法
  • AngularJS指令开发(1)——参数详解
  • Mac 鼠须管 Rime 输入法 安装五笔输入法 教程
  • node.js
  • swift基础之_对象 实例方法 对象方法。
  • windows-nginx-https-本地配置
  • 从重复到重用
  • 分享一份非常强势的Android面试题
  • 聚簇索引和非聚簇索引
  • 开发基于以太坊智能合约的DApp
  • 理清楚Vue的结构
  • 面试总结JavaScript篇
  • 使用putty远程连接linux
  • 使用阿里云发布分布式网站,开发时候应该注意什么?
  • 数据仓库的几种建模方法
  • 算法之不定期更新(一)(2018-04-12)
  • 吴恩达Deep Learning课程练习题参考答案——R语言版
  • 移动端 h5开发相关内容总结(三)
  • 怎么将电脑中的声音录制成WAV格式
  • 中文输入法与React文本输入框的问题与解决方案
  • # 数仓建模:如何构建主题宽表模型?
  • #Z0458. 树的中心2
  • (day 12)JavaScript学习笔记(数组3)
  • (Java入门)抽象类,接口,内部类
  • (回溯) LeetCode 78. 子集
  • (七)Knockout 创建自定义绑定
  • (十六)一篇文章学会Java的常用API
  • (算法二)滑动窗口
  • .net 8 发布了,试下微软最近强推的MAUI
  • .NET Framework 4.6.2改进了WPF和安全性
  • .NET Micro Framework初体验(二)
  • .Net Redis的秒杀Dome和异步执行
  • .NET分布式缓存Memcached从入门到实战
  • .net生成的类,跨工程调用显示注释
  • .NET之C#编程:懒汉模式的终结,单例模式的正确打开方式
  • @Transactional 详解
  • [ vulhub漏洞复现篇 ] struts2远程代码执行漏洞 S2-005 (CVE-2010-1870)