使用QT基于YMODEM协议实现串口文件发送(和xshell互通)

在这里插入图片描述

背景

项目需要用QT实现一个YMODEM文件传输的功能,目标下位机是MCU嵌入式设备,且下位机程序已经经过xshell传输文件的验证。

YMODEM 简介

YMODEM协议是一个文件传输协议,常用于嵌入式设备。本文不对YMODEM做过多的阐述,阅读需建立在你已经对YMODEM有一定了解的基础上。如果要了解YMODEM协议,推荐几个地址:

维基百科 YMODEM
Ymodem 协议详解
YMODEM协议简介
YMODEM协议中文翻译

但要注意的是这些文章都有一些小的细节性的错误,文章的评论区有人指出了,需要注意甄别。

YMODEM通信协议:

发送端----------------------------------------------------------------接收端

<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< C

SOH 00 FF “foo.c” "1064’’ NUL[118] CRC CRC >>>>>>>>>>>>>

<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< ACK

<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< C

STX 01 FE data[1024] CRC CRC>>>>>>>>>>>>>>>>>>>>>>>>

<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< ACK

STX 02 FD data[1024] CRC CRC>>>>>>>>>>>>>>>>>>>>>>>

<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< ACK

STX 03 FC data[1024] CRC CRC>>>>>>>>>>>>>>>>>>>>>>>

<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< ACK

STX 04 FB data[1024] CRC CRC>>>>>>>>>>>>>>>>>>>>>>>

<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< ACK

SOH 05 FA data[100] 1A[28] CRC CRC>>>>>>>>>>>>>>>>>>

<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< ACK

EOT >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< NAK

EOT>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< ACK

<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< C

SOH 00 FF NUL[128] CRC CRC >>>>>>>>>>>>>>>>>>>>>>>

<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< ACK

核心代码

文件发送的核心代码,主要步骤是:

  1. 启动YMODEM传输,并建立通道。
  2. 发送首帧数据,即文件名和文件大小信息。
  3. 按照1024或者128字节发送文件数据。
  4. 结束传输。
Ymodem::Code YmodemFileTransmit::callback(Status status, uint8_t* buff, uint32_t* len)
{
    switch (status) {
        case StatusEstablish: {
                if (file->open(QFile::ReadOnly) == true) {
                    QFileInfo fileInfo(*file);

                    fileSize  = fileInfo.size();
                    fileCount = 0;

                    strcpy((char*)buff, fileInfo.fileName().toLocal8Bit().data());
                    strcpy((char*)buff + fileInfo.fileName().toLocal8Bit().length() + 1,
                           QByteArray::number(fileInfo.size()).data());
//                    char source = 0x20;
//                    strcpy((char*)buff + fileInfo.fileName().toLocal8Bit().length() + 1 +
//                           QByteArray::number(fileInfo.size()).size(), &source);
                    *len = YMODEM_PACKET_SIZE;

                    YmodemFileTransmit::status = StatusEstablish;

                    transmitStatus(StatusEstablish);

                    return CodeAck;
                } else {
                    YmodemFileTransmit::status = StatusError;

                    writeTimer->start(WRITE_TIME_OUT);

                    return CodeCan;
                }
            }

        case StatusTransmit: {
                if (fileSize != fileCount) {
                    if ((fileSize - fileCount) > YMODEM_PACKET_1K_SIZE) {
                        qint64 r = file->read((char*)buff, YMODEM_PACKET_1K_SIZE);
                        fileCount += r;
                        *len = YMODEM_PACKET_1K_SIZE;
                    } else {

                        qint64 r = file->read((char*)buff, YMODEM_PACKET_SIZE);
                        fileCount += r;

                        *len = YMODEM_PACKET_SIZE;
                    }

                    progress = (int)(fileCount * 100 / fileSize);
                    YmodemFileTransmit::status = StatusTransmit;

                    transmitProgress(progress);
                    transmitStatus(StatusTransmit);

                    return CodeAck;
                } else {
                    YmodemFileTransmit::status = StatusTransmit;

                    transmitStatus(StatusTransmit);

                    return CodeEot;
                }
            }

        case StatusFinish: {
                file->close();

                YmodemFileTransmit::status = StatusFinish;

                writeTimer->start(WRITE_TIME_OUT);

                return CodeAck;
            }

        case StatusAbort: {
                file->close();

                YmodemFileTransmit::status = StatusAbort;

                writeTimer->start(WRITE_TIME_OUT);

                return CodeCan;
            }

        case StatusTimeout: {
                YmodemFileTransmit::status = StatusTimeout;

                writeTimer->start(WRITE_TIME_OUT);

                return CodeCan;
            }

        default: {
                file->close();

                YmodemFileTransmit::status = StatusError;

                writeTimer->start(WRITE_TIME_OUT);

                return CodeCan;
            }
    }
}

