接着前面的博客,我们继续深入,我们先聊自定义接口消息。
一. ROS2自定义接口消息概念介绍
回想一下前面博客,话题用的消息msg是一个String类型,服务接口类型用的是官方示例里自定义的AddTwoInts类型,动作接口类型是自定义的。实际上不管是话题,服务,动作都是可以自定义接口的。接口的作用是为了定义传输数据的结构,这样方便分布在不同位置的程序间,互相收发数据。接口 = 通信协议 = 数据格式约定。 没有接口,节点之间根本不知道对方发的是什么、怎么解析。
1. 话题、服务、动作接口(数据格式约定)的区别,自定义接口要满足的规则
| 通信方式 | 接口文件后缀 | 结构 | 用途 |
|---|---|---|---|
| 话题(Topic) | .msg |
一段数据 | 连续传输(传感器、速度) |
| 服务(Service) | .srv |
两段数据(请求 + 响应) | 一次性调用(查询、开关) |
| 动作(Action) | .action |
三段数据(目标 + 结果 + 反馈) | 长时间任务(导航、机械臂) |
如下是举例:
话题接口.msg
int32 X
int32 Y
int32 Z
服务接口.srv
# 请求(Request)
int32 A
int32 B
int32 C
---
# 响应(Response)
int32 Sum
int32 Accumulate
动作接口.action
# 目标(Goal)
float32 TargetX
float32 TargetY
---
# 结果(Result)
bool Success
---
# 反馈(Feedback)
float32 Progress
后面再自定义接口类型时,按照上面方式照搬即可:
---是用来区分多个部分
每个不分可支持任意多个变量
命名规则:
- 必须大写字母开头
- 不能有下划线
_ - 只能用字母、数字
不用管为什么要满足这些规则,毕竟是人家制定的游戏规则,咱们按照这个来做就行。
2.可直接使用的接口
可看到,在前面博客介绍话题时,发送的消息并没有去自定义接口。那是因为ROS2自带自带标准接口类型 ,或者提供的官方示例(随安装ROS2时自动安装上了),不用自己定义,直接拿来用即可。
1)自带标准数据类型
int8 int16 int32 int64
uint8 uint16 uint32 uint64
float32 float64
bool # 真/假
string # 字符串
int32[] # 整数数组
float64[] # 浮点数数组
string[] # 字符串数组
2)话题(Topic)自带常用高级接口
几何消息(控制机器人)
Point # 点 (x,y,z)
Pose # 位姿 (位置+朝向)
Twist # 速度 (线速度+角速度)
Quaternion # 四元数
Vector3 # 三维向量
传感器消息(雷达、相机、IMU)
mage # 图片
Imu # 陀螺仪/加速度计
LaserScan # 激光雷达
Joy # 手柄
Range # 超声波
导航消息
Odometry # 里程计
Path # 路径
状态 / 日志
Header # 时间戳+坐标系
Empty # 空消息
Bool # 布尔
Int32 # 整数
Float32 # 浮点数
String # 字符串
3)服务(Service)自带常用接口
最常用官方服务
Empty # 无请求、无响应
SetBool # 请求:bool,响应:bool+string
Trigger # 请求:空,响应:成功+消息
示例中服务
AddTwoInts
SetBool
Trigger
Empty
导航 / 控制服务
nav2_msgs
ros2_control
ROS2 的示例服务(含 AddTwoInts)都在 example_interfaces 包里;
系统自带官方服务在 std_srvs;
小乌龟示例在 turtlesim。
还有就是要手动安装的服务,可以通过第三方包直接安装,如Nav2 和 ros2_control
sudo apt update
sudo apt install ros-humble-navigation2 ros-humble-nav2-bringup
sudo apt install ros-humble-ros2-control ros-humble-ros2-controllers
分别对应如下的路径:
/opt/ros/humble/share/example_interfaces/srv/
/opt/ros/humble/share/std_srvs/srv/
/opt/ros/humble/share/turtlesim/srv/
/opt/ros/humble/share/nav2_msgs/srv/
/opt/ros/humble/share/control_msgs/srv/
4)动作自带标准接口
example_interfaces/action/Fibonacci(官方教程用)
turtlesim/action/RotateAbsolute(小乌龟示例)
其它是安装第三方库,能用的动作接口,如Nav2、MoveIt2带的,不属于基础 ROS2 默认安装。
3. 小乌龟示例介绍
turtlesim(小乌龟)是自带完整 "话题 + 服务 + 动作" 三合一的官方示例 ,接口全在 turtlesim 包里,安装 ROS2 桌面版就自带。
话题(Topic)- 持续数据流,主要示例了如下3个:
/turtle1/cmd_vel → geometry_msgs/msg/Twist(速度指令,控制运动)
/turtle1/pose → turtlesim/msg/Pose(乌龟坐标 + 角度)
/turtle1/color_sensor → turtlesim/msg/Color(轨迹颜色)
服务(Service)- 请求 - 应答,主要示例了如下
/clear → std_srvs/srv/Empty(清轨迹)
/reset → std_srvs/srv/Empty(重置)
/spawn → turtlesim/srv/Spawn(生成新乌龟)
/kill → turtlesim/srv/Kill(杀死乌龟)
/turtle1/set_pen → turtlesim/srv/SetPen(设置画笔)
/turtle1/teleport_absolute → turtlesim/srv/TeleportAbsolute(绝对瞬移)
/turtle1/teleport_relative → turtlesim/srv/TeleportRelative(相对瞬移)
动作(Action)- 带反馈 + 可取消(ROS2 重点),主要示例了如下
/turtle1/rotate_absolute → turtlesim/action/RotateAbsolute(转到绝对角度,有进度反馈)
接口位置全在全在 /opt/ros/humble/share/turtlesim/ 下
消息:/opt/ros/humble/share/turtlesim/msg/
服务: /opt/ros/humble/share/turtlesim/srv/
动作:/opt/ros/humble/share/turtlesim/action/
但依赖的公共接口(不在 turtlesim 里)
geometry_msgs/msg/Twiststd_srvs/srv/Emptyrcl_interfaces/...(参数服务)
那想一想为什么安装ROS2会自带安装小乌龟示例呢?
这和一些开源软件,商业软件一样,要方便一个没有接触过的人自己学习。一般都是通过提供sample示例 + 帮助文档方式让新手快速入门,并快速展示自己的肌肉和实力。那么小乌龟示例的作用也在此,其虽小,但五脏俱全。
1)让新手不用真实机器人,也能立刻跑通 ROS2,它有模拟机器人的功能,方便身边没有实物机器人的时候做一些验证
2)一次性演示:话题 + 服务 + 动作
3)还能用来测试你的 ROS2 环境是否正常
4)可以认为小乌龟 = ROS2 的 "Hello World,通过它能够快速入门。
二. ROS2自定义接口消息使用
1. 自己手写话题、服务、动作接口
为了演示,这边一次性创建所有类型接口,并且会创建多个各类型接口,一个是单变量,一个是多变量。
ros2 pkg create --build-type ament_cmake my_custom_interfaces
然后在包目录下,去手动或者命令创建msg,srv,action文件夹,注意名字得就是它们,不要改名。这些名称是 ROS 社区约定俗成的标准目录名,不是强制语法,但全行业统一遵守,目的是规范结构、一眼看懂用途、工具自动识别。

