Qt Quick与ROS通过UDP协议实现网络通信

news/2025/1/31 19:49:27/文章来源:https://www.cnblogs.com/rui27/p/18695911

实现目标

项目需要编写一个无人机地面站,无人机在ROS系统下运行,地面站需要与无人机建立通信,能够控制无人机起飞、降落、飞行,并能够接收无人机的状态信息。
该无人机系统的组成如下图所示:

地面站通过无线网络与无人机上位机建立通信,上位机负责将飞控的无人机状态数据转发给地面站,并接收地面站的指令,再根据指令控制无人机执行相应的任务。该文章的目标就是构建图中网络通信的部分,首先以控制无人机起飞为例。

项目通过两部分实现:
1、在Qt Quick中重写QUdpSocket类,实现地面站发送UDP消息
2、在创建一个ROS节点,接收地面站的消息并控制无人机起飞

预备知识

1、了解UDP协议

无线通信网络通过UDP协议构建,UDP(用户数据报协议)是一种无连接的网络协议,UDP的主要特点包括无连接性、不保证可靠交付和面向报文的传输方式。由于UDP不建立连接,因此减少了开销和延迟,但同时也意味着它不保证数据的可靠传输。UDP适用于那些对实时性要求高,但可以容忍一定程度数据丢失的应用。
通俗的说,UDP通信类似于ROS中的话题,发送者只管往网络中不停发送消息,并不考虑是否有人接收以及接收者是否来得及接收;而接收者则是按照指定的频率不停接收网络中的消息。(与此相类比的,TCP协议则类似于ROS中的服务,这里暂不多提,后续若有用到再记录)

2、Qt Quick的使用

QML(Qt Meta-Object Language)是一种用于创建用户界面的声明性语言。它使用JSON格式的语法来描述用户界面,可以快速地创建出具有良好交互性和动画效果的应用程序。Qt Quick是一种用于编写应用程序的框架,在该框架中,开发者通过QML编写前端程序,通过C++编写后端程序。
在使用DUP协议搭建无线通信网络前,需要先掌握QML与C++的交互,这篇博客中有很详细的教程https://blog.csdn.net/xiezhongyuan07/article/details/109245920, 该项目中用到了“将C++类名注册到QML,并在QML声明一个对象并进行访问“的方法实现QML与C++的交互。

3、掌握ROS系统的使用

