【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)
15 ( NAK)
04 (收 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;}
这里也一定要注意,平常进行数据接收我们一般也是采用信号触发的方式,但这边不是,用得read
和write
,传参分别是存储信息的地址和读取长度,返回实际读取长度。当发来一共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也是一样的情况,由于不连接通讯,倒是不用增加连接步骤,但是接收信息不用常用的信号触发实现,也是直接用read
和write
,其目的其实都是为了方便代码编写,更好使用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可执行文件并打包给其他人使用】,里面讲得很清晰。
相关文章:

【QT】基于UDP/TCP/串口 的Ymodom通讯协议客户端
【QT】基于UDP/TCP/串口的Ymodom通讯协议客户端 前言Ymodom实现QT实现开源库的二次开发-1开源库的二次开发-2 串口方式实现TCP方式实现UDP方式实现补充:文件读取补充:QT 封装成EXE 前言 Qt 运行环境 Desktop_Qt_5_11_2_MSVC2015_64bit ,基于…...

超详细!!!electron-vite-vue开发桌面应用之引入UI组件库element-plus(四)
云风网 云风笔记 云风知识库 一、安装element-plus以及图标库依赖 npm install element-plus --save npm install element-plus/icons-vue npm i -D unplugin-icons二、vite按需引入插件 npm install -D unplugin-vue-components unplugin-auto-importunplugin-vue-componen…...

【排序篇】实现快速排序的三种方法
🌈个人主页:Yui_ 🌈Linux专栏:Linux 🌈C语言笔记专栏:C语言笔记 🌈数据结构专栏:数据结构 文章目录 1 交换排序1.1 冒泡排序1.2 快速排序1.2.1 hoare版本1.2.2 挖坑法1.2.3 前后指针…...
Java 标识符(详解)
文章目录 一、简介二、命名规则三、命名规范 一、简介 在 Java 中,用于给变量、类、方法等命名的符号组合,我们称之为Java标识符,它就像是给这些编程元素贴上的独特标签,以便在程序中能够准确地引用和操作它们。 二、命名规则 标…...

2024年,有哪些优质的计算机书籍推荐?
在2024年,计算机领域的新书层出不穷,涵盖了从基础理论到前沿技术的多个方面。以下是今年出版的几本备受关注的计算机新书。 1. AI与机器学习类 1、深度学习详解 1.李宏毅老师亲笔推荐,杨小康、周明、叶杰平、邱锡鹏鼎力推荐! 2.数百万次播…...
Python基础知识点--总结
1. 注释 注释用于提高代码的可读性,在代码中添加说明文字,使代码更容易理解。 单行注释:使用 # 符号开头,注释内容在符号之后的行内。多行注释:使用三引号( 或 """)包裹注释内…...

高效记录与笔记整理的策略:工具选择、结构设计与复习方法
✨✨ 欢迎大家来访Srlua的博文(づ ̄3 ̄)づ╭❤~✨✨ 🌟🌟 欢迎各位亲爱的读者,感谢你们抽出宝贵的时间来阅读我的文章。 我是Srlua小谢,在这里我会分享我的知识和经验。&am…...
Request重复读的问题
换了新工作都有时间写文章,每天也是加班到很晚,也不是工作内容多,主要是还是效率低,要考虑多干的很心累。 一、关于request重复读的问题,从源码的角度来分析 为什么他不能重复读 跳转 再看源码前可能需要一些基础的…...
Linux学习第60天:Linux驱动开发的一些总结
今天是Linux驱动开发的最后一个章节,题目中标明是60天完成的,其实在实际学习及笔记的整理中不止是60天。中间有过断更,有时断更的时间还是挺长的。这是在整个Linux驱动开发学习中最不满意的地方。 题目为Linux学习,其实这个题目有…...
OPP || 继承和抽象类 || 访问控制
OPP面向对象程序设计 数据抽象:类的接口声明和定义实现分离继承:类构成的(树型)层次关系动态绑定:忽略相似类型区别,用统一的方式使用 基类派生类: 继承:类名 冒号 访问说明符 …...

蓝牙音视频远程控制协议(AVRCP) command跟response介绍
零.声明 本专栏文章我们会以连载的方式持续更新,本专栏计划更新内容如下: 第一篇:蓝牙综合介绍 ,主要介绍蓝牙的一些概念,产生背景,发展轨迹,市面蓝牙介绍,以及蓝牙开发板介绍。 第二篇:Trans…...

