ROS2 编程-一般过程 -学习笔记 ubuntu24 + ros2 rolling+modbus-rtu通信
第0步、ROS2一键安装和VSCODE调试开发
感谢鱼香社区的工具,贡献特别大!
wget http://fishros.com/install -O fishros && . fishros
按照如下命令行方式打开VSCODE,方便调试
source install/setup.bash && code
第一步、ROS2工作空间
1、工作空间简介
在ROS机器人开发中,我们针对机器人某些功能进行开发时,各种编写的代码、参数、脚本等文件,需要放置在某一个文件夹里进行管理,这个文件夹在ROS系统中就叫做工作空间。所以工作空间是一个存放项目开发相关文件的文件夹,也是开发过程中存放所有资料的大本营。
2、创建工作空间
# 创建工作的空间
mkdir -p yahboomcar_ws/src
cd yahboomcar_ws
cd ~/yahboomcar_ws
3、编译工作空间
build、install和log目录:
ROS系统中一个典型的工作空间结构如上所示,这个yahboomcar_ws就是工作空间的根目录,里边会有四个子目录,或者叫做四个子空间。
-
src,代码空间,未来编写的代码、脚本,都需要人为的放置到这里;
-
build,编译空间,保存编译过程中产生的中间文件;
-
install,安装空间,放置编译得到的可执行文件和脚本;
-
log,日志空间,编译和运行过程中,保存各种警告、错误、信息等日志。
总体来讲,这四个空间的文件夹,我们绝大部分操作都是在src中进行的,编译成功后,就会执行install里边的结果,build和log两个文件夹用的很少。
这里也要强调一点,工作空间的名称我们可以自己定义,数量也并不是唯一的,比如:
工作空间1:ros2_ws_a, 用于A机器人的开发
工作空间1:ros2_ws_b, 用于B机器人的开发
工作空间1:ros2_ws_c, 用于C机器人的开发
以上情况是完全允许的,就像是我们在集成开发环境中创建了多个新工程一样,都是并列存在的关系。
4、设置环境变量
编译成功后,为了让系统能够找到我们的功能包和可执行文件,还需要设置环境变量:
# 仅在当前终端生效
source install/setup.bash
# 所有终端均生效
echo "source ~/yahboomcar_ros2_ws/yahboomcar_ws/install/setup.bash" >> ~/.bashrc
第二步、ROS2功能包
1、功能包简介
每个机器人可能有很多功能,比如移动控制、视觉感知、自主导航等,如果我们把这些功能的源码都放到一起当然也是可以的,但是当我们想把其中某些功能分享给别人时,就会发现代码都混合到了一起,很难拆分出来。
功能包就是这个原理,我们把不同功能的代码划分到不同的功能包中,尽量降低他们之间的耦合关系,当需要在ROS社区中分享给别人的时候,只需要说明这个功能包该如何使用,别人很快就可以用起来了。
所以功能包的机制,是提高ROS中软件复用率的重要方法之一。
2、创建功能包
如何在ROS2中创建一个功能包呢?我们可以使用这个指令:
ros2 pkg create <package_name> --build-type <build-type> --dependencies <dependencies> --node-name <node-name>
-
pkg:表示功能包相关的功能;
-
create:表示创建功能包;
-
package_name:新建功能包的名字;
-
build-type:表示新创建的功能包是C++还是Python的,如果使用C++或者C,那这里就跟ament_cmake,如果使用Python,就跟ament_python;
-
dependencies:表示功能包的依赖项,C++功能包需包含rclcpp,Python功能包需包含rclpy ,还有其它需要的依赖;
-
node-name:可执行程序的名称,会自动生成对应的源文件并生成配置文件;
比如在终端中分别创建C++和Python版本的功能包:
cd ~/yahboomcar_ws/src
# 创建C++功能包
ros2 pkg create pkg_helloworld_cpp --build-type ament_cmake --dependencies rclcpp --node-name helloworld
# 创建Python功能包
ros2 pkg create pkg_helloworld_py --build-type ament_python --dependencies rclpy --node-name helloworld
# 创建Python功能包 server intereface
ros2 pkg create pkg_interfaces --build-type ament_cmake --dependencies rosidl_default_generators sensor_msgs --license Apache-2.0
rm -rf build install log
colcon build --packages-select pkg_interfaces --allow-overriding pkg_interfaces
ros2 interface list | grep pkg_interfaces
ros2 interface show pkg_interfaces/srv/Add
3、编译功能包
在创建好的功能包中,我们可以继续完成代码的编写,之后需要编译和配置环境变量,才能正常运行:
cd ~/yahboomcar_ws
# 编译工作空间所有功能包
colcon build
# 编译指定功能包(一个或多个)
colcon build --packages-select 功能包列表
#配置环境变量
source install/setup.bash
4、带功能包的完整工作空间结构
ROS2工作空间的目录结构如下:
WorkSpace --- 自定义的工作空间。
|--- build:存储中间文件的目录,该目录下会为每一个功能包创建一个单独子目录。
|--- install:安装目录,该目录下会为每一个功能包创建一个单独子目录。
|--- log:日志目录,用于存储日志文件。
|--- src:用于存储功能包源码的目录。
|-- C++功能包
|-- package.xml:包信息,比如:包名、版本、作者、依赖项。
|-- CMakeLists.txt:配置编译规则,比如源文件、依赖项、目标文件。
|-- src:C++源文件目录。
|-- include:头文件目录。
|-- msg:消息接口文件目录。
|-- srv:服务接口文件目录。
|-- action:动作接口文件目录。
|-- Python功能包
|-- package.xml:包信息,比如:包名、版本、作者、依赖项。
|-- setup.py:与C++功能包的CMakeLists.txt类似。
|-- setup.cfg:功能包基本配置文件。
|-- resource:资源目录。
|-- test:存储测试相关文件。
|-- 功能包同名目录:Python源文件目录。
另外,无论是Python功能包还是C++功能包,都可以自定义一些配置文件相关的目录。
|-- C++或Python功能包
|-- launch:存储launch文件。
|-- rviz:存储rviz2配置相关文件。
|-- urdf:存储机器人建模文件。
|-- params:存储参数文件。
|-- world:存储仿真环境相关文件。
|-- map:存储导航所需地图文件。
|-- ......
上述这些目录也可以定义为其他名称,或者根据需要创建其他一些目录。
第三步、ROS2节点
1、节点简介
在通信时,不论采用何种方式,通信对象的构建都依赖于节点(Node),在ROS2中,一般情况下每个节点都对应某一单一的功能模块(例如:雷达驱动节点可能负责发布雷达消息,摄像头驱动节点可能负责发布图像消息)。一个完整的机器人系统可能由许多协同工作的节点组成,ROS2中的单个可执行文件(C++程序或Python程序)可以包含一个或多个节点。
2、创建节点流程
-
编程接口初始化
-
创建节点并初始化
-
实现节点功能
-
销毁节点并关闭接口
3、Hello World节点案例
这里以python功能包为例讲解
3.1、创建python功能包
cd ~/yahboomcar_ros2_ws/yahboomcar_ws/src
ros2 pkg create pkg_helloworld_py --build-type ament_python --dependencies rclpy --node-name helloworld
3.2、编写代码
执行上面命令后会创建pkg_helloworld_py,同时会创建helloworld.py文件来编写节点:

删除原本 helloworld.py 的代码,编写如下代码:
import rclpy # ROS2 Python接口库
from rclpy.node import Node # ROS2 节点类
import time
"""
创建一个HelloWorld节点, 初始化时输出“hello world”日志
"""
class HelloWorldNode(Node):
def __init__(self, name):
super().__init__(name) # ROS2节点父类初始化
while rclpy.ok(): # ROS2系统是否正常运行
self.get_logger().info("Hello World") # ROS2日志输出
time.sleep(0.5) # 休眠控制循环时间
def main(args=None): # ROS2节点主入口main函数
rclpy.init(args=args) # ROS2 Python接口初始化
node = HelloWorldNode("helloworld") # 创建ROS2节点对象并进行初始化
rclpy.spin(node) # 循环等待ROS2退出
node.destroy_node() # 销毁节点对象
rclpy.shutdown() # 关闭ROS2 Python接口
完成代码的编写后需要设置功能包的编译选项,让系统知道Python程序的入口,打开功能包的setup.py文件,加入如下入口点的配置:

3.3、编译功能包
cd ~/yahboomcar_ros2_ws/yahboomcar_ws
colcon build --packages-select pkg_helloworld_py
source install/setup.bash
3.4、运行节点
ros2 run pkg_helloworld_py helloworld
运行成功后,可以在终端中看到循环打印“Hello World”字符串的效果:

4、ROS2使用USB-CAM 摄像头
在虚拟机上安装ubuntu24 使用usb-cam 错误频繁,不用虚拟机就好了
usb-cam
ls /dev/video* # 查看摄像头设备节点(如 /dev/video0)
lsusb # 确认摄像头出现在USB设备列表中
4.1 GIT库源代码,然后编译成pkg使用
https://github.com/ros-drivers/usb_cam
解压到 xx_ws/src下,编译错误:
没有那个文件或目录
38 | #include "camera_info_manager/camera_info_manager.hpp"
解决:
# 缺少camera_info_manager/camera_info_manager
sudo apt install ros-rolling-camera-info-manager
#检查工作空间依赖正确
rosdep install -y --from-paths src --ignore-src --rosdistro rolling
# 编译
colcon build --symlink-install
4.2 安装命令
安装二进制码
$sudo apt install ros-rolling-usb-cam
运行节点 $ros2 run usb_cam usb_cam_node_exe
显示图像 $ros2 run rqt_image_view rqt_image_view
相机参数
$v4l2-ctl --list-formats-ext -d /dev/video0
运行节点,带参数
$ros2 run usb_cam usb_cam_node_exe \
--ros-args \ -p video_device:=/dev/video2 \ -p pixel_format:=mjpeg2rgb \ -p image_width:=1920 \ -p image_height:=1080
设备权限设置Issue: "Permission denied" for /dev/video0
查看当前usb接口类设备
$lsusb
临时方法:
$sudo chmod 666 /dev/video0
持续方法:
当前用户通过加入设备组方式,使用usb-cam
$sudo usermod -aG video $USER
查看当前用户是否在设备组
$groups $USER
deepseek 问题: $ros2 run usb_cam usb_cam_node_exe python编一个ROS2节点 订阅 /image_raw 显示图像
5、ROS2使用python3-pymodbus
# 查看是否有可用的官方包
sudo apt search python3-pymodbus
# 如果存在则安装
sudo apt install python3-pymodbus
# 查看版本
python3 -c "from pymodbus import __version__; print(__version__)"
# 查找USB转串口
lsusb
# 查找设备文件名(通过插拔操作识别,通常是 /dev/ttyUSB0)
ls /dev/tty*
# 识别用户组
ls -a -g /dev/ttyUSB*
# 当前用户加入用户组
sudo usermod -a -G dialout $USER
#查看当前用户是否在设备组
groups $USER
应用代码及测试报文
# pymodbus 3.6.9 from pymodbus.client import ModbusSerialClient as ModbusClient from pymodbus.transaction import ModbusRtuFramer import time def run_rs485_master(): # 创建Modbus RTU客户端 client = ModbusClient( method='rtu', port='/dev/ttyUSB0', baudrate=9600, timeout=1, # 增加超时时间 parity='N', stopbits=1, bytesize=8, retries=3, # 自动重试次数 strict=True ) # 连接设备 connection = client.connect() print(f"Connected: {connection}") try: while True: # 读取保持寄存器 (功能码03) result = client.read_holding_registers( address=0, count=10, slave=1 # 从站地址 ) if not result.isError(): print(f"保持寄存器值: {result.registers}") else: print(f"读取错误: {result}") # 写入单个寄存器 (功能码06) write_result = client.write_register( address=0, value=1234, slave=1 ) if not write_result.isError(): print("写入寄存器成功") time.sleep(1) except KeyboardInterrupt: print("用户中断") finally: client.close() if __name__ == "__main__": run_rs485_master()
测试报文:
主机(客户端):
01 03 00 00 00 0A C5 CD
从机(服务器):
01 03 14 00 01 00 02 00 03 00 04 00 05 00 06 00 07 00 08 00 09 00 0A 8F 16
主机(客户端):
01 06 00 00 04 D2 0B 57
从机(服务器):
01 06 00 00 04 D2 0B 57
示范使用 s/c 通信 parameter通信服务 在pkg_service 实现 多个client node 通过一个server node 访问一个modbus外设。
环境搭建:
使用两个USB转串口交叉互联,一个usb转串口接 ubuntu 内 ros2 rolling, 另一个usb转串口接 win内串口调试助手,具有按模板自动应答功能。
代码:
interfaces:
# ros2 pkg create pkg_interfaces --build-type ament_cmake --dependencies rosidl_default_generators sensor_msgs --license Apache-2.0 # 在ROS 2包的srv目录下创建 ModbusRTU.srv uint8 slave_id # 从站地址 uint16 address # 寄存器/线圈地址 uint16 count # 读取数量(用于功能码01-04) uint16 value # 写入值(用于功能码05,06) uint16[] registers # 写入的多个寄存器值(用于功能码16) uint8 function_code # Modbus功能码(1,2,3,4,5,6,16) --- uint16[] registers # 读取结果 bool success # 操作是否成功 string message # 错误信息
CMakeLists.txt
cmake_minimum_required(VERSION 3.8)
project(pkg_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)
find_package(rosidl_default_generators REQUIRED) # user add
# uncomment the following section in order to fill in
# further dependencies manually.
# find_package(<dependency> REQUIRED)
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()
rosidl_generate_interfaces(${PROJECT_NAME} #user add
"srv/ModbusRTU.srv"
)
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>pkg_interfaces</name> <version>0.0.0</version> <description>TODO: Package description</description> <maintainer email="ubuntu@todo.todo">ubuntu</maintainer> <license>Apache-2.0</license> <buildtool_depend>ament_cmake</buildtool_depend> <buildtool_depend>rosidl_default_generators</buildtool_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>
node:
1 # modbus_rtu_server.py 2 3 # 01 (0x01): 读线圈状态 4 # 02 (0x02): 读离散输入 5 # 03 (0x03): 读保持寄存器 6 # 04 (0x04): 读输入寄存器 7 8 # 05 (0x05): 写单个线圈 9 # 06 (0x06): 写单个寄存器 10 # 16 (0x10): 写多个寄存器 11 12 # 读操作(01-04): 使用address和count 13 # 写单个(05,06): 使用address和value 14 # 写多个(16): 使用address和registers数组 15 16 #!/usr/bin/env python3 17 import rclpy 18 from rclpy.node import Node 19 from pkg_interfaces.srv import ModbusRTU 20 from pymodbus.client import ModbusSerialClient as ModbusClient 21 from pymodbus.exceptions import ModbusException 22 from pymodbus.pdu import ExceptionResponse 23 import time 24 # 在ROS2中,MultiThreadedExecutor 和 SingleThreadedExecutor 25 # 是两种核心的任务调度器,用于管理节点回调函数的执行。 26 from rclpy.executors import MultiThreadedExecutor 27 28 class ndModbusRTUServer(Node): 29 def __init__(self): 30 super().__init__('modbus_rtu_server') 31 32 # 参数配置 33 self.declare_parameters( 34 namespace='', 35 parameters=[ 36 ('port', '/dev/ttyUSB0'), 37 ('method', 'rtu'), 38 ('baudrate', 9600), 39 ('timeout', 0.1), 40 ('stopbits', 1), 41 ('bytesize', 8), 42 ('parity', 'N') 43 ] 44 ) 45 46 # 初始化Modbus客户端 47 self.client = ModbusClient( 48 method=self.get_parameter('method').value, 49 port=self.get_parameter('port').value, 50 baudrate=self.get_parameter('baudrate').value, 51 timeout=self.get_parameter('timeout').value, 52 stopbits=self.get_parameter('stopbits').value, 53 bytesize=self.get_parameter('bytesize').value, 54 parity=self.get_parameter('parity').value 55 ) 56 57 if not self.client.connect(): 58 self.get_logger().error("Modbus连接失败!") 59 raise RuntimeError("Modbus连接失败") 60 61 # 创建服务 62 self.srv = self.create_service( 63 ModbusRTU, 64 '/modbus_rtu_service', 65 self.handle_request 66 ) 67 self.get_logger().info("Modbus RTU服务已启动...") 68 69 def handle_request(self, request, response): 70 result = None 71 try: 72 # 根据功能码处理不同请求 73 if request.function_code == 1: # 读线圈状态 74 result = self.client.read_coils( 75 address=request.address, 76 count=request.count, 77 slave=request.slave_id 78 ) 79 response.registers = [1 if bit else 0 for bit in result.bits] 80 81 elif request.function_code == 2: # 读离散输入 82 result = self.client.read_discrete_inputs( 83 address=request.address, 84 count=request.count, 85 slave=request.slave_id 86 ) 87 response.registers = [1 if bit else 0 for bit in result.bits] 88 89 elif request.function_code == 3: # 读保持寄存器 90 result = self.client.read_holding_registers( 91 address=request.address, 92 count=request.count, 93 slave=request.slave_id 94 ) 95 response.registers = result.registers 96 97 elif request.function_code == 4: # 读输入寄存器 98 result = self.client.read_input_registers( 99 address=request.address, 100 count=request.count, 101 slave=request.slave_id 102 ) 103 response.registers = result.registers 104 105 elif request.function_code == 5: # 写单个线圈 106 result = self.client.write_coil( 107 address=request.address, 108 value=request.value, 109 slave=request.slave_id 110 ) 111 response.registers = [] 112 113 elif request.function_code == 6: # 写单个寄存器 114 result = self.client.write_register( 115 address=request.address, 116 value=request.value, 117 slave=request.slave_id 118 ) 119 response.registers = [] 120 121 elif request.function_code == 16: # 写多个寄存器 122 result = self.client.write_registers( 123 address=request.address, 124 values=request.registers, 125 slave=request.slave_id 126 ) 127 response.registers = [] 128 129 else: 130 raise ValueError(f"不支持的功能码: {request.function_code}") 131 132 # 检查Modbus异常响应 133 if isinstance(result, ExceptionResponse): 134 raise ModbusException(f"从站返回异常: {result}") 135 136 if result.isError(): 137 response.success = False 138 response.message = "通信失败" 139 else: 140 response.success = True 141 response.message = "通信成功" 142 143 except ModbusException as e: 144 response.success = False 145 response.message = f"Modbus错误: {str(e)}" 146 except Exception as e: 147 response.success = False 148 response.message = f"系统错误: {str(e)}" 149 150 return response 151 152 def main(args=None): 153 # rclpy.init(args=args) 154 # nd_server = ndModbusRTUServer() 155 # try: 156 # rclpy.spin(nd_server) 157 # except KeyboardInterrupt: 158 # pass 159 # finally: 160 # nd_server.client.close() 161 # nd_server.destroy_node() 162 # rclpy.shutdown() 163 rclpy.init(args=args) 164 try: 165 server = ndModbusRTUServer() 166 executor = MultiThreadedExecutor(num_threads=4) 167 executor.add_node(server) 168 169 try: 170 executor.spin() 171 except KeyboardInterrupt: 172 server.get_logger().info("Shutting down...") 173 finally: 174 server.destroy_node() 175 finally: 176 rclpy.shutdown() 177 178 if __name__ == '__main__': 179 main()
1 # modbus_rtu_client02.py 2 #!/usr/bin/env python3 3 import rclpy 4 from rclpy.node import Node 5 6 from pkg_interfaces.srv import ModbusRTU # 替换为你的包名 7 8 class ndModbusRTUClient(Node): 9 def __init__(self): 10 super().__init__('modbus_rtu_client') 11 self.cli = self.create_client(ModbusRTU, '/modbus_rtu_service') 12 while not self.cli.wait_for_service(timeout_sec=1.0): 13 self.get_logger().info('等待服务上线...') 14 15 def send_request(self, slave_id, address, count=0, value=0, function_code=3,registers=[]): 16 request = ModbusRTU.Request() 17 request.slave_id = slave_id 18 request.address = address 19 request.count = count 20 request.value = value 21 request.function_code = function_code 22 request.registers = registers 23 24 future = self.cli.call_async(request) 25 rclpy.spin_until_future_complete(self, future) 26 return future.result() 27 28 def main(args=None): 29 rclpy.init(args=args) 30 ndclient = ndModbusRTUClient() 31 32 # 示例1:读取寄存器 01 03 10 00 01 00 02 00 03 00 04 00 05 00 06 00 07 00 08 72 98 33 34 response = None 35 response = ndclient.send_request( 36 slave_id=1, 37 address=0, 38 count=8, 39 function_code=3 40 ) 41 if response.success: 42 print(f"读取结果: {response.registers}") 43 else: 44 print(f"读取失败: {response.message}") 45 response = None 46 # 示例2:写入寄存器 发送和接受报文一样 47 response = ndclient.send_request( 48 slave_id=1, 49 address=5, 50 value=1234, 51 function_code=6 52 ) 53 print("写入成功: {response.message}" if response.success else f"写入失败: {response.message}") 54 response = None 55 # 示例3:写多个寄存器 56 response = ndclient.send_request( 57 slave_id=1, 58 address=0, 59 registers=[1234,2345,3456], 60 function_code=16 #(0x10): 写多个寄存器 61 ) 62 print(f"写入成功: {response.message}" if response.success else f"写入失败: {response.message}") 63 64 65 66 ndclient.destroy_node() 67 rclpy.shutdown() 68 69 if __name__ == '__main__': 70 main()
setup.py
'modbus_rtu_server = pkg_service.modbus_rtu_server:main', 'modbus_rtu_client02 = pkg_service.modbus_rtu_client02:main',
测试数据报文
#分布式通信
#在同一个局域网内,要求ROS_DOMAIN_ID 相等。
echo "export ROS_DOMAIN_ID=6" >> ~/.bashrc
source ~/.bashrc
echo $ROS_DOMAIN_ID
6、ROS2启动多个不同工作包pkg内节点node 举例
from launch import LaunchDescription from launch_ros.actions import Node """ 启动不同功能包内 不同的节点 # 在xx_ws/src内 """
def generate_launch_description(): usb_cam_node = Node( package='usb_cam', executable='usb_cam_node_exe', name='usb_cam', parameters=[ { 'video_device': '/dev/video0', # 摄像头设备路径 'image_width': 1920, # 图像宽度 'image_height': 1080, # 图像高度 'pixel_format': 'mjpeg2rgb', # 像素格式 # 'framerate': 30, # 帧率 # 'brightness': 50, # 亮度(0-100) # 'contrast': 50, # 对比度(0-100) # 'saturation': 50, # 饱和度(0-100) # 'sharpness': 50, # 锐度(0-100) # 'focus': -1, # 自动对焦(-1表示自动) # 'auto_exposure': True, # 自动曝光 # 'exposure': 100, # 曝光值 # 'white_balance': True, # 自动白平衡 # 'camera_name': 'default', # 相机名称 # 'camera_info_url': '', # 相机标定信息URL # 'io_method': 'mmap' # I/O方法(mmap/userptr) } ], output='screen' ) publisher_node = Node( package='pkg_topic', executable='publisher_demo', output='screen' ) subscriber_node = Node( package='pkg_topic', executable='subscriber_demo', output='screen' ) return LaunchDescription([ usb_cam_node, publisher_node, subscriber_node ])
7、Ubuntu24 开机自动运行 ROS2 rolling 命令举例
Option 1: Systemd Service (Starts at Boot, No Login Required)
向deepseek提问:在ubuntu24 中编写脚本 开机自动运行 如下ros2 rolling 的命令行:ubuntu@ubuntu-VMware-Virtual-Platform:~/yahboomcar_ws$ source install/setup.bash ubuntu@ubuntu-VMware-Virtual-Platform:~/yahboomcar_ws$ ros2 run pkg_helloworld_py helloworld
1. 创建启动脚本
ubuntu@ubuntu-VMware-Virtual-Platform:~/yahboomcar_ws$ tree -L 3
.
├── build
│ ├── COLCON_IGNORE
│ └── pkg_helloworld_py
│ ├── build
│ ├── colcon_build.rc
│ ├── colcon_command_prefix_setup_py.sh
│ ├── colcon_command_prefix_setup_py.sh.env
│ ├── install.log
│ ├── pkg_helloworld_py.egg-info
│ └── prefix_override
├── install
│ ├── COLCON_IGNORE
│ ├── local_setup.bash
│ ├── local_setup.ps1
│ ├── local_setup.sh
│ ├── _local_setup_util_ps1.py
│ ├── _local_setup_util_sh.py
│ ├── local_setup.zsh
│ ├── pkg_helloworld_py
│ │ ├── lib
│ │ └── share
│ ├── setup.bash
│ ├── setup.ps1
│ ├── setup.sh
│ └── setup.zsh
├── log
│ ├── build_2025-07-06_10-30-30
│ │ ├── events.log
│ │ └── logger_all.log
│ ├── build_2025-07-06_10-33-20
│ │ ├── events.log
│ │ ├── logger_all.log
│ │ └── pkg_helloworld_py
│ ├── build_2025-07-06_10-35-55
│ │ ├── events.log
│ │ ├── logger_all.log
│ │ └── pkg_helloworld_py
│ ├── COLCON_IGNORE
│ ├── latest -> latest_build
│ └── latest_build -> build_2025-07-06_10-35-55
├── src
│ └── pkg_helloworld_py
│ ├── package.xml
│ ├── pkg_helloworld_py
│ ├── resource
│ ├── setup.cfg
│ ├── setup.py
│ └── test
└── yahboomcar_startup.sh
代码:
#!/bin/bash # 等待ROS2环境初始化 sleep 5 source /opt/ros/rolling/setup.bash source /home/ubuntu/yahboomcar_ws/install/setup.bash ros2 run pkg_helloworld_py helloworld #sleep 1 #journalctl -u yahboomcar.service -f
修改 yahboomcar_startup.sh 权限
sudo chmod +x /usr/local/bin/yahboomcar_startup.sh
2. 创建systemd服务文件
sudo nano /etc/systemd/system/yahboomcar.service
GNU nano 7.2 /etc/systemd/system/yahboomcar.service [Unit] Description=Yahboomcar ROS2 Node After=network.target [Service] Type=exec # 改为oneshot表示单次任务 # Type=exec 明确告知systemd这是一个后台守护进程 User=ubuntu ExecStart=/home/ubuntu/yahboomcar_ws/yahboomcar_startup.sh Restart=on-failure #Restart=no # 完全禁用自动重启 RestartSec=5s
# 限制日志大小为 50MB,最多保留 5 个备份
StandardOutput=journal
StandardError=journal
LogRateLimitIntervalSec=30s
LogRateLimitBurst=1000 # 每条日志算一个"burst"
StandardOutput=inherit StandardError=inherit # 仅用于调试,正式部署时移除 [Install] WantedBy=multi-user.target
3. 启用并启动服务
sudo systemctl daemon-reload sudo systemctl enable yahboomcar.service sudo systemctl start yahboomcar.service
停止systemd 服务
sudo systemctl stop yahboomcar.service
修改 systemd 服务文件,重新加载并启动服务
sudo systemctl daemon-reload
sudo systemctl restart yahboomcar.service
4. 检查服务状态
sudo systemctl status yahboomcar.service
调试命令总结
| 目的 | 命令 |
|---|---|
| 实时查看日志 | journalctl -u yahboomcar.service -f |
| 查看完整日志 | journalctl -u yahboomcar.service --no-pager |
| 检查服务状态 | sudo systemctl status yahboomcar.service |
| 手动触发运行 | /home/ubuntu/yahboomcar_ws/yahboomcar_startup.sh |
查看文件是否存在并可执行
ls -l /home/ubuntu/yahboomcar_ws/yahboomcar_startup.sh
stat /opt/ros/rolling/setup.bash
确保:
-
文件存在
-
有可执行权限(
-rwxr-xr-x) -
属主是
ubuntu用户
设置脚本有可执行权限
chmod +x /home/ubuntu/yahboomcar_ws/yahboomcar_startup.sh
Option 2: Autostart at Login (Graphical Session)
This method runs the ROS2 node when the user logs in (recommended for desktop use).
在同一网段内,实现ROS2 node在两台PC机上topic通信。
两个节点具有相同的接口定义 msg,并且编译通过并source启动。
QoS类型等级相同。
(1) 检查 ROS 2 节点是否真正运行
ros2 node list
如果返回空,说明节点未启动成功。
可能原因:
-
环境变量未加载:开机时未正确 source ROS 2
-
依赖未启动:FastDDS 发现服务未就绪
-
网络未就绪:开机时网络接口未初始化完成
- ROS2 未启动完全
(2) Ping检查 和 ros2 topic list 检测
(3). Create a startup script
mkdir -p ~/.local/bin
nano ~/.local/bin/start_ros_camera.sh
Paste this (make sure paths are correct):
Make it executable:
chmod +x ~/.local/bin/start_ros_camera.sh
(4) Add it to Ubuntu's Startup Applications
-
Open Startup Applications (search in the app menu).
-
Click Add.
-
Enter:
-
Name:
ROS USB Camera -
Command:
/home/ubuntu/.local/bin/start_ros_camera.sh -
(Optional) Delay:
5(to ensure ROS is ready)
-
-
Click Save.
Now, when you log in, the camera viewer will launch automatically.
Setting Up Automatic Login Without Password on Ubuntu 24.04
To enable automatic login without requiring a password on Ubuntu 24.04, you can configure this through the GNOME Display Manager (GDM). Here's how to do it:
Method 1: Using GUI Settings
-
Open Settings (click the system menu in the top-right corner and select "Settings")
-
Go to the Users section
-
Click Unlock and enter your password when prompted
-
Toggle the Automatic Login switch to ON for your user account

浙公网安备 33010602011771号