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

news2024/11/13 11:25:36

【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 ->StatusFinish

ymodem.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()存buff
            strcpy((char*)buff, fileInfo.fileName().toLocal8Bit().data());
            //将文件大小QByteArray::number(fileInfo.size()).data())存buff
            strcpy((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可执行文件并打包给其他人使用】,里面讲得很清晰。

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

PowerShell自动化Windows系统管理任务

​ 大家好&#xff0c;我是程序员小羊&#xff01; 前言 Windows系统管理涉及许多繁琐的任务&#xff0c;如用户管理、文件操作、系统更新、网络配置等。PowerShell作为Windows的命令行工具和脚本语言&#xff0c;可以极大地简化这些管理任务。本文将探讨如何使用PowerShell自动…

【教学类-75-01】20240817“通义万相图片最大化+透明png”的修图流程

背景需求&#xff1a; 打印了袜子配对的PDF模版&#xff0c;做预测试 【教学类-74-02】彩色袜子配对02--左右配对-CSDN博客文章浏览阅读497次&#xff0c;点赞10次&#xff0c;收藏9次。【教学类-74-02】彩色袜子配对02--左右配对https://blog.csdn.net/reasonsummer/article…

09:链表的介绍

链表 1、算法的定义2、链表 1、算法的定义 通俗的定义&#xff1a;解题的方法与步骤。       狭义的定义&#xff1a;对存储的数据的操作。       广义的定义&#xff1a;无论数据是如何存储的&#xff0c;对数据从操作都是一样的。 到目前为止我们可以通过2种结构来存储…

关于订单最终一致性解决方案

背景 整体的交易架构主要由两部分组成&#xff1a;C端交易平台 - B端交易平台 由于组织架构的特殊性&#xff0c;并没有采用两阶段提交、三阶段提交这种刚性分布式事务的方案。 主要采用了基于TCC思想的TOC柔性事务补偿方案。 柔性事务&#xff1a;遵循BASE原则&#xff0c;…

Redis7.x安装系列教程(四)集群部署原理详解

1、什么是集群部署 Redis集群(cluster)是Redis的一种分布式运行模式&#xff0c;通过分片(sharding)提供数据的自动分区和管理&#xff0c;实现数据的高可用性和可扩展性。 在集群模式下&#xff0c;数据分布在多个Redis节点上&#xff0c;节点分为主节点和从节点。主节点负责…

Pytorch如何判断两个模型的权重是否相同(比较权重差异/参数字典)

参考资料&#xff1a; GPT-4o mini的回答 第一种方法是使用md5sum这个命令(Linux上)&#xff0c;但是由于环境的不同&#xff0c;哪怕是load之后转存似乎都会有差&#xff0c;所以效果不大。 第二种方法是使用代码比较&#xff0c;这段代码是我找GPT要的&#xff0c;感觉非常不…

Linux线程间通信学习记录(线程同步)

0.线程间通信的方法 &#xff08;1&#xff09;.全局变量&#xff08;要结合同步机制&#xff09; &#xff08;2&#xff09;.信号量 &#xff08;3&#xff09;.P操作 &#xff08;4&#xff09;.V操作 一.线程同步 同步&#xff1a;指的是多个任务按照约定的先后次序相互…

OpenCV图像滤波(19)计算图像每个像素点的邻域内的平方和函数sqrBoxFilter()的使用

操作系统&#xff1a;ubuntu22.04 OpenCV版本&#xff1a;OpenCV4.9 IDE:Visual Studio Code 编程语言&#xff1a;C11 算法描述 计算覆盖在滤波器上的像素值的平方和。 对于源图像中的每一个像素 (x, y)&#xff0c;函数计算那些与放置在像素 (x, y) 上的滤波器重叠的邻域像…

【百度】25届秋招内推码

内推码 IV1RBB 介绍 &#x1f4e3; 百度TPG技术中台事业群组—深度学习技术平台部 25届校招正在进行中&#xff0c;可通过定向内推形式get校招绿色通道 &#xff01; 欢迎联系我定向内推 &#x1f31f;【部门介绍】 飞桨&#xff08;PaddlePaddle&#xff09;以百度多年的深度…

坐牢第二十七天(聊天室)

基于UDP的网络聊天室 一.项目需求&#xff1a; 1.如果有用户登录&#xff0c;其他用户可以收到这个人的登录信息 2.如果有人发送信息&#xff0c;其他用户可以收到这个人的群聊信息 3.如果有人下线&#xff0c;其他用户可以收到这个人的下线信息 4.服务器可以发送系统信息…

idea 遇到依赖引入失败问题

在引入 aspects 的相关依赖时&#xff0c;没有找到这个版本 <dependency><groupId>org.springframework</groupId><artifactId>spring-aspects</artifactId><version>6.0.0-M2</version> </dependency> 第一次尝试&#xff…

C++编程:生产者-消费者模型中条件变量的使用问题及优化方案

文章目录 0. 引言1. 生产者-消费者模型简介1.1 示例代码1.2 为什么必须加锁&#xff1f; 2. 上述代码存在的问题2.1 信号丢失2.2 锁的作用范围2.3 竞态条件 3. 优化方案3.1 使用两个条件变量3.2 扩展锁的作用域3.3 使用原子操作3.4 使用无锁队列 4. 底层实现与深入探讨5. 流程图…

『 C++ 』IO流

文章目录 IO流概述iostream 的标准对象C流和C标准库I/O函数的同步 sync_with_stdiofstream 文件流文件流的打开标志二进制读写二进制读写的浅拷贝问题文本读写 字符串流注意 IO流概述 流是指数据的有序传输序列,路表示数据从一个地方流向另一个地方的过程,流可以是输入流也可以…

欧盟新规:苹果App Store开发者需公开联系方式,透明度提升还是隐私挑战?

本文首发于公众号“AntDream”&#xff0c;欢迎微信搜索“AntDream”或扫描文章底部二维码关注&#xff0c;和我一起每天进步一点点 随着数字经济的蓬勃发展&#xff0c;欧盟对数字服务的监管也在不断加强。最近&#xff0c;苹果公司宣布了一项针对欧盟App Store的新政策&#…

Lesson 67 The weekend

Lesson 67 The weekend 词汇 greengrocer 菜市场 构成&#xff1a;green n. 绿色的    grocer n. 食杂店&#xff0c;小卖店 商店词汇&#xff1a;shop n. 商店      store n. 小店      market n. 市场      super market 超市      Sunday market 二…

Codeforces Round 949 (Div. 2) C.D构造和E题

C题链接 D题链接 E题链接 C题思路&#xff1a; 我们设相邻的两个-1的位置是的值是l和r&#xff0c;他们直接的距离是d(也就是r的下标减l的下标)。 思路1&#xff1a;直接模拟操作&#xff0c;看所有操作里是否有合法操作。 比如1 -1 -1 -1 -1 -1 7. 容易想到1*213,3*217&a…

psychopy 中文语义相关判断任务实验设计

参考文献&#xff1a; [石如彬, 谢久书, 杨梦情, & 王瑞明. (2022). 语言和情境对具体概念感知运动仿真的影响. 心理学报, 54(6), 583–594. https://doi.org/10.3724/SP.J.1041.2022.00583] 2.2.4实验1。 演示效果 按下“上方向键” 按F或J 反馈信息&#xff1a; 实验步骤…

C#中的S7协议

S7协议-S7COMM S7COMM 进行写 CTOP->PDU type已知枚举值 0X0E连接请求0x0d连接确认0x08断开请求0x0c断开确认0x05拒绝访问0x01加急数据0x02加急数据确认0x04用户数据0x07TPDU错误0x0f数据传输 S7Header->ROSCTR已知枚举值 0X01JOB REQUEST。主站发送请求0x02Ack。从站…

jmeter压测websocket

1、jmeter安装websocket插件 下载地址 pjtr / JMeter WebSocket Samplers / Downloads — Bitbucket 下载之后&#xff0c;放到lib/ext文件夹下&#xff0c;重启jmeter即可&#xff0c;看到下图这些证明插件安装成功 2、脚本 新建websocket request-response sampler

day05-SpringBootWeb请求响应学习笔记

上面说过&#xff0c;浏览器向服务端发送请求&#xff0c;服务端会给浏览器发送出响应&#xff0c;无论是哪种&#xff0c;都包含三部分。这一章&#xff0c;依旧围绕这部分内容 请求 Postman 由于前后端分离&#xff0c;对我们后端技术人员来讲&#xff0c;在开发过程中&…