然后在msg,srv,action中添加接口文件,注意首字母需大写,且不能有下划线如下:

Msgdemo1.msg如下:
#名字
string name
#年龄
int32 age
Msgdemo2.msg如下:
#爱好
string hobby
Srvdemo1.srv如下:
# 请求 Request
int32 score # 得分
float32 weights # 体重
---
# 响应 Response
bool success
string message #得分值和体重值
Srvdemo2.srv如下:
# 请求 Request
string address
---
# 响应 Response
string message #地址
Actiondemo1.action如下:
# Goal 目标
int32 count
int32 x
int32 y
---
# Feedback 过程反馈
int32 index #计算到第几次了
int32 feedback_c #和
int32 feedback_accumulate #积
---
# Result 最终结果
float32 sum
float32 accumulate
Actiondemo2.action如下:
# Goal 目标
int32 count
---
# Feedback 过程反馈
int32 index #计算到第几次了
---
# Result 最终结果
float32 sum
接下来修改CMakeLists.txt
cmake_minimum_required(VERSION 3.8)
project(my_custom_interfaces)
if(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_CXX_COMPILER_ID MATCHES "Clang")
add_compile_options(-Wall -Wextra -Wpedantic)
endif()
# find dependencies
find_package(ament_cmake REQUIRED)
# uncomment the following section in order to fill in
# further dependencies manually.
# find_package(<dependency> REQUIRED)
# 生成接口
find_package(rosidl_default_generators REQUIRED)
rosidl_generate_interfaces(${PROJECT_NAME}
"msg/Msgdemo1.msg"
"msg/Msgdemo2.msg"
"srv/Srvdemo1.srv"
"srv/Srvdemo2.srv"
"action/Actiondemo1.action"
"action/Actiondemo2.action"
)
if(BUILD_TESTING)
find_package(ament_lint_auto REQUIRED)
# the following line skips the linter which checks for copyrights
# comment the line when a copyright and license is added to all source files
set(ament_cmake_copyright_FOUND TRUE)
# the following line skips cpplint (only works in a git repo)
# comment the line when this package is in a git repo and when
# a copyright and license is added to all source files
set(ament_cmake_cpplint_FOUND TRUE)
ament_lint_auto_find_test_dependencies()
endif()
ament_package()
修改package.xml
<?xml version="1.0"?>
<?xml-model href="http://download.ros.org/schema/package_format3.xsd" schematypens="http://www.w3.org/2001/XMLSchema"?>
<package format="3">
<name>my_custom_interfaces</name>
<version>0.0.0</version>
<description>TODO: Package description</description>
<maintainer email="yahboom@168.com">jetson</maintainer>
<license>TODO: License declaration</license>
<buildtool_depend>ament_cmake</buildtool_depend>
<buildtool_depend>rosidl_default_generators</buildtool_depend>
<exec_depend>rosidl_default_runtime</exec_depend>
<depend>action_msgs</depend>
<member_of_group>rosidl_interface_packages</member_of_group>
<test_depend>ament_lint_auto</test_depend>
<test_depend>ament_lint_common</test_depend>
<export>
<build_type>ament_cmake</build_type>
</export>
</package>
接下来编译接口包
colcon build --packages-select my_custom_interfaces
编译成功效果

