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、编译工作空间

 colcon build 
 构建完成后,我们应该看到buildinstalllog目录:

image-20231023151712427

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>
 ros2命令中:
  • 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、创建节点流程

  1. 编程接口初始化

  2. 创建节点并初始化

  3. 实现节点功能

  4. 销毁节点并关闭接口 

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文件来编写节点:

image-20231023163116372

删除原本 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文件,加入如下入口点的配置:

image-20231023172134879

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”字符串的效果:

image-20231023163749024

 

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       # 错误信息
View Code

 

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()
View Code

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>
View Code

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()
modbus_rtu_server.py

 

 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()
modbus_rtu_client02.py

 

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 节点是否真正运行

bash
 
ros2 node list

如果返回空,说明节点未启动成功。

可能原因:

  • 环境变量未加载:开机时未正确 source ROS 2

  • 依赖未启动:FastDDS 发现服务未就绪

  • 网络未就绪:开机时网络接口未初始化完成

  • ROS2 未启动完全

 

(2) Ping检查  和 ros2 topic list  检测

 

(3). Create a startup script

bash
 
mkdir -p ~/.local/bin
nano ~/.local/bin/start_ros_camera.sh

Paste this (make sure paths are correct):

bash
 
#!/bin/bash

# ==============================================
# ROS 2 开机自启动脚本(含网络和FastDDS检查)
# 路径:/home/你的用户名/ros2_autostart.bash
# ==============================================

# --------------------------
# 配置区(根据实际情况修改)
# --------------------------
export ROS_DOMAIN_ID=6   #   
export ROS_LOCALHOST_ONLY=0 #  
TARGET_IP="192.168.2.1"  # 需要ping通的目标IP
MAX_RETRIES=10           # 最大重试次数
RETRY_DELAY=5            # 重试间隔(秒)
FASTREDS_DISCOVERY_TIMEOUT=30  # FastDDS发现等待超时(秒)

# --------------------------
# 函数:检查网络连通性
# --------------------------
check_network() {
    for ((i=1; i<=$MAX_RETRIES; i++)); do
        if ping -c 1 $TARGET_IP &> /dev/null; then
            echo "[OK] 网络已连通(成功ping通 $TARGET_IP)"
            return 0
        else
            echo "[...] 等待网络连接...(尝试 $i/$MAX_RETRIES)"
            sleep $RETRY_DELAY
        fi
    done
    echo "[ERROR] 网络未连通,无法启动ROS节点!"
    exit 1
}

# --------------------------
# 函数:检查FastDDS发现服务
# --------------------------
check_fastdds_discovery() {
    local start_time=$(date +%s)
    
    echo "[...] 检查FastDDS发现服务..."
    
    while true; do
        # 方法1:通过ros2 CLI检查
        if ros2 daemon status &> /dev/null; then
            echo "[OK] FastDDS发现服务已就绪"
            return 0
        fi
        
        # 方法2:通过端口检测(备用方案)
        if ss -ulnp | grep -q 7400; then
            echo "[OK] 检测到DDS发现端口活动"
            return 0
        fi
        
        # 超时判断
        local current_time=$(date +%s)
        if (( current_time - start_time > FASTREDS_DISCOVERY_TIMEOUT )); then
            echo "[ERROR] FastDDS发现服务启动超时!"
            exit 2
        fi
        
        echo "[...] 等待FastDDS发现服务...(已等待 $((current_time - start_time))秒)"
        sleep 2
    done
}

# --------------------------
# 主执行逻辑
# --------------------------
echo "======= ROS 2 节点自启动脚本 ======="
echo "启动时间: $(date)"

# 检查网络
check_network

# Initialize ROS daemon
ros2 daemon stop
ros2 daemon start
# Wait for daemon to be ready
sleep 5

# 加载ROS环境
source /opt/ros/rolling/setup.bash
source /home/ubuntu/part_image_ws/install/setup.bash
# 检查FastDDS发现服务
check_fastdds_discovery

# 启动ROS节点(示例)
echo "[...] 启动ROS 2节点..."
#ros2 run demo_nodes_cpp talker &
# ros2 run your_package your_node &  # 启动其他节点
#ros2 run usb_cam_viewer usb_cam_viewer --ros-args -p camera_id:=0 2>&1 | tee /tmp/ros_node.log
ros2 run usb_cam_viewer usb_cam_viewer --ros-args -p camera_id:=0 
echo "[OK] ROS 2节点已成功启动"
echo "当前运行的ROS节点:"
ros2 node list

 

Make it executable:

bash
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

  1. Open Settings (click the system menu in the top-right corner and select "Settings")

  2. Go to the Users section

  3. Click Unlock and enter your password when prompted

  4. Toggle the Automatic Login switch to ON for your user account

 

 

 

 

 

 

posted @ 2025-06-17 09:54  辛河  阅读(204)  评论(0)    收藏  举报