文件接收的核心代码,主要步骤是:

  1. 开启YMODEM文件接收,建立通道。
  2. 解析首帧数据,即解析文件名和文件大小信息。
  3. 按照发送者的发送大小(1024或者128字节)解析文件数据。
  4. 接收完成,结束传输。
Ymodem::Code YmodemFileReceive::callback(Status status, uint8_t* buff, uint32_t* len)
{
    switch (status) {
        case StatusEstablish: {
                QByteArray b = QByteArray((char*)buff, 133).toHex();
                qDebug() << "recvbuff:" <<  b;
                if (buff[0] != 0) {
                    int  i         =  0;
                    char name[128] = {0};
                    char size[128] = {0};

                    for (int j = 0; buff[i] != 0 && buff[i] != 0x20; i++, j++) {
                        qDebug() << buff[i];
                        name[j] = buff[i];
                    }

                    i++;
                    //SOH 00 FF foo.c 3232 NUL[118] CRCH CRCL
                    //0或者空格,xshell 在文件名称和文件大小中间发送的是空格,即0x20,标准YMODEM协议要求文件名称以'\0'(也就是0)结尾,所以如果要实现和xshell互通,此处要兼容
                    for (int j = 0; buff[i] != 0 && buff[i] != 0x20; i++, j++) {
                        qDebug() << buff[i];
                        size[j] = buff[i];
                    }

                    fileName  = QString::fromLocal8Bit(name);
                    fileSize  = QString(size).toULongLong();
                    fileCount = 0;
                    qDebug() << "StatusEstablish::fileName:" << fileName ;
                    qDebug() << "StatusEstablish::fileSize:" << fileSize ;
                    file->setFileName(filePath + fileName);

                    if (file->open(QFile::WriteOnly) == true) {
                        YmodemFileReceive::status = StatusEstablish;

                        receiveStatus(StatusEstablish);

                        return CodeAck;
                    } else {
                        YmodemFileReceive::status = StatusError;

                        writeTimer->start(WRITE_TIME_OUT);

                        return CodeCan;
                    }
                } else {
                    YmodemFileReceive::status = StatusError;

                    writeTimer->start(WRITE_TIME_OUT);

                    return CodeCan;
                }
            }

        case StatusTransmit: {
                if ((fileSize - fileCount) > *len) {
                    qint64 w = file->write((char*)buff, *len);
                    fileCount += *len;
                } else {

                    qint64 w = file->write((char*)buff, fileSize - fileCount);
                    fileCount += fileSize - fileCount;
                }

                progress = fileSize == 0 ? 0 : (int)(fileCount * 100 / fileSize);
                YmodemFileReceive::status = StatusTransmit;

                receiveProgress(progress);
                receiveStatus(StatusTransmit);

                return CodeAck;
            }

        case StatusFinish: {
                file->close();

                YmodemFileReceive::status = StatusFinish;

                writeTimer->start(WRITE_TIME_OUT);

                return CodeAck;
            }

        case StatusAbort: {
                file->close();

                YmodemFileReceive::status = StatusAbort;

                writeTimer->start(WRITE_TIME_OUT);

                return CodeCan;
            }

        case StatusTimeout: {
                YmodemFileReceive::status = StatusTimeout;

                writeTimer->start(WRITE_TIME_OUT);

                return CodeCan;
            }

        default: {
                file->close();

                YmodemFileReceive::status = StatusError;

                writeTimer->start(WRITE_TIME_OUT);

                return CodeCan;
            }
    }
}

在进行YMODEM 上位机开发时,有两个坑需要注意,否则大概率掉坑里。

1号坑:

下位机那边反馈,使用xshell给MCU发送文件正常,使用我们的软件一开始就卡住。

经过对xshell抓包分析,发现xshell默认配置会在发送YMODEM前先发送"rb -E"指令,我们的下位机收到无法解析的指令后,会默认回复’C’,所以xshell收到’C’后开始了正常的文件发送。

但是根据YMODEM协议标准(可以看上面的通信协议),在启动文件传输后,发送端是静止的,只需要等待接收端发送第一个’C’, 所以我们的上位机并没有做,导致启动发送就卡住了。修改代码,在开始发送前,先发送任意字符,触发接收端的’C’回复,修改后测试正常了。

所以如果打开也涉及到YMODEM开发,需要注意和你们的下位机同事沟通清楚’C’字符在什么时候回复,有没有按照标准来。