该项目的ROS系统在概念上分为了3部分,第一部分负责无人机仿真,第二部分运行飞控中的程序,第三部分负责运行上位机程序。该项目的核心在于搭建网络通信,这部分代码写在上位机程序中,因此只需掌握ROS的基本使用,就能完成无线网络的搭建;但若要完成的实现该项目,还需了解PX4无人机及其offboard模式,详细内容可在PX4官网(https://docs.px4.io/main/zh/ros/mavros_offboard_cpp.html)或该博客(https://blog.csdn.net/weixin_45031928/article/details/135142557)学习。

在Qt Quick中发送UDP消息到网络

首先在.pro文件中的头部添加Qt += network,指定当前项目使用网络模块。

随后在左侧项目栏右键添加新的文件,按如下截图创建新的C++文件:



创建完C++文件后我们需要引入QUdpSocket类,并在此基础上定义我们自己的类,来完成发送UDP消息的任务。


下面贴上代码和各部分的注释

#ifndef MYUDPSOCKET_H
#define MYUDPSOCKET_H#include <QObject>
#include <QUdpSocket>  //引入QUdpSocket库//重写定义自己的MyUdpSocket类,继承自QUdpSocket
class MyUdpSocket : public QUdpSocket
{Q_OBJECT  //编译器会编译带有Q_OBJECT宏的C++类,且该宏声明了一些成员函数,以实现信号和槽的功能
public:explicit MyUdpSocket(QObject *parent = nullptr);Q_INVOKABLE void sendMessage();  //Q_INVOKABLE标识可以将该函数暴露给QML语言,在QML编程时可以直接使用该函数signals:  //定义一些信号,在调用该类的时候会发送这些信号,例如我们常用的onClicked信号和onCompeled信号
};#endif // MYUDPSOCKET_H
#include "MyUdpSocket.h"
#include <QNetworkDatagram>  //该库中的函数用来发送UDP消息
#include <QHostAddress>      //该库用来获取主机ip地址//在构造函数中绑定本机的ip地址,并设置端口号为65535
MyUdpSocket::MyUdpSocket(QObject *parent): QUdpSocket{parent}
{bind(QHostAddress::LocalHost, 65535);
}//sendMessage函数用于发送UDP消息
void MyUdpSocket::sendMessage()
{QByteArray data = "start";int bytesWritten = writeDatagram(data, 8, QHostAddress::LocalHost, 65535);  //writeDatagram用于发送UDP消息,4个参数分别是:发送的数据、字节数、ip地址、端口号(发送失败返回-1)if(bytesWritten != -1){qDebug()<<"发送成功"<<bytesWritten<<"字节";}else{qDebug()<<"发送失败";}
}

在ROS节点中接收UDP消息

在offboard模式下控制PX4无人机起飞的代码中添加上接收UDP消息的部分(星号注释之间的为添加的内容),完整代码和注释如下:

/*** @file offb_node.cpp* @brief Offboard control example node, written with MAVROS version 0.19.x, PX4 Pro Flight* Stack and tested in Gazebo Classic SITL*/#include <ros/ros.h>
#include <geometry_msgs/PoseStamped.h>
#include <mavros_msgs/CommandBool.h>
#include <mavros_msgs/SetMode.h>
#include <mavros_msgs/State.h>#include <stdio.h>    //包含所需的库函数
#include <unistd.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <string.h>
#include <arpa/inet.h>mavros_msgs::State current_state;
void state_cb(const mavros_msgs::State::ConstPtr& msg){current_state = *msg;
}geometry_msgs::PoseStamped current_pos;
void pos_cb(const geometry_msgs::PoseStamped::ConstPtr& msg){current_pos = *msg;
}int main(int argc, char **argv)
{ros::init(argc, argv, "offb_node");ros::NodeHandle nh;/*******************************************************************************/int fd = socket(AF_INET, SOCK_DGRAM, 0);  //创建socket嵌套字(socket类似于单片机里的收发缓存区?大概),AF_INET是IPV4协议簇,SOCK_DGRAM指UDP数据报if(fd == -1){perror("socket error");exit(1);}  struct sockaddr_in serv;  //创建sockaddr_in类型的服务器结构体serv,该结构体包含四个成员:地址类型sin_family、端口号sin_port、IP地址sin_addr、填充字节sin_zeromemset(&serv, 0, sizeof(serv));  //将结构体中的数据清零serv.sin_family = AF_INET;serv.sin_port = htons(65535);serv.sin_addr.s_addr = htonl(INADDR_ANY);  //INADDR_ANY参数表示绑定到所有可用的IP地址上int ret = bind(fd, (struct sockaddr*)&serv, sizeof(serv));  //绑定结构体,传入参数分别是:socket返回值、结构体地址、结构体长度if(ret == -1){perror("bind error");exit(1);}struct sockaddr_in client;  //创建客户端结构体socklen_t cli_len = sizeof(client);char buf[1024] = {0};  //创建接收数据缓存数组int start = 0;/*******************************************************************************/ros::Subscriber state_sub = nh.subscribe<mavros_msgs::State>("mavros/state", 10, state_cb);ros::Subscriber local_pos = nh.subscribe<geometry_msgs::PoseStamped>("mavros/local_position/pose", 10, pos_cb);ros::Publisher local_pos_pub = nh.advertise<geometry_msgs::PoseStamped>("mavros/setpoint_position/local", 10);ros::ServiceClient arming_client = nh.serviceClient<mavros_msgs::CommandBool>("mavros/cmd/arming");ros::ServiceClient set_mode_client = nh.serviceClient<mavros_msgs::SetMode>("mavros/set_mode");//the setpoint publishing rate MUST be faster than 2Hzros::Rate rate(20.0);// wait for FCU connectionwhile(ros::ok() && !current_state.connected){ros::spinOnce();rate.sleep();}geometry_msgs::PoseStamped target_pose;target_pose.pose.position.x = 0;target_pose.pose.position.y = 0;target_pose.pose.position.z = 2;//send a few setpoints before startingfor(int i = 100; ros::ok() && i > 0; --i){local_pos_pub.publish(target_pose);ros::spinOnce();rate.sleep();}mavros_msgs::SetMode offb_set_mode;offb_set_mode.request.custom_mode = "OFFBOARD";mavros_msgs::CommandBool arm_cmd;arm_cmd.request.value = true;ros::Time last_request = ros::Time::now();while(ros::ok()){/*******************************************************************************/if(start == 0){int recvlen = recvfrom(fd, buf, sizeof(buf), 0,   //当没有接收到数据时,程序在此阻塞;当接收到数据时,将客户端接收到的数据存入buf中,并将start置1,开始运行后续的起飞程序(struct sockaddr*)&client, &cli_len);if(recvlen == -1){perror("recvform error");exit(1);}printf("received data: %s\n", buf);start = 1;}/*******************************************************************************/if( current_state.mode != "OFFBOARD" &&(ros::Time::now() - last_request > ros::Duration(5.0))){if( set_mode_client.call(offb_set_mode) &&offb_set_mode.response.mode_sent){ROS_INFO("Offboard enabled");}last_request = ros::Time::now();} else {if( !current_state.armed &&(ros::Time::now() - last_request > ros::Duration(5.0))){if( arming_client.call(arm_cmd) &&arm_cmd.response.success){ROS_INFO("Vehicle armed");}last_request = ros::Time::now();}}local_pos_pub.publish(target_pose);ros::spinOnce();rate.sleep();// if(current_state.armed && current_state.mode == "OFFBOARD"){//     if(target_pose.pose.position.z - current_pos.pose.position.z < 0.2){//         target_pose.pose.position.x = 3;//         target_pose.pose.position.y = 4;//         target_pose.pose.position.z = 5;//     }// }}return 0;
}

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

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

5.C++提高编程

C++提高编程。C++提高编程 本阶段主要针对C++泛型编程和STL技术做详细讲解,探讨C++更深层的使用 1 模板 1.1 模板的概念 模板就是建立通用的模具,大大提高复用性 例如生活中的模板 一寸照片模板:PPT模板:模板的特点: 模板不可以直接使用,它只是一个框架 模板的通用并不是…

又来新活了!AI电商搜索,或是下一个90亿美元独角兽?

全新体验,大模型驱动的对话式购物搜索。图源:https://www.shopencore.ai/ 全新体验,大模型驱动的对话式购物搜索。 Encore, 由2024年10月成立的美国初创公司开发。定位于二手商品对话式购物搜索,最终目标为个人购物助理。 2024年12月3日获得YC(Y combinator)的50万美元天使…

qq网页版下载音乐教程

点一首音乐开始播放,务必要播放界面内只有一首音乐,然后f12调试,找到audio标签;然后复制src=”” 双引号内的内容到新标签打开,然后在播放栏,右键,就可以保存音乐了,注意有的音乐是m4a格式,下载完成后还要转换成mp3。谢雨尘安-谢雨尘安的博客

gin: 校验参数时返回自定义错误信息

一,代码 1,global/validator.go package globalimport "github.com/go-playground/validator/v10"//存放GetMessages()方法 type Validator interface {GetMessages() ValidatorMessages }//校验信息 type ValidatorMessages map[string]string// GetErrorMsg方法,…

VM笔记_Modbus通信触发流程

1,通信触发流程 ①通信配置② 接收事件新建③全局触发-事件触发4, 通信心跳配置和启用5, 效果展示

[SWPUCTF 2021 新生赛]easyupload3.0 Writeup

题目来源:NSSCTF 题目方向:Web 题目类型:文件上传 2.0的做法和1.0相同,不过用.phtml绕过就行 1.这里去了解了一下.htaccess文件: htaccess文件是Apache服务中的一个配置文件,它负责相关目录下的网页配置。通过htaccess文件,可以帮助我们实现:网页301重定向、自定义404错…

数据库性能调优中的配置参数调整:提升系统效率的关键环节

title: 数据库性能调优中的配置参数调整:提升系统效率的关键环节 date: 2025/1/31 updated: 2025/1/31 author: cmdragon excerpt: 数据库的性能直接影响到应用程序的响应能力和用户体验,因此在日常运维中,管理员需要定期对数据库系统进行性能调优。配置参数调整是数据库性…

PID 温控设计(基于 STC51)

PID 温控设计(基于 STC51) 一、需求分析 开关型控制存在的问题:加热的过程是全功率加热,三极管发热量大,温度控制振荡幅度大,控制精度较低。而通过采用PID方法能够更加精确地控制加热片处于目标温度,并在一个较小范围内浮动。精度要求:0.2℃ 温控范围 目标温度:45℃ 温…

gin: 接收参数时校验

一,安装第三方库: $ go get -u github.com/go-playground/validator/v10 go: downloading github.com/go-playground/validator/v10 v10.24.0 go: downloading github.com/gabriel-vasile/mimetype v1.4.8 go: downloading golang.org/x/crypto v0.32.0 go: downloading golan…

Java异常分类及处理

Throwable 是 Java 语言中所有错误或异常的超类,在 Java 中只有 Throwable 类型的实例才可以被抛出(throw)或者捕获(catch),它是异常处理机制的基本组成类型。实例分为 Error 和 Exception 两种。 其中,AWTError GUI图形界面化编程相关异常。Error(错误)是程序无法处理…

Apple Safari 18.3 - macOS 专属浏览器 (独立安装包下载)

Apple Safari 18.3 - macOS 专属浏览器 (独立安装包下载)Apple Safari 18.3 - macOS 专属浏览器 (独立安装包下载) 适用于 macOS Sonoma 和 macOS Ventura 的 Safari 浏览器 18 请访问原文链接:https://sysin.org/blog/apple-safari-18/ 查看最新版。原创作品,转载请保留出处…

AppSpider Pro 7.5.015 for Windows - Web 应用程序安全测试

AppSpider Pro 7.5.015 for Windows - Web 应用程序安全测试AppSpider Pro 7.5.015 for Windows - Web 应用程序安全测试 Rapid7 Dynamic Application Security Testing (DAST) released Jan 30, 2025 请访问原文链接:https://sysin.org/blog/appspider/ 查看最新版。原创作品…