MySQL的InnoDB存储引擎中的Buffer Pool机制
目录 Buffer Pool 简介 定义 为什么需要Buffer Pool 图解重点知识 Buffer Pool 的组成 数据页(Data Pages) 索引页(Index Pages) 插入缓冲页(Insert Buffer Pages) undo页(Undo Pages&a…...
5. MongoDB 文档插入、更新、删除、查询
1. 插入文档 文档的数据结构和JSON基本一样。所有存储在集合中的数据都是BSON格式。 BSON是一种类似JSON的二进制形式的存储格式,是Binary JSON的简称。常用的插入文档方法包括: db.collection.insertOne():插入单个文档db.collection.inse…...

⌈ 传知代码 ⌋ DETR[端到端目标检测]
💛前情提要💛 本文是传知代码平台中的相关前沿知识与技术的分享~ 接下来我们即将进入一个全新的空间,对技术有一个全新的视角~ 本文所涉及所有资源均在传知代码平台可获取 以下的内容一定会让你对AI 赋能时代有一个颠覆性的认识哦&#x…...
Oracle之触发器
简介 触发器在数据库里以独立的对象存储,他与存储过程不同的是,存储过程通过其他程序来启动运行或直接启动运行而触发器是由一个事件来启动运行,即触发器是当某个事件发生时自动式运行。并企,触发器不能接收参数。所以运行触发器…...
从零搭建微前端架构:解耦大型项目的终极方案
随着前端应用的复杂度不断提升,单体前端应用(Monolithic Frontend)的维护和扩展难度也日益增加。微前端(Micro-Frontend)作为一种新兴架构理念,旨在将大型前端项目拆分为多个独立、可独立部署的微应用,从而提升项目的可维护性和灵活性。这篇文章将带你从零开始搭建一个微…...
24/8/17算法笔记 MPC算法
MPC算法,在行动前推演一下 MPC(Model Predictive Control,模型预测控制)是一种先进的控制策略,它利用未来预测模型来优化当前的控制动作。MPC的核心思想是,在每一个控制步骤中,都基于当前系统状…...

GROUP_CONCAT 用法详解(Mysql)
GROUP_CONCAT GROUP_CONCAT 是 MySQL 中的一个聚合函数,用于将分组后的多行数据连接成一个单一的字符串。 通常用于将某个列的多个值合并到一个字符串中,以便更方便地显示或处理数据。 GROUP_CONCAT([DISTINCT] column_name[ORDER BY column_name [ASC…...
Golang httputil 包深度解析:HTTP请求与响应的操控艺术
标题:Golang httputil 包深度解析:HTTP请求与响应的操控艺术 引言 在Go语言的丰富标准库中,net/http/httputil包是一个强大的工具集,它提供了操作HTTP请求和响应的高级功能。从创建自定义的HTTP代理到调试HTTP流量,h…...
SQLALchemy 分页
SQLALchemy 分页 1. 使用SQLAlchemy的`slice`和`offset`/`limit`SQLAlchemy 1.4及更新版本SQLAlchemy 1.3及更早版本使用第三方库注意事项在Web开发中,分页是处理大量数据时一个非常重要的功能。SQLAlchemy是一个流行的Python SQL工具包和对象关系映射(ORM)库,它允许开发者…...

华为云AI开发平台ModelArts
华为云ModelArts:重塑AI开发流程的“智能引擎”与“创新加速器”! 在人工智能浪潮席卷全球的2025年,企业拥抱AI的意愿空前高涨,但技术门槛高、流程复杂、资源投入巨大的现实,却让许多创新构想止步于实验室。数据科学家…...
Vue记事本应用实现教程
文章目录 1. 项目介绍2. 开发环境准备3. 设计应用界面4. 创建Vue实例和数据模型5. 实现记事本功能5.1 添加新记事项5.2 删除记事项5.3 清空所有记事 6. 添加样式7. 功能扩展:显示创建时间8. 功能扩展:记事项搜索9. 完整代码10. Vue知识点解析10.1 数据绑…...

【OSG学习笔记】Day 18: 碰撞检测与物理交互
物理引擎(Physics Engine) 物理引擎 是一种通过计算机模拟物理规律(如力学、碰撞、重力、流体动力学等)的软件工具或库。 它的核心目标是在虚拟环境中逼真地模拟物体的运动和交互,广泛应用于 游戏开发、动画制作、虚…...

深入理解JavaScript设计模式之单例模式
目录 什么是单例模式为什么需要单例模式常见应用场景包括 单例模式实现透明单例模式实现不透明单例模式用代理实现单例模式javaScript中的单例模式使用命名空间使用闭包封装私有变量 惰性单例通用的惰性单例 结语 什么是单例模式 单例模式(Singleton Pattern&#…...
鸿蒙中用HarmonyOS SDK应用服务 HarmonyOS5开发一个医院挂号小程序
一、开发准备 环境搭建: 安装DevEco Studio 3.0或更高版本配置HarmonyOS SDK申请开发者账号 项目创建: File > New > Create Project > Application (选择"Empty Ability") 二、核心功能实现 1. 医院科室展示 /…...

dedecms 织梦自定义表单留言增加ajax验证码功能
增加ajax功能模块,用户不点击提交按钮,只要输入框失去焦点,就会提前提示验证码是否正确。 一,模板上增加验证码 <input name"vdcode"id"vdcode" placeholder"请输入验证码" type"text&quo…...

【2025年】解决Burpsuite抓不到https包的问题
环境:windows11 burpsuite:2025.5 在抓取https网站时,burpsuite抓取不到https数据包,只显示: 解决该问题只需如下三个步骤: 1、浏览器中访问 http://burp 2、下载 CA certificate 证书 3、在设置--隐私与安全--…...
Linux云原生安全:零信任架构与机密计算
Linux云原生安全:零信任架构与机密计算 构建坚不可摧的云原生防御体系 引言:云原生安全的范式革命 随着云原生技术的普及,安全边界正在从传统的网络边界向工作负载内部转移。Gartner预测,到2025年,零信任架构将成为超…...
工业自动化时代的精准装配革新:迁移科技3D视觉系统如何重塑机器人定位装配
AI3D视觉的工业赋能者 迁移科技成立于2017年,作为行业领先的3D工业相机及视觉系统供应商,累计完成数亿元融资。其核心技术覆盖硬件设计、算法优化及软件集成,通过稳定、易用、高回报的AI3D视觉系统,为汽车、新能源、金属制造等行…...
CRMEB 框架中 PHP 上传扩展开发:涵盖本地上传及阿里云 OSS、腾讯云 COS、七牛云
目前已有本地上传、阿里云OSS上传、腾讯云COS上传、七牛云上传扩展 扩展入口文件 文件目录 crmeb\services\upload\Upload.php namespace crmeb\services\upload;use crmeb\basic\BaseManager; use think\facade\Config;/*** Class Upload* package crmeb\services\upload* …...