2号坑:

在YMODEM传输文件名和文件大小时,YMODEM协议中明确要求了文件名必须以null(即’\0’)作为结束符,求证:

  • https://en.wikipedia.org/wiki/YMODEM
  • https://pauillac.inria.fr/~doligez/zmodem/ymodem.txt

见 wikipedia 的 References 文章中的 Chapter 1 ,
The pathname shall be a null terminated ASCII string as described below.

但抓包发现xshell在文件名和文件大小中间填充的是空格(即0x20),如果这个时候接收端说xshell和我是互通的来证明自己没问题,就欲哭无泪了。因为他很有可能解析文件名和文件大小时判断的是0x20,而不是’\0’。所以,此处也要跟下位机沟通清楚,他们是如何解析文件名和文件大小的,如果没有判断’\0’,文件名和文件大小肯定就不对了。

结束补充

Demo截图:
在这里插入图片描述
参考:

SerialPortYmodem

对其在xshell兼容方面作了改动和优化。

Demo及源代码下载地址:
https://download.csdn.net/download/u012534831/88625925

其他代码我打包上传到csdn资源中,关注公号后在后台留言需要下载的资源,我看到后免费发给你,并可以得到我的免费解答。 原创不易,谢谢支持。

在这里插入图片描述

关注公众号 QTShared,带你探索更多QT相关知识。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/241674.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Tomcat主配置文件(server.xml)详解

前言 Tomcat主配置文件&#xff08;server.xml&#xff09;是Tomcat服务器的主要配置文件&#xff0c;文件位置在conf目录下&#xff0c;它包含了Tomcat的全局配置信息&#xff0c;包括监听端口、虚拟主机、安全配置、连接器等。 目录 1 server.xml组件类别 2 组件介绍 3 se…

003 FeedForward前馈层

一、环境 本文使用环境为&#xff1a; Windows10Python 3.9.17torch 1.13.1cu117torchvision 0.14.1cu117 二、前馈层原理 Transformer模型中的前馈层&#xff08;Feed Forward Layer&#xff09;是其关键组件之一&#xff0c;对于模型的性能起着重要作用。下面将用900字对…

cpp:1:10: fatal error: opencv2/core.hpp: 没有那个文件或目录

前言&#xff1a; 我按照官网方法安装了opencv&#xff0c;运行的也是官网的测试代码&#xff1a; #include <opencv2/core.hpp> #include <opencv2/highgui.hpp> using namespace cv; int main() {printf("hello world")return 0; }

boost1.55 安装使用教程 windows

第一步 &#xff1a;首先在boost官网上下载库压缩包 添加链接描述 选择自己需要的版本进行下载 解压后执行booststrap.bat 用来生成创建b2.exe 和bjam.exe 拓展&#xff1a;.\b2 --help 了解一下有哪些参数可以配置 默认b2.exe编译后&#xff0c;链接到项目如果出现如下错误…

VLAN基本原理

目录 一、VLAN概念及优势 &#xff08;一&#xff09;基本理念 &#xff08;二&#xff09;VLAN的特点 二、VLAN ID 种类、范围及用途 &#xff08;一&#xff09;静态VLAN &#xff08;二&#xff09;动态VLAN &#xff08;三&#xff09;VLAN三种端口类型 &#xff0…

深入理解Java虚拟机---类加载机制

类加载机制 什么是类加载机制类加载的时机类加载的过程加载验证文件格式验证元数据验证字节码验证符号引用验证 准备解析初始化 类加载器双亲委派模型 什么是类加载机制 虚拟机把描述类的数据从 Class 文件加载到内存&#xff0c;并对数据进行校验、转换解析和初始化&#xff…

centOS安装bochsXshell连接centos启动可视化界面

centOS安装bochs 参考&#xff1a;https://blog.csdn.net/muzi_since/article/details/102559187 首先安装依赖环境&#xff1a; yum install gtk2 gtk2-devel yum install libXt libXt-devel yum install libXpm libXpm-devel yum install SDL SDL-devel yum install libXr…

已解决:No goals have been specified for this build. You must specify a vali

[ERROR] No goals have been specified for this build. You must specify a valiTOC 完整报错 No goals have been specified for this build. You must specify a valid lifecycle phase or a goal in the format : or :[:]:. Available lifecycle phases are: pre-clean, c…

6. Service详解

6. Service详解 文章目录 6. Service详解6.1 Service介绍6.2 Service类型6.3 Service使用6.3.1 实验环境准备6.3.2 ClusterIP类型的Service6.3.3 HeadLess类型的Service6.3.3.1 deployment和statefulset区别6.3.3.2 statefulset deployment 区别 6.3.4 NodePort类型的Service6.…

