ROS1基础入门:从零搭建机器人通信系统(Python/C++)

文章目录


前言

ROS(Robot Operating System)是机器人领域的“瑞士军刀”,通过分布式节点通信模块化设计,解决了机器人软件开发中的复杂性问题。本文将从工作空间搭建开始,手把手带你实现话题发布/订阅服务请求/响应参数服务器配置,并提供完整的Python/C++代码示例与调试技巧。


一、ROS核心概念:5分钟理解机器人通信架构

1. 节点(Node):功能最小单元

  • 作用:独立运行的功能模块(如摄像头驱动、PID控制器)。
  • 特点
    • 每个节点是一个可执行程序(如talker_node)。
    • 支持多语言(C++/Python/Java)。
    • 通过节点名唯一标识(如/camera_node)。

2. 主节点(Master):通信调度中心

  • 作用
    • 注册节点信息(如节点名、提供的服务)。
    • 匹配话题发布者与订阅者、服务请求者与服务提供者。
  • 特点
    • 单实例运行(通过roscore启动)。
    • 无主节点时,节点间无法通信。

3. 话题(Topic):异步消息总线

  • 通信模式
    • 发布-订阅(Pub/Sub):一对多广播(如传感器数据流)。
    • 特点
      • 数据自动序列化传输。
      • 发布者与订阅者无需直接交互。
      • 支持多语言混合通信(Python发布者+C++订阅者)。

4. 服务(Service):同步请求-响应

  • 通信模式
    • 请求-响应(RPC):一对一调用(如查询电池电量)。
    • 特点
      • 客户端发送请求后立即阻塞,等待服务端返回结果。
      • 适用于需要确定性结果的场景。

5. 参数服务器(Parameter Server):全局配置仓库

  • 作用
    • 存储机器人全局配置(如最大速度、传感器校准参数)。
    • 支持动态参数更新(无需重启节点)。
  • 访问方式
    • 通过rosparam命令行工具。
    • 在代码中通过节点句柄读写。

二、实战第一步:创建ROS工作空间

1. 创建目录结构

# 创建工作空间根目录(catkin_ws可以任意替换你喜欢的名字)mkdir -p ~/catkin_ws/src cd ~/catkin_ws/ 

2. 编译空工作空间

# 编译(生成开发环境) catkin_make 

我创建了ros_learn/ros1用于存放ros1的相关代码

![在这里插入图片描述](https://i-blog.ZEEKLOGimg.cn/direct/c5d48ea4077f4c989111c070ccb457fa.png

3. 配置环境变量

# 临时生效source devel/setup.bash # 永久生效(添加到~/.bashrc,根据自己的情况进行替换即可)echo"source ~/ros_lean/ros1/devel/setup.bash">> ~/.bashrc source ~/.bashrc 

4.工作空间组成

catkin_ws/(工作空间根目录)
src/(源码包目录,放置 ROS 的包)
build/(编译时的构建空间,生成中间文件)
devel/(开发空间,包含可直接运行的产物、环境设置脚本)
install/(安装空间,可选;完整安装后对外暴露的产物放在这里)
logs/(构建/运行日志,某些工具会生成)


三、实战2:话题通信(Python/C++)

对于ros1而已,一个工作空间下可以放多个程序包实现,源代码一般放在src目录下。
包级目录示例:my_pkg/
CMakeLists.txt(包的构建脚本)
package.xml(包的元数据与依赖声明)
src/(实现源码,可按需要放子目录)
include/(头文件,便于对外暴露的接口)
msg/(自定义消息定义,.msg 文件)
srv/(自定义服务定义,.srv 文件)
action/(自定义动作定义,.action 文件,若有)
launch/(启动文件,.launch 文件,使用 roslaunch 启动多个节点)
config/(配置文件,如 YAML/JSON,用于参数化节点)
scripts/(Python 脚本等可执行程序)
test/(测试用例)

1. Python实现(ros-noetic默认python版本为Python3,18.04的系统安装的是melodic,默认版本为Python2)

这里我创建名为talk_listen/scripts存放以下两个python代码。
可以在vsode中管理你的代码并安装喜欢的插件,往往有事半功倍的效果。

在这里插入图片描述

(1)发布者节点(talker.py)

#!/usr/bin/env python3import rospy from std_msgs.msg import String deftalker():# 初始化节点(名称唯一) rospy.init_node('talker', anonymous=True)# 创建话题发布者(队列大小10) pub = rospy.Publisher('chatter', String, queue_size=10)# 设置发布频率(10Hz) rate = rospy.Rate(10) count =0whilenot rospy.is_shutdown(): msg =f"Hello ROS {count}" rospy.loginfo(msg) pub.publish(msg) rate.sleep() count +=1if __name__ =='__main__':try: talker()except rospy.ROSInterruptException:pass

(2)订阅者节点(listener.py)

#!/usr/bin/env python3import rospy from std_msgs.msg import String defcallback(msg): rospy.loginfo(f"Received: {msg.data}")deflistener(): rospy.init_node('listener', anonymous=True) rospy.Subscriber('chatter', String, callback) rospy.spin()if __name__ =='__main__': listener()
在这里插入图片描述

这里我们简单的实现最基础的话题通信。
代码写好后我们可以直接使用python命令,无需编译。因为python3已经安装了ros的相关包
我们可以直接执行看一下效果。
ros1的程序执行都依赖于roscore。因此我们需要开三个终端进行测试。

在这里插入图片描述


单独执行talk.py,每0.1秒发送一次,Hello ROS并计数

执行listener.py,以同样的频率进行接受

在这里插入图片描述


你会发现在网络良好的状态下接受和发送接近同步

2. C++实现

python执行ros1代码并不依赖于本身的工作空间,但是在实际生产过程中,我们需要通过各种ros包的相互依赖关系完成作业。python只是快速实现我们所需功能的方法,它简单易上手,能够快速观察到想要的结果。
这里我将讲述正确创建一个ros包的过程。

#首先让我们进入工作空间下创建的src文件夹,将之前创建的talk_listen文件夹删除,在src目录下输入这样一行指令#示例包名为 talk_listen,依赖 roscpp、std_msgs, catkin_create_pkg talk_listen roscpp std_msgs 
在这里插入图片描述


此时才正式出现了我在python代码中所写的正常的ros包组成。scripts可以自己创建。
接着我们在刚自动创建的src文件夹下写好我们的代码

(1)发布者节点(talker.cpp)

#include"ros/ros.h"#include"std_msgs/String.h"intmain(int argc,char**argv){ ros::init(argc, argv,"talker"); ros::NodeHandle nh; ros::Publisher pub = nh.advertise<std_msgs::String>("chatter",10); ros::Rate rate(10);// 10Hzint count =0;while(ros::ok()){ std_msgs::String msg; msg.data ="Hello ROS "+ std::to_string(count++);ROS_INFO("%s", msg.data.c_str()); pub.publish(msg); rate.sleep();}return0;}

(2)订阅者节点(listener.cpp)

#include"ros/ros.h"#include"std_msgs/String.h"voidcallback(const std_msgs::String::ConstPtr& msg){ROS_INFO("Received: %s", msg->data.c_str());}intmain(int argc,char**argv){ ros::init(argc, argv,"listener"); ros::NodeHandle nh; ros::Subscriber sub = nh.subscribe("chatter",10, callback); ros::spin();return0;}
在这里插入图片描述


红色波浪线可以不用管,哈哈,这是找不到依赖头文件,我们会使用cmake工具来找到我们的依赖,你也可以试着去解决,如果你觉得看着别扭。实际上这不影响我们代码的实现。

3. 编译与运行

对于c/c++代码而已,需要将代码编译链接为可执行文件。这里我们需要用到cmake工具。实际上一个ros包乃至一个工作空间一般都是用cmake工具作为依赖管理的。

在这里插入图片描述


创建ros包后会自动在ros包的目录下创建cmakelist文件,里面写了各种示例,英语好的同学肯定能够看懂~
一个典型的ROS包中的CMakeLists.txt包含以下部分:

cmake_minimum_required(VERSION 3.0.2)# 指定CMake最低版本 project(my_ros_package)# 定义项目名称(需与包名一致)# 查找依赖的CMake包(包括ROS组件) find_package(catkin REQUIRED COMPONENTS roscpp std_msgs message_generation # 若需生成消息/服务,需添加此依赖)# 配置catkin相关参数(如安装路径、系统依赖等) catkin_package( CATKIN_DEPENDS roscpp std_msgs # 声明本包依赖的ROS包 DEPENDS system_lib # 声明非ROS系统依赖(如Eigen、OpenCV))# 添加头文件路径 include_directories(include ${catkin_INCLUDE_DIRS})# 定义可执行文件 add_executable(my_node src/my_node.cpp) target_link_libraries(my_node ${catkin_LIBRARIES})# 安装规则(将文件安装到系统目录) install(TARGETS my_node RUNTIME DESTINATION ${CATKIN_PACKAGE_BIN_DESTINATION})

find_package(catkin REQUIRED COMPONENTS …)
用于声明构建本包所需的ROS依赖包(如roscpp、std_msgs)。
若依赖包未安装,CMake会报错。
catkin_package()
配置包的元信息,供其他包调用时使用。重要参数:
INCLUDE_DIRS:声明本包的头文件路径。
LIBRARIES:声明本包生成的库。
CATKIN_DEPENDS:声明本包运行时依赖的ROS包。

其他的相信聪明的你肯定会自己下去偷偷学习了

这里就直接给大家现成的cmake文件去使用

cmake_minimum_required(VERSION 3.0.2) project(talk_listen)# 查找依赖的ROS包 find_package(catkin REQUIRED COMPONENTS roscpp std_msgs )# 配置catkin包 catkin_package( CATKIN_DEPENDS roscpp std_msgs )# 设置包含目录 include_directories( include ${catkin_INCLUDE_DIRS})# 创建C++可执行文件 add_executable(talk_node src/talker.cpp) target_link_libraries(talk_node ${catkin_LIBRARIES}) add_executable(listener_node src/listener.cpp) target_link_libraries(listener_node ${catkin_LIBRARIES})# 安装Python脚本 install(PROGRAMS scripts/listener.py scripts/talker.py DESTINATION ${CATKIN_PACKAGE_BIN_DESTINATION})# 安装可执行文件 install(TARGETS talk_node listener_node RUNTIME DESTINATION ${CATKIN_PACKAGE_BIN_DESTINATION})
# 回到工作空间目录下,编译工作空间 catkin_make 
在这里插入图片描述


如果没有报错,编译百分百通过

在这里插入图片描述


在这里插入图片描述


此时会出现build和devel文件夹存放我们编译过程和产物
此时我们重新打开一个终端或者在当前终端中刷新环境变量

source ~/.bashrc 

仍然打开三个终端

#终端一 roscore #终端二 rosrun talk_listen talk_node #终端三 rosrun talk_listen listener_node 
在这里插入图片描述


如果rosrun执行提示找不到某文件,可能需要重新刷新环境变量哦~

如果你觉得一次开三个终端太麻烦,那么你可以学习launch文件的使用。

Launch文件是ROS(Robot Operating System)中的一种XML格式的配置文件,用于批量启动和管理多个ROS节点。它允许通过单个命令启动整个ROS应用程序,而不需要手动逐个启动每个节点。(一条指令管饱)
在原有cmake文件的结尾添加

# 安装launch文件 install(DIRECTORY launch/ DESTINATION ${CATKIN_PACKAGE_SHARE_DESTINATION}/launch )

这里给出启动两种代码launch文件,在ros包中创建launch文件夹并创建文件即可

在这里插入图片描述
<launch><!-- 同时启动C++版本的talker和listener --><node pkg="talk_listen"type="talk_node"name="talker"output="screen"/><node pkg="talk_listen"type="listener_node"name="listener"output="screen"/></launch>
<launch><!-- 启动Python版本的节点 --><node pkg="talk_listen"type="talker.py"name="python_talker"output="screen"/><node pkg="talk_listen"type="listener.py"name="python_listener"output="screen"/></launch>

来到工作空间目录重新编译(每次修改cmake文件或者c++文件等都需要进行编译哦~)

catkin_make source ~/.bashrc roslaunch talk_listen talk_listen.launch 
在这里插入图片描述


launch文件会自动检测是否加载ros核心如果没有会自动创建。

在这里插入图片描述


之前说过,我们写的python代码没有包含在ros包中,现在我们在cmake中添加了增加.py文件的支持
我们可以使用rosrun代码执行python代码
在执行代码前我们需要进行权限赋予,否则会权限报错

sudochmod +x scripts/* roscore rosrun talk_listen talker.py rosrun talk_listen listener.py 
在这里插入图片描述


在这里插入图片描述


文章后面的代码就不给大家示例和教程了,相信大家可以自己补全,我只把最后结果贴出来。


四、实战3:服务通信(Python/C++)

1. Python实现

(1)服务端(add_two_ints_server.py)

#!/usr/bin/env python3import rospy from rospy_tutorials.srv import AddTwoInts, AddTwoIntsResponse defhandle_add(req): result = req.a + req.b rospy.loginfo("Adding %d + %d = %d", req.a, req.b, result)return AddTwoIntsResponse(result)defadd_two_ints_server(): rospy.init_node('add_two_ints_server') s = rospy.Service('add_two_ints', AddTwoInts, handle_add) rospy.loginfo("AddTwoInts server ready.") rospy.spin()if __name__ =='__main__': add_two_ints_server()

(2)客户端(add_two_ints_client.py)

#!/usr/bin/env python3import rospy from rospy_tutorials.srv import AddTwoInts defadd_two_ints_client(a, b): rospy.wait_for_service('add_two_ints', timeout=10)try: add_two_ints = rospy.ServiceProxy('add_two_ints', AddTwoInts) response = add_two_ints(a, b)return response.sumexcept rospy.ServiceException as e: rospy.logerr("Service call failed: %s", e)returnNoneif __name__ =='__main__': rospy.init_node('add_two_ints_client') result = add_two_ints_client(10,20)if result isnotNone:print("10 + 20 = %d"% result)else:print("Service call failed")
在这里插入图片描述

2. C++实现

(1)服务端(add_two_ints_server.cpp)

#include"ros/ros.h"#include"std_srvs/AddTwoInts.h"boolhandle_add(std_srvs::AddTwoInts::Request &req, std_srvs::AddTwoInts::Response &res){ res.sum = req.a + req.b;ROS_INFO("Request: a=%ld, b=%ld",(longint)req.a,(longint)req.b);ROS_INFO("Sending back sum: %ld",(longint)res.sum);returntrue;}intmain(int argc,char**argv){ ros::init(argc, argv,"add_two_ints_server"); ros::NodeHandle nh; ros::ServiceServer service = nh.advertiseService("add_two_ints", handle_add);ROS_INFO("Ready to add two ints."); ros::spin();return0;}

(2)客户端(add_two_ints_client.cpp)

#include"ros/ros.h"#include"std_srvs/AddTwoInts.h"intmain(int argc,char**argv){ ros::init(argc, argv,"add_two_ints_client"); ros::NodeHandle nh;if(argc !=3){ROS_ERROR("Usage: add_two_ints_client X Y");return1;} ros::ServiceClient client = nh.serviceClient<std_srvs::AddTwoInts>("add_two_ints"); std_srvs::AddTwoInts srv; srv.request.a =atoll(argv[1]); srv.request.b =atoll(argv[2]);if(client.call(srv)){ROS_INFO("Sum: %ld",(longint)srv.response.sum);}else{ROS_ERROR("Service call failed");return1;}return0;}
![在这里插入图片描述](https://i-blog.ZEEKLOGimg.cn/direct/8a46116cb08e491aa4ff3b94030b8ab4.png
![在这里插入图片描述](https://i-blog.ZEEKLOGimg.cn/direct/734a0f27baf64da0a7ab657d07f1ab7a.pn


在这里插入图片描述

五、实战4:参数服务器(Python/C++)

1. Python实现

#!/usr/bin/env python3import rospy defparam_demo(): rospy.init_node('param_demo') rospy.loginfo("Parameter Demo Started")try:# 设置参数 rospy.set_param("robot_name","Robo1") rospy.set_param("/global_speed",5.0) rospy.set_param("~private_param",True)# 获取参数(带默认值) robot_name = rospy.get_param("robot_name","DefaultRobot") global_speed = rospy.get_param("/global_speed",1.0) private_param = rospy.get_param("~private_param",False) rospy.loginfo("Robot Name: %s", robot_name) rospy.loginfo("Global Speed: %.1f", global_speed) rospy.loginfo("Private Param: %s",str(private_param))# 检查参数是否存在if rospy.has_param("global_speed"): rospy.loginfo("global_speed exists")else: rospy.loginfo("global_speed deleted")# 搜索参数 param_names = rospy.get_param_names() rospy.loginfo("Available parameters:")for name in param_names: rospy.loginfo(" %s", name)# 获取所有参数 all_params = rospy.get_param("/") rospy.loginfo("All parameters on root: %s",str(all_params))# 删除参数 rospy.delete_param("global_speed")# 验证删除if rospy.has_param("global_speed"): rospy.logwarn("global_speed still exists!")else: rospy.loginfo("global_speed successfully deleted")# 设置参数列表和字典 rospy.set_param("sensor_ids",[1,2,3,4]) rospy.set_param("robot_config",{"max_speed":10.0,"min_speed":0.5})# 获取复杂参数 sensor_ids = rospy.get_param("sensor_ids",[]) robot_config = rospy.get_param("robot_config",{}) rospy.loginfo("Sensor IDs: %s",str(sensor_ids)) rospy.loginfo("Robot Config: %s",str(robot_config))except rospy.ROSException as e: rospy.logerr("ROS error occurred: %s",str(e))except Exception as e: rospy.logerr("Unexpected error: %s",str(e))if __name__ =='__main__': param_demo()
在这里插入图片描述

2. C++实现

#include"ros/ros.h"intmain(int argc,char**argv){ ros::init(argc, argv,"param_demo"); ros::NodeHandle nh;// 设置参数 nh.setParam("robot_name","Robo1"); nh.setParam("/global_speed",5.0); nh.setParam("~private_param",true);// 获取参数 std::string robot_name;double global_speed;bool private_param; nh.getParam("robot_name", robot_name); nh.getParam("/global_speed", global_speed); nh.getParam("~private_param", private_param);ROS_INFO("Robot Name: %s", robot_name.c_str());ROS_INFO("Global Speed: %.1f", global_speed);ROS_INFO("Private Param: %d", private_param);// 删除参数 nh.deleteParam("global_speed");// 检查参数是否存在if(nh.hasParam("global_speed")){ROS_INFO("global_speed exists");}else{ROS_INFO("global_speed deleted");}return0;}
在这里插入图片描述

六、调试神器:ROS命令行工具箱

1. 基础命令

# 查看活跃节点 rosnode list # 查看节点详情 rosnode info /talker # 查看当前话题 rostopic list # 查看话题消息类型 rostopic type /chatter # 查看实时数据(文本模式) rostopic echo /chatter # 查看服务列表 rosservice list # 调用服务(手动输入参数) rosservice call /add_two_ints "a: 10\nb: 20"

2. 可视化工具

# 启动rqt图形界面 rqt # 使用rqt_plot实时绘图 rqt_plot /chatter # 启动rviz三维可视化 rviz 

七、总结与进阶路线

核心结论

  1. 话题:适合传感器数据流(如激光雷达、摄像头)。
  2. 服务:适合需要即时反馈的操作(如导航目标设定)。
  3. 参数服务器:适合全局配置管理(如机器人物理参数)。

进阶方向

  1. 消息记录与回放:使用rosbag保存/复现实验数据。
  2. 动态参数配置:通过dynamic_reconfigure实时调整参数。
  3. 多机通信:配置ROS_MASTER_URI实现多机器人协同。

通过掌握这些技能,你已经具备了开发复杂机器人系统的核心能力。下一步可以尝试:

  • 集成摄像头实现目标检测
  • 使用SLAM算法构建地图
  • 开发自主导航功能

遇到问题时,善用rostopicrosservice等命令行工具,或查阅http://wiki.ros.org/。祝你在机器人世界探索愉快! 🚀

Read more

智能进化:人工智能对上位机系统的全面重塑与影响分析

智能进化:人工智能对上位机系统的全面重塑与影响分析

文章目录 * **一、 内核重构:从“监控窗口”到“智能决策引擎”** * **二、 场景深化:从“单一监控”到“全域智能”** * **三、 架构演进:从“封闭塔楼”到“开放云边端协同体”** * **四、 挑战与破局:智能征途上的关键障碍** * **五、 未来图景:向自适应与共生智能演进** * **六、 产业影响:重塑竞争格局与价值分配** * **结论** 在工业自动化与信息化融合的浪潮中,上位机(Supervisory Control and Data Acquisition, SCADA/HMI系统)作为连接物理设备与数字世界的“神经中枢”,正经历一场由人工智能(AI)驱动的深刻革命。这场变革远非简单的功能叠加,而是从核心架构、功能范式到生态角色的系统性重构。AI的融入,正使上位机从一个被动的数据监控与指令执行平台,演变为一个具备感知、分析、

实测|WSL2 从零部署 OpenClaw AI 助手:安装配置与实战运行教程

实测|WSL2 从零部署 OpenClaw AI 助手:安装配置与实战运行教程

【本文作者:Rickton】 本文是 2026 年最新可用的 WSL2 + OpenClaw 完整部署教程,面向零基础用户,从开启 WSL2、安装 Ubuntu、配置 Node.js 到一键启动 OpenClaw 控制台,一步一命令,复制粘贴即可运行。 解决 Windows 原生部署报错、环境不兼容、端口不通、Node 版本不对等常见问题,适合本地搭建 AI 助手、学习 Agent 开发。 第一部分:认识 WSL2—— Windows 中的 Linux 环境 WSL2 是什么? WSL2 (Windows Subsystem for Linux 2) 是微软官方提供的一项功能,

本地离线部署AI大模型:OpenClaw + Ollama + Qwen3.5:cloud/Qwen3:0.6b 超详细教程(无需GPU)

本地离线部署AI大模型:OpenClaw + Ollama + Qwen3.5:cloud/Qwen3:0.6b 超详细教程(无需GPU)

前言 随着开源大模型越来越成熟,我们完全可以在自己电脑上本地运行AI,不联网、不上传数据、免费使用,隐私性极强。 今天这篇文章,我会一步步带你完成:Ollama + Qwen3.5:cloud(主力模型)+ Qwen3:0.6b(轻量备选)+ OpenClaw 的本地部署,实现一个属于自己的本地聊天AI,兼顾效果与低配置适配。 一、项目介绍 本项目实现本地离线运行阿里通义千问系列大模型(Qwen3.5:cloud 主力模型 + Qwen3:0.6b 轻量备选模型),全程不需要云端API,不需要高性能显卡,普通电脑就能跑,可根据自身电脑配置选择对应模型。 用到的工具: * Ollama:最简单的本地大模型管理工具,一键拉取、运行、管理模型 * Qwen3.5:cloud:阿里云开源的轻量高性能大语言模型,对话效果强、适配本地部署,作为主力使用

API 调用基础:执行式AI必备网络请求知识

API 调用基础:执行式AI必备网络请求知识

API 调用基础:执行式AI必备网络请求知识 📝 本章学习目标:本章是入门认知部分,帮助零基础读者建立对AI Agent的初步认知。通过本章学习,你将全面掌握"API 调用基础:执行式AI必备网络请求知识"这一核心主题。 一、引言:为什么这个话题如此重要 在AI Agent快速发展的今天,API 调用基础:执行式AI必备网络请求知识已经成为每个开发者和研究者必须了解的核心知识。无论你是技术背景还是非技术背景,理解这一概念都将帮助你更好地把握AI时代的机遇。 1.1 背景与意义 💡 核心认知:AI Agent正在从"对话工具"进化为"执行引擎",能够主动完成任务、调用工具、与外部世界交互。这一变革正在深刻改变我们的工作和生活方式。 从2023年AutoGPT的横空出世,到如今百花齐放的Agent生态,短短一年多时间,执行式AI已经从概念走向落地。根据最新统计,全球AI Agent市场规模已突破百亿美元,年增长率超过100%