如下是验证接口是否生成成功:
首先用interface show下接口
ros2 interface show 消息类型
# 示例
ros2 interface show std_msgs/msg/String
注:ros2 interface list | grep 可以搜索包含关键字的接口。
这边用如下两句话看下上面定义的话题消息接口。
python
ros2 interface show my_custom_interfaces/msg/Msgdemo1
ros2 interface show my_custom_interfaces/msg/Msgdemo2
ros2 interface show my_custom_interfaces/srv/Srvdemo1
ros2 interface show my_custom_interfaces/srv/Srvdemo2
ros2 interface show my_custom_interfaces/action/Actiondemo1
ros2 interface show my_custom_interfaces/action/Actiondemo2
能正常展示字段,说明接口编译完成。


2. 接下来就测试自己手写的这些接口吧
1)测试话题接口
下面开始代码调用接口示例(即在一个可执行程序中的节点对象中,去使用话题发布接收满足上面数据规则的接口消息)。这边博主继续复用之前的python包,新建一个topic_use_customer_interface_publisher_side.py文件,在里面实现话题的发布,代码如下:
python
import rclpy
from rclpy.node import Node
from my_custom_interfaces.msg import Msgdemo1
from my_custom_interfaces.msg import Msgdemo2
from rclpy.executors import MultiThreadedExecutor
class TopicTalker1(Node):
def __init__(self):
super().__init__("topic_publisher_node1")
self.pub = self.create_publisher(Msgdemo1, "/Msgdemo1_topic", 10)
self.timer1 = self.create_timer(1.0, self.pub_cb1)
self.timer2 = self.create_timer(1.0, self.pub_cb2)
self.count = 0
def pub_cb1(self):
msg = Msgdemo1()
msg.name = "topic_publisher_node1_timer1"
msg.age = 5
self.pub.publish(msg)
self.get_logger().info("节点1_定时器1发布数据")
def pub_cb2(self):
msg = Msgdemo1()
msg.name = "topic_publisher_node1_timer2"
msg.age = 15
self.pub.publish(msg)
self.get_logger().info("节点1_定时器2发布数据")
class TopicTalker2(Node):
def __init__(self):
super().__init__("topic_publisher_node2")
self.pub = self.create_publisher(Msgdemo2, "/Msgdemo2_topic", 10)
self.timer = self.create_timer(1.0, self.pub_cb)
def pub_cb(self):
msg = Msgdemo2()
msg.hobby = "/topic_publisher_node2"
self.pub.publish(msg)
self.get_logger().info("节点2发布数据")
def main(args=None):
# 全局初始化一次ROS上下文
rclpy.init(args=args)
node1 = TopicTalker1()
node2 = TopicTalker2()
# 多线程,让两个节点同时跑
executor = MultiThreadedExecutor()
executor.add_node(node1)
executor.add_node(node2)
try:
executor.spin()
finally:
executor.shutdown()
node1.destroy_node()
node2.destroy_node()
rclpy.shutdown()
if __name__ == "__main__":
main()
再创建一个名为topic_use_customer_interface_subscriber_side.py的文件,在里面实现话题的订阅
python
# 导入相关的库
import rclpy
from rclpy.node import Node
from my_custom_interfaces.msg import Msgdemo1
from my_custom_interfaces.msg import Msgdemo2
class Topic_Sub(Node):
def __init__(self, name):
super().__init__(name)
# self.create_subscription(
# msg_type, # 1. 消息类型(必须)
# topic, # 2. 话题名称(必须)
# callback, # 3. 收到消息后的回调函数(必须)
# qos_profile, # 4. 队列长度(必须)
# callback_group=None, # 5. 回调组(可选)
# )
# 4个必须参数(你写代码必写)
# msg_type 消息类型,如String、Image、LaserScan
# topic 话题名字字符串,如"my_topic"
# callback 收到消息后自动调用的函数
# qos_profile队列长度,一般写10
self.sub1 = self.create_subscription(Msgdemo1, "/Msgdemo1_topic", self.sub_callback1, 1)
print("1")
self.sub2 = self.create_subscription(Msgdemo2, "/Msgdemo2_topic", self.sub_callback2, 1)
def sub_callback1(self, msg):
# print(msg.data,flush=True)
self.get_logger().info(f"{msg.name},{msg.age}")
def sub_callback2(self, msg):
# print(msg.data,flush=True)
self.get_logger().info(f"{msg.hobby}")
def main():
rclpy.init()
sub_demo = Topic_Sub("topic_subscriber_node") # 创建对象并进行初始化
rclpy.spin(sub_demo)
sub_demo.destroy_node() # 销毁节点对象
rclpy.shutdown() # 关闭ROS2 Python接口
再继续编辑配置文件setup.py, 如下:

完毕后,编译功能包 colcon build
下来我们验证下是否设置成功,先运行含有发布话题的节点的可执行程序
python
ros2 run pythonpackagedemo1 topic_use_customer_interface_publisher_side
再运行含有订阅话题的节点的可执行程序
python
ros2 run pythonpackagedemo1 topic_use_customer_interface_subscriber_side
发布订阅效果如下:

我们再看看一些额外额信息,如节点、话题数据

命令发布话题数据
python
ros2 topic pub -r 2 /Msgdemo1_topic my_custom_interfaces/msg/Msgdemo1 "{name: 'cmd_pub', age: 20}"
命令行订阅话题数据
python
ros2 topic echo /Msgdemo1_topic
查看话题宽带和频率
python
ros2 topic hz /Msgdemo1_topic
ros2 topic bw /Msgdemo1_topic
命令执行效果如下:

2)测试服务接口
下面开始代码调用接口示例(即在一个可执行程序中的节点对象中,去使用服务的服务端接收满足上面数据规则的接口消息目标,然后执行一些操作)。这边博主继续复用之前的python包,新建一个service_use_cusomer_interface_server_side.py文件,在里面实现话题的发布,代码如下:
python
# 导入相关的库文件
import rclpy
from rclpy.node import Node
from my_custom_interfaces.srv import Srvdemo1
from my_custom_interfaces.srv import Srvdemo2
class Service_Server(Node):
def __init__(self, name):
super().__init__(name)
# 作用是:注册一个服务,监听客户端的请求,并调用回调函数处理请求。
self.srv1 = self.create_service(Srvdemo1, '/srv1', self.trans1)
self.srv2 = self.create_service(Srvdemo2, '/srv2', self.trans2)
def trans1(self, request, response):
response.success = True
response.message = f"I have connected the info: score->{request.score},weigts->{request.weights}"
print(response.message)
return response
def trans2(self, request, response):
response.message = f"I have got the info: address->{request.address}"
print(response.message)
return response
# ROS2规定服务回调函数必须有两个参数:
# request:客户端发来的请求数据(这里是两个整数a、b)
# response:服务端返回给客户端的响应数据(这里是求和结果sum)
# 函数最后必须return response。
# 话题 = 聊天、广播、持续发消息(单向)
# 服务 = 打电话、请求 - 响应、干完就挂(双向)
# 话题(Topic)= 大喇叭广播
# 老师拿着喇叭一直喊:"现在温度 25 度!现在速度 1m/s!"
# 学生们随便听,想听就听,不想听就不听
# 不用回复,老师只管喊
# 服务(Service)= 打电话
# 你打给朋友:"帮我算一下 3+5 等于几?"(请求)
# 朋友算完:"等于 8"(响应)
# 必须一问一答,打完电话就结束
# 特性 话题 (Topic) 服务 (Service)
# 通信模式 发布 → 订阅(单向) 请求 → 响应(双向)
# 是否需要回复 不需要 必须等待回复
# 使用场景 传感器数据、持续状态 执行任务、计算、开关
# 频率 高频(几十~几百 Hz) 低频(偶尔调用)
# 连接方式 多对多 一对一
# 阻塞 不阻塞 会阻塞(等结果)
# 只要是持续不断、实时传输的数据,都用话题:
# 摄像头图像
# 激光雷达数据
# 机器人位置、速度
# 温度、气压
# 遥控器指令
# 特点:只管发,不管对方收没收到。
# 只要是需要执行一个动作、需要结果、需要确认的,都用服务:
# 让机器人移动到某个点
# 计算两个数的和
# 打开 / 关闭传感器
# 保存一张图片
# 重置系统
# 查询机器人当前状态
# 特点:我叫你做,你必须做完告诉我结果。
# 最关键的区别:有没有响应
# 数据流用话题,任务调用用服务!
def main():
rclpy.init()
server_demo = Service_Server("service_server_node")
rclpy.spin(server_demo)
server_demo.destroy_node() # 销毁节点对象
rclpy.shutdown() # 关闭ROS2 Python接口
再创建一个名为service_use_customer_interface_client_side.py的文件,在里面实现话题的订阅
python
import rclpy
from rclpy.node import Node
from my_custom_interfaces.srv import Srvdemo1
from my_custom_interfaces.srv import Srvdemo2
class ServiceClient(Node):
def __init__(self, node_name):
super().__init__(node_name)
self.cli1 = self.create_client(Srvdemo1, "/srv1")
self.cli2 = self.create_client(Srvdemo2, "/srv2")
self.get_logger().info("客户端就绪,等待服务...")
# 异步发起srv1请求(不阻塞)
def send_req_srv1(self, score, weights):
if not self.cli1.wait_for_service(timeout_sec=2.0):
self.get_logger().error("/srv1 服务未上线")
return None
req = Srvdemo1.Request()
req.score = score
req.weights = weights
future = self.cli1.call_async(req)
return future
# 异步发起srv2请求(不阻塞)
def send_req_srv2(self, address):
if not self.cli2.wait_for_service(timeout_sec=2.0):
self.get_logger().error("/srv2 服务未上线")
return None
req = Srvdemo2.Request()
req.address = address
future = self.cli2.call_async(req)
return future
def main():
rclpy.init()
client = ServiceClient("service_client_node")
# 1. 一次性同时发起两个异步请求,不等待返回
fut1 = client.send_req_srv1(score=95, weights=88.0)
fut2 = client.send_req_srv2(address="Suzhou Wujiang")
future_list = []
if fut1 is not None:
future_list.append(fut1)
if fut2 is not None:
future_list.append(fut2)
# 2. 自旋等待所有请求全部完成
while future_list:
rclpy.spin_once(client)
for f in future_list[:]:
if f.done():
res = f.result()
if f == fut1:
client.get_logger().info(f"【srv1结果】success:{res.success}, msg:{res.message}")
elif f == fut2:
client.get_logger().info(f"【srv2结果】msg:{res.message}")
future_list.remove(f)
client.destroy_node()
rclpy.shutdown()
if __name__ == "__main__":
main()
再继续编辑配置文件setup.py, 如下:

完毕后,编译功能包
下来我们验证下是否设置成功,先运行含有服务端的节点的可执行程序
python
ros2 run pythonpackagedemo1 service_use_cusomer_interface_server_side
再运行含有客户端可执行程序
python
ros2 run pythonpackagedemo1 service_use_customer_interface_client_side
服务请求和响应执行效果如下:

我们再看看一些额外额信息,如节点、话题数据

命令行调用服务客户端(发起请求)
python
ros2 service call /srv1 my_custom_interfaces/srv/Srvdemo1 "{score: 90, weights: 25.4}"
执行效果如下:

3)测试动作接口
博主这边就不去测试了,因为之前博客中有完整创建动作通讯接口、使用测试的介绍。
二. 参数服务案例
类似编程中的全局变量,可以便于在多个程序中共享某些数据,参数是ROS机器人系统中的全局字典,可以运行多个节点中共享数据。参数的特性非常丰富,比如某一个节点共享了一个参数,其他节点都可以访问,如果某一个节点对参数进行了修改,其他节点也有办法立刻知道,从而获取最新的数值。
每个节点自带一组内置服务:get_parameters / set_parameters / list_parameters
- 存储:键值对(bool/int/double/string/ 数组)
- 生命周期:归属单个节点,节点销毁参数消失
- 定位:节点运行配置、开关、标定常量、静态设置、低速微调
用话题、服务、动作可以实现参数服务同样的功能,但为了修改参数,往往要自定义消息接口,要编译,要创建服务端,客户端或者发布者,订阅者,其实没有必要这么繁琐,使用参数服务可以简化操作。如下是它们的横向对比。
| 方式 | 通信模型 | 典型频率 | 适用场景 | 优缺点 |
|---|---|---|---|---|
| 参数服务 | 同步 RPC(内置现成服务) | 低频,Hz 级以内 | 配置、标定、启停开关、偶尔改配置 | 优点:零开发、命令行调试、yaml 批量配置; 缺点:慢、阻塞、不适合高频 |
| 话题 Topic | 异步发布订阅 | 任意高频(10~1000Hz) | 持续状态推送、实时指令、传感器数据、周期性更新变量 | 优点:实时、非阻塞、适合高频刷新; 缺点:要自定义消息、启停都要写收发代码,配置管理弱 |
| 自定义服务 Service | 同步一问一答 RPC | 中低频 | 需要 "下发 + 立即应答结果" 的单次请求 | 优点:可控应答、可靠性强;缺点:需要手动写服务端 + 客户端,繁琐,不适合单纯配置 |
| 动作 Action | 异步 + 反馈 + 取消 + 结果 | 长时任务 | 耗时任务:导航、机械臂运动、抓取、移动规划 | 优点:带进度、可取消; 缺点:太重,用来改参数纯属过度设计 |
接下来我们在上面话题示例,稍微改写下,用上参数服务。
新建topic_use_param_publisher_side.py文件如下:
python
import rclpy
from rclpy.node import Node
from my_custom_interfaces.msg import Msgdemo1
from my_custom_interfaces.msg import Msgdemo2
from rclpy.executors import MultiThreadedExecutor
class TopicTalker1(Node):
def __init__(self):
super().__init__("topic_publisher_node1")
self.pub = self.create_publisher(Msgdemo1, "/Msgdemo1_topic", 10)
self.timer1 = self.create_timer(1.0, self.pub_cb1)
#声明参数
self.declare_parameter('name', 'topic_publisher_node1_timer1')
self.declare_parameter('age', 5)
#修改自身参数
self.set_parameters([
rclpy.Parameter("name", "modifiedname"),
rclpy.Parameter("age", 100)
])
#参数修改回调(外部改参自动触发)
self.add_on_set_parameters_callback(self.param_cb)
def param_cb(self, params):
for p in params:
self.get_logger().info(f"更新参数 {p.name} = {p.value}")
return rcl_interfaces.msg.SetParametersResult(successful=True)
def pub_cb1(self):
name = self.get_parameter('name').get_parameter_value().string_value # 从ROS2系统中读取参数的值
age = self.get_parameter('age').value # 从ROS2系统中读取参数的值
msg = Msgdemo1()
msg.name = name
msg.age = age
self.pub.publish(msg)
self.get_logger().info("节点1_定时器1发布数据")
def main(args=None):
# 全局初始化一次ROS上下文
rclpy.init(args=args)
node1 = TopicTalker1()
rclpy.spin(node1)
node1.destroy_node()
if __name__ == "__main__":
main()
接下来配置setup.py,然后编译生成可执行程序,博主这边就不再赘述,上面都有。读者可自行尝试。至于在别的程序里如何访问这些参数,或者修改其值,这边也不再去逐一演示了。到此,自定义消息接口和参数服务地理解及使用方法,已经很清晰了,授人以鱼不如授人以渔。我们学习一样新东西,重点是大局把握住方法,理解它,搞清楚为什么要有它地出现。然后知道怎么去用即可,一些很西化的东西,实际用的时候再去测试或查找资料。
我们最后用命令行测试下上面的参数服务。
python
ros2 run pythonpackagedemo1 topic_use_param_publisher_side
执行完毕后使用如下命令语句去监视
python
ros2 topic echo /Msgdemo1_topic
可看到未修改参数前打印如下:

用如下命令语句去修改参数
python
ros2 param set topic_publisher_node1 name jack

可看到,有各种成熟的反馈机制,使用也越来越得心应手了。如下是一些常用命令,效果如下。


我们再继续实验把保存的参数服务参数从本地读回来(读回来之前,已经手动把参数文件修改了下)

此篇到这儿就结束啦,我们后面在继续深入了解!