Trace 在多线程异步体系下传递

JAVA 线程异步常见的实现方式有&#xff1a; new ThreadExecutorService 当然还有其他的&#xff0c;比如fork-join&#xff0c;这些下文会有提及&#xff0c;下面主要针对这两种场景结合 DDTrace 和 Springboot 下进行实践。 引入 DDTrace sdk <properties><java.…

湖农大邀请赛shell_rce漏洞复现

湖农大邀请赛 shell_rce 复现 在 2023 年湖南农业大学邀请赛的线上初赛中&#xff0c;有一道 shell_rce 题&#xff0c;本文将复现该题。 题目内容&#xff0c;打开即是代码&#xff1a; <?phpclass shell{public $exp;public function __destruct(){$str preg_replace…

Shopify怎么避免被封店?封店原因有哪些?

市场研究的一份报告显示&#xff0c;全球跨境电子商务市场预计到2028年将达到30422亿美元&#xff0c;其中&#xff0c;亚太地区是最大的跨境电商市场&#xff0c;据海关统计数据&#xff0c;近五年来&#xff0c;我国跨境电商进出口增长近10倍。跨境电商业务新的增长风口已经到…

图像去噪——PMRID训练自己数据集及推理测试(详细图文教程)

目录 一、源码包准备二、数据集准备2.1 提取数据集名称2.2 .txt报错问题2.2.1 正确格式2.2.2 错误格式 三、修改配置参数四、训练及保存模型权重4.1 训练4.2 保存模型权重文件 五、模型推理测试5.1 导入测试集5.2 测试5.3 测试结果5.3.1 测试场景15.3.2 测试场景2 5.4 推理速度…

jsp+servlet+图书交流平台 有filter过滤器

在线图书推荐与交流平台 随着数字化的进展和人们对持续学习的追求&#xff0c;在线资源变得越来越受欢迎。对于众多读者来说&#xff0c;找到合适的书籍和与其他读者交流阅读体验是非常有价值的。为了满足这一需求&#xff0c;我们提出了一个在线图书推荐与交流平台的设计。此…

千梦网创:赚钱就是服侍好双爹

“爹啊&#xff0c;我没钱用啦&#xff0c;给我啃一下。” 想赚钱&#xff0c;最快的方式就是啃爹。 不管你做什么项目&#xff0c;同行永远都是我们的爹。 跟着爹走&#xff0c;有吃有喝不用愁。 跟着老爹走&#xff0c;蛋花汤里加骨头。 小时候父亲总是把我们高高的举过…

查询mysql服务器当前时区设置、session当前时区设置

使用命令SELECT global.time_zone;可以查询mysql服务器的当前时区设置&#xff0c;例如&#xff1a; 使用命令SELECT session.time_zone;可以查询session的当前时区设置&#xff0c;例如&#xff1a;

Vue 3 开发中遇到的问题及解决方案(fix bug)

开发环境&#xff1a;mac系统&#xff0c;node版本&#xff1a; 16.15.0 版本兼容问题 vite v3.2.4 building for development... hasInjectionContext is not exported by node_modules/pinia/node_modules/vue-demi/lib/index.mjs, imported by node_modules/pinia/dist/pini…

【算法题】冠亚军排名,奖牌榜排名(js)

解法&#xff1a; function solution(lines) {const list [];for (let i 0; i < lines.length; i) {const line lines[i];const [country, gold, silver, bronze] line.split(" ");list.push({country,gold: gold - 0,silver: silver - 0,bronze: bronze - 0…

Java 数据结构篇-用数组、堆实现优先级队列

&#x1f525;博客主页&#xff1a; 【小扳_-CSDN博客】 ❤感谢大家点赞&#x1f44d;收藏⭐评论✍ 文章目录 1.0 优先级队列说明 2.0 用数组实现优先级队列 3.0 无序数组实现优先级队列 3.1 无序数组实现优先级队列 - 入队列 offer(E value) 3.2 无序数组实现优先级队列 - 出…

RocketMQ可视化工具 打包遇到的yarn intall 问题

文章目录 RocketMQ可视化工具1.github上下载2.修改参数3.运行4.打包5.出错6.解决7.重试8.再解决9.很奇怪运行没错&#xff0c;但是测试错啦10.不想深究&#xff0c;直接跳过测试11.展示成功 RocketMQ可视化工具 1.github上下载 下载地址 https://github.com/apache/rocketmq-…
最新文章