鱼香ROS 3 лет назад
Родитель
Сommit
c06f05cb2e

+ 24 - 3
docs/_sidebar.md

@@ -1,3 +1,25 @@
+- [**ROS2代码模板**](codebook/README.md)
+  - rclcpp
+    -  [节点](codebook/rclcpp/nodes.md) 
+    -  [参数](codebook/rclcpp/parameters.md) 
+    -  [tf2](codebook/rclcpp/tf2.md) 
+    -  [时间](codebook/rclcpp/time.md) 
+    -  [点云PCL](codebook/rclcpp/pcl.md) 
+    -  [解决方案](codebook/rclcpp/workarounds.md) 
+
+  - rclpy
+    -  [节点](codebook/rclpy/nodes.md) 
+    -  [参数](codebook/rclpy/parameters.md) 
+    -  [tf2](codebook/rclpy/tf2.md) 
+    -  [时间](codebook/rclpy/time.md) 
+
+  - 其他相关
+    -  [CMake](codebook/pages/cmake.md) 
+    -  [Colcon](codebook/pages/colcon.md) 
+    -  [Launch](codebook/pages/launch.md) 
+    -  [网络通讯](codebook/pages/networking.md) 
+    -  [功能包](codebook/pages/packages.md) 
+
 - 第 1 章 ROS2介绍
   - [第1章 章节介绍](chapt1/章节介绍.md)
   - [1.1 ROS2的前世今生](chapt1/1.1ROS2的前世今生.md) 
@@ -34,10 +56,10 @@
     - [2.POP方法编写C++节点并测试](chapt3/3.6.2POP方法编写C++节点并测试.md) 
     - [3.OOP方式编写一个节点](chapt3/3.6.3OOP方式编写一个节点.md) 
   - 章节练习
-    -  [分别使用POP和OOP创建穷鬼张三](chapt3\分别使用POP和OOP创建穷鬼张三.md) 
+    -  [分别使用POP和OOP创建穷鬼张三](chapt3/分别使用POP和OOP创建穷鬼张三.md) 
   - 扩展阅读
     -  [扩展阅读1:本章CLI工具总结](chapt3/3.7扩展阅读.md)  
-    -  [扩展阅读2:ROS2客户端库RCL介绍](chapt3\扩展阅读2:ROS2客户端库RCL介绍.md) 
+    -  [扩展阅读2:ROS2客户端库RCL介绍](chapt3/扩展阅读2:ROS2客户端库RCL介绍.md) 
 - 第 4 章 通信机制上(话题与服务)
   - [章节介绍](chapt4/章节介绍.md) 
   - [4.1 ROS2话题介绍](chapt4/4.1ROS2话题介绍.md) 
@@ -89,7 +111,6 @@
   -  [7.3.1齐次坐标变换](chapt7/7.3.1齐次坐标变换.md)
   -   [7.3.2动手学坐标变换](chapt7/7.3.2动手学坐标变换.md) 
   -   [7.4机器人运动学介绍](chapt7/7.4机器人运动学介绍.md)
-
 - 第 8 章 动手学机器人建模
   - [章节介绍](chapt8/章节介绍.md) 
   - [8.1URDF统一机器人建模语言](chapt8/8.1URDF统一机器人建模语言.md)

+ 41 - 0
docs/codebook/README.md

@@ -0,0 +1,41 @@
+# ROS2 代码模板
+
+增加该部分内容主要是为了大家写代码的时候方便复制粘贴,本工程主要代码来自国外大佬开源仓库(mikeferguson/ros2_cookbook),小鱼根据日常使用需求和最新的ROS2特性进行了增改。
+
+该部分工程分为以下几个部分:
+
+ * 客户端库
+   * rclcpp [API](http://docs.ros2.org/latest/api/rclcpp/)
+     * [节点和组件](codebook/rclcpp/nodes.md)
+     * [参数](codebook/rclcpp/parameters.md)
+     * [点云处理](codebook/rclcpp/pcl.md)
+     * [时间](codebook/rclcpp/time.md)
+     * [TF2](codebook/rclcpp/tf2.md)
+     * [解决方案](codebook/rclcpp/workarounds.md)
+   * rclpy [API](http://docs.ros2.org/latest/api/rclpy/)
+     * [节点](codebook/rclpy/nodes.md)
+     * [参数](codebook/rclpy/parameters.md)
+     * [时间](codebook/rclpy/time.md)
+     * [TF2](codebook/rclpy/tf2.md)
+ * [Launch](codebook/pages/launch.md)
+ * [通信相关 (DDS & CycloneDDS)](codebook/pages/networking.md)
+ * 命令行工具
+   * ```ros2 run <pkg> <node>```
+   * ```ros2 node list```
+   * ```ros2 topic list```
+   * ```ros2 topic info <topic_name> --verbose``` Qos细节查看.
+   * ```ros2 param list```
+   * [colcon](codebook/pages/colcon.md) 构建工具.
+   * ```ros2 doctor --report``` gives tons of information.
+* [CMake](codebook/pages/cmake.md)
+* 功能包
+   * [设置 bloom/git 总是使用 ssh](https://answers.ros.org/question/234494/diagnosing-issues-with-bloom-github-two-factor-authentication/)
+   * `rosdep install --from-paths src --ignore-src --rosdistro=foxy -y`
+* [Package Documentation](codebook/pages/packages.md)
+* 状态页面
+  * [Foxy Debian 构建状态](http://repo.ros2.org/status_page/ros_foxy_default.html)
+  * [Rolling Debian 构建状态](http://repo.ros2.org/status_page/ros_rolling_default.html)
+  * [对比 Foxy/Rolling](http://repo.ros2.org/status_page/compare_foxy_rolling.html)
+  * [对比 Foxy/Humble](http://repo.ros2.org/status_page/compare_foxy_humble.html)
+
+

+ 169 - 0
docs/codebook/pages/cmake.md

@@ -0,0 +1,169 @@
+# CMake
+
+虽然你不需要完全了解 CMake 就能使用 ROS2,但稍微了解一点会很有帮助。你可能会对[CMake tutorial](https://cmake.org/cmake/help/latest/guide/tutorial/index.html)感兴趣,它解释了 CMake 的基础知识。
+
+## Ament
+
+Ament 是一组专门为 ROS2 设计的 CMake 模块,目的是使 CMake 更易于使用。另请参阅文档。
+
+Ament 包的基本结构:
+
+
+```cmake
+cmake_minimum_required(VERSION 3.5)
+project(my_package_name)
+
+# Default to C++14
+if(NOT CMAKE_CXX_STANDARD)
+  set(CMAKE_CXX_STANDARD 14)
+endif()
+
+if(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_CXX_COMPILER_ID MATCHES "Clang")
+  add_compile_options(-Wall -Wextra -Wpedantic)
+endif()
+
+# Find packages
+find_package(ament_cmake REQUIRED)
+find_package(rclcpp REQUIRED)
+
+# Include our own headers
+include_directories(include)
+
+# Create a node
+add_executable(my_node src/my_node.cpp)
+ament_target_dependencies(my_node
+  rclcpp
+  # Other ament dependencies
+  # This sets up include and linker paths
+)
+
+add_library(my_library src/my_library.cpp)
+ament_target_dependencies(my_library
+  rclcpp
+)
+
+# Install our headers
+install(
+  DIRECTORY include/
+  DESTINATION include
+)
+
+# Install our node and library
+install(TARGETS my_node my_library
+  ARCHIVE DESTINATION lib
+  LIBRARY DESTINATION lib
+  RUNTIME DESTINATION lib/${PACKAGE_NAME}
+)
+
+# Install some Python scripts
+install(
+  PROGRAMS
+    scripts/my_script.py
+  DESTINATION
+    lib/${PROJECT_NAME}
+)
+
+# Tell downstream packages where to find our headers
+ament_export_include_directories(include)
+# Tell downstream packages our libraries to link against
+ament_export_libraries(my_library)
+# Help downstream packages to find transitive dependencies
+ament_export_dependencies(
+  rclcpp
+)
+ament_package()
+```
+
+## Linting 配置
+
+我更喜欢更具 ROS1 风格的代码风格。要允许大括号位于各自的线路上,请执行以下操作:
+
+
+```cmake
+if(BUILD_TESTING)
+  find_package(ament_cmake_cpplint)
+  ament_cpplint(FILTERS "-whitespace/braces" "-whitespace/newline")
+endif()
+```
+
+## 安装 Python 脚本
+
+
+```cmake
+install(
+  PROGRAMS
+    scripts/script1.py
+    scripts/script2.py
+  DESTINATION lib/${PROJECT_NAME}
+)
+```
+
+## 取决于同一包中的消息
+
+通常最好的做法是将消息放在不同的包中,但有时,特别是对于驱动程序,你希望将消息放在同一包中。
+
+
+```cmake
+find_package(rosidl_default_generators REQUIRED)
+
+# Generate some messages
+rosidl_generate_interfaces(${PROJECT_NAME}
+  "msg/MyMessage.msg"
+)
+
+# Add a node which uses the messages
+add_executable(my_node my_node.cpp)
+rosidl_target_interfaces(my_node ${PROJECT_NAME} "rosidl_typesupport_cpp")
+```
+
+## 从普鲁金利布中去除助推器
+
+默认情况下,Pluginlib 同时支持 Boost::Shared_PTRS 和 STD::Shared_PTRS,如果你想避免在闪亮的新 ROS2 库中依赖 Boost,你需要明确告诉 pluginlib 不要包含 Boost 版本:
+
+
+```cmake
+target_compile_definitions(your_library PUBLIC "PLUGINLIB__DISABLE_BOOST_FUNCTIONS")
+```
+
+## 使用特征 3
+
+将<>r=“10”/>作为依赖项添加到你的 Package.xml 中,然后:
+
+
+```cmake
+find_package(Eigen3 REQUIRED)
+include_directories(${EIGEN3_INCLUDE_DIRS})
+```
+
+## 在 C++ 中构建 Python 扩展
+
+下面的示例基于包。
+
+
+```cmake
+find_package(PythonLibs REQUIRED)
+find_package(Boost REQUIRED python)
+find_package(ament_cmake_python REQUIRED)
+find_package(python_cmake_module REQUIRED)
+
+ament_python_install_package(${PROJECT_NAME})
+
+add_library(
+  my_python SHARED
+  ${SOURCE_FILES}
+)
+set_target_properties(
+  my_python PROPERTIES
+  LIBRARY_OUTPUT_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}
+  PREFIX ""
+)
+target_link_libraries(my_python
+  ${Boost_LIBRARIES}
+  ${PYTHON_LIBRARIES}
+)
+
+install(
+  TARGETS my_python
+  DESTINATION "${PYTHON_INSTALL_DIR}/${PROJECT_NAME}"
+)
+```

+ 64 - 0
docs/codebook/pages/colcon.md

@@ -0,0 +1,64 @@
+# Command Line: Colcon
+
+## Build
+
+用于在工作区构建 ROS2 包。
+
+生成所有包:
+
+
+```
+colcon build
+```
+
+要避免在调整 Python 脚本、配置文件和启动文件时重新构建:
+
+
+```
+colcon build --symlink-install
+```
+
+要修复大多数构建问题,特别是在向工作区添加或删除包或安装新的 RMW 实现的情况下,请清除 CMake 缓存。有关详细信息,请参阅<>r=“9”/>帖子。
+
+
+```
+colcon build --cmake-clean-cache
+```
+
+## CMake 参数
+
+
+```
+colcon build --cmake-args -DCMAKE_BUILD_TYPE=RelWithDebInfo
+```
+
+## 测试
+
+要测试并将结果显示在屏幕上,请执行以下操作:
+
+
+```
+colcon test
+colcon test-result --verbose
+```
+
+构建/测试单个包:
+
+
+```
+colcon <verb> --packages-select <package-name>
+```
+
+## 格式化
+
+将输出显示到屏幕上:
+
+
+```
+colcon <verb> --event-handlers console_direct+
+```
+
+## 动作文档
+
+ * [build](https://colcon.readthedocs.io/en/released/reference/verb/build.html)
+ * [test](https://colcon.readthedocs.io/en/released/reference/verb/test.html)

+ 173 - 0
docs/codebook/pages/english/cmake.md

@@ -0,0 +1,173 @@
+# CMake
+
+While you don't need to know everything about CMake to use ROS2, knowing a bit
+will really be helpful. You might be interested in the
+[CMake tutorial](https://cmake.org/cmake/help/latest/guide/tutorial/index.html)
+which explains the basics of CMake.
+
+## Ament
+
+Ament is a set of CMake modules specifically designed for ROS2 with the intent
+of making CMake easier to use. See also the
+[Ament CMake](https://index.ros.org/doc/ros2/Tutorials/Ament-CMake-Documentation/)
+documentation.
+
+The basic structure of an ament package:
+
+```cmake
+cmake_minimum_required(VERSION 3.5)
+project(my_package_name)
+
+# Default to C++14
+if(NOT CMAKE_CXX_STANDARD)
+  set(CMAKE_CXX_STANDARD 14)
+endif()
+
+if(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_CXX_COMPILER_ID MATCHES "Clang")
+  add_compile_options(-Wall -Wextra -Wpedantic)
+endif()
+
+# Find packages
+find_package(ament_cmake REQUIRED)
+find_package(rclcpp REQUIRED)
+
+# Include our own headers
+include_directories(include)
+
+# Create a node
+add_executable(my_node src/my_node.cpp)
+ament_target_dependencies(my_node
+  rclcpp
+  # Other ament dependencies
+  # This sets up include and linker paths
+)
+
+add_library(my_library src/my_library.cpp)
+ament_target_dependencies(my_library
+  rclcpp
+)
+
+# Install our headers
+install(
+  DIRECTORY include/
+  DESTINATION include
+)
+
+# Install our node and library
+install(TARGETS my_node my_library
+  ARCHIVE DESTINATION lib
+  LIBRARY DESTINATION lib
+  RUNTIME DESTINATION lib/${PACKAGE_NAME}
+)
+
+# Install some Python scripts
+install(
+  PROGRAMS
+    scripts/my_script.py
+  DESTINATION
+    lib/${PROJECT_NAME}
+)
+
+# Tell downstream packages where to find our headers
+ament_export_include_directories(include)
+# Tell downstream packages our libraries to link against
+ament_export_libraries(my_library)
+# Help downstream packages to find transitive dependencies
+ament_export_dependencies(
+  rclcpp
+)
+ament_package()
+```
+
+## Linting Configuration
+
+I prefer a more ROS1-style code style. To allow braces to be on their
+own lines:
+
+```cmake
+if(BUILD_TESTING)
+  find_package(ament_cmake_cpplint)
+  ament_cpplint(FILTERS "-whitespace/braces" "-whitespace/newline")
+endif()
+```
+
+## Installing Python Scripts
+
+```cmake
+install(
+  PROGRAMS
+    scripts/script1.py
+    scripts/script2.py
+  DESTINATION lib/${PROJECT_NAME}
+)
+```
+
+## Depending on Messages in Same Package
+
+It is generally best practice to put messages in separate packages, but sometimes,
+especially for drivers, you want the messages in the same package.
+
+```cmake
+find_package(rosidl_default_generators REQUIRED)
+
+# Generate some messages
+rosidl_generate_interfaces(${PROJECT_NAME}
+  "msg/MyMessage.msg"
+)
+
+# Add a node which uses the messages
+add_executable(my_node my_node.cpp)
+rosidl_target_interfaces(my_node ${PROJECT_NAME} "rosidl_typesupport_cpp")
+```
+
+## Removing Boost from Pluginlib
+
+Pluginlib supports both boost::shared_ptrs and std::shared_ptrs by default,
+if you want to avoid depending on Boost in your shiny new ROS2 library, you
+need to specifically tell pluginlib not to include the Boost versions:
+
+```cmake
+target_compile_definitions(your_library PUBLIC "PLUGINLIB__DISABLE_BOOST_FUNCTIONS")
+```
+
+## Using Eigen3
+
+Add _eigen_ to your package.xml as a dependency, and then:
+
+```cmake
+find_package(Eigen3 REQUIRED)
+include_directories(${EIGEN3_INCLUDE_DIRS})
+```
+
+## Building Python Extensions in C++
+
+The example below is based on the
+[etherbotix](https://github.com/mikeferguson/etherbotix) package.
+
+```cmake
+find_package(PythonLibs REQUIRED)
+find_package(Boost REQUIRED python)
+find_package(ament_cmake_python REQUIRED)
+find_package(python_cmake_module REQUIRED)
+
+ament_python_install_package(${PROJECT_NAME})
+
+add_library(
+  my_python SHARED
+  ${SOURCE_FILES}
+)
+set_target_properties(
+  my_python PROPERTIES
+  LIBRARY_OUTPUT_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}
+  PREFIX ""
+)
+target_link_libraries(my_python
+  ${Boost_LIBRARIES}
+  ${PYTHON_LIBRARIES}
+)
+
+install(
+  TARGETS my_python
+  DESTINATION "${PYTHON_INSTALL_DIR}/${PROJECT_NAME}"
+)
+```

+ 62 - 0
docs/codebook/pages/english/colcon.md

@@ -0,0 +1,62 @@
+# Command Line: Colcon
+
+## Build
+
+_colcon_ is used to build ROS2 packages in a workspace.
+
+Build all packages:
+
+```
+colcon build
+```
+
+To avoid having to rebuild when tweaking Python scripts,
+config files, and launch files:
+
+```
+colcon build --symlink-install
+```
+
+To fix most build issues, especially if you have added or removed packages
+to your workspace or installed new RMW implementations, clean the CMake
+cache. See this
+[ROS Answers](https://answers.ros.org/question/333534/when-to-use-cmake-cleanconfigure/)
+post for more details.
+
+```
+colcon build --cmake-clean-cache
+```
+
+## CMake Arguments
+
+```
+colcon build --cmake-args -DCMAKE_BUILD_TYPE=RelWithDebInfo
+```
+
+## Test
+
+To test and get results to screen:
+
+```
+colcon test
+colcon test-result --verbose
+```
+
+Build/test a single package:
+
+```
+colcon <verb> --packages-select <package-name>
+```
+
+## Formatting
+
+Get the output to the screen:
+
+```
+colcon <verb> --event-handlers console_direct+
+```
+
+## Verb Documentation
+
+ * [build](https://colcon.readthedocs.io/en/released/reference/verb/build.html)
+ * [test](https://colcon.readthedocs.io/en/released/reference/verb/test.html)

+ 137 - 0
docs/codebook/pages/english/launch.md

@@ -0,0 +1,137 @@
+# roslaunch2
+
+## Python-Based Launch Files
+
+Python-based launch files all pretty much follow the same structure.
+Note that prior to _Foxy_, the parameters _name_, _namespace_, and _executable_ were
+[prefixed with node\_](https://index.ros.org/doc/ros2/Releases/Release-Foxy-Fitzroy/#launch-ros):
+
+```python
+from launch import LaunchDescription
+from launch_ros.actions import Node
+
+def generate_launch_description():
+    return LaunchDescription([
+        Node(
+            name='node_runtime_name',
+            package='ros2_package_name',
+            executable='name_of_executable',
+            parameters=[{'name_of_int_param': 1,
+                         'name_of_str_param': 'value'}],
+            remappings=[('from', 'to')],
+            output='screen',
+        ),
+        # More Nodes!
+    ])
+```
+
+## Making a Launch File Executable
+
+Normally, launch files are run with:
+
+```
+ros2 launch pkg launch.py
+```
+
+But, sometimes you want an executable launch file (for instance to
+put in a systemd job). Assuming you follow the default pattern shown
+above, all you need to add:
+
+```python
+#!/usr/bin/env python3
+
+import sys
+from launch import LaunchService
+
+# define generate_launch_description() as above
+
+if __name__ == '__main__':
+    desc = generate_launch_description()
+    service = LaunchService(argv=sys.argv[1:])
+    service.include_launch_description(desc)
+    return service.run()
+```
+
+## Loading Parameters From a File
+
+Some nodes have many parameters, it's easier to put them in a YAML file:
+
+```yaml
+node_name:
+  ros__parameters:
+      some_int_param: 1
+      some_str_param: "the_value"
+```
+
+To load this:
+
+```python
+from ament_index_python.packages import get_package_share_directory
+
+# Assuming you have a file called package_name/config/params.yaml
+node_params = os.path.join(
+    get_package_share_directory('package_name'),
+    'config',
+    'params.yaml'
+)
+
+# Add this to your LaunchDescription
+Node(
+    name='node_runtime_name',
+    package='ros2_package_name',
+    executable='name_of_executable',
+    parameters=[{'another_param': 42.0},
+                 node_params]
+)
+```
+
+
+## Including Python Launch Files
+
+```python
+import os
+from ament_index_python.packages import get_package_share_directory
+from launch.actions import IncludeLaunchDescription
+from launch.launch_description_sources import PythonLaunchDescriptionSource
+
+# Assuming you have a file called package_name/launch/my_launch.launch.py
+my_launch_file = os.path.join(
+    get_package_share_directory('package_name'),
+    'launch',
+    'my_launch.launch.py'
+)
+
+# Add this to your LaunchDescription
+IncludeLaunchDescription(
+    PythonLaunchDescriptionSource([my_launch_file])
+),
+```
+
+## Loading a URDF
+
+Most robots need to load their URDF into the robot_state_publisher,
+and maybe other nodes as well:
+
+```python
+import os
+from ament_index_python.packages import get_package_share_directory
+from launch_ros.actions import Node
+
+# Load the URDF into a parameter
+desc_dir = get_package_share_directory('robot_description_pkg')
+urdf_path = os.path.join(desc_dir, 'robots', 'my_urdf.urdf')
+urdf = open(urdf_path).read()
+
+# Add this to your LaunchDescription
+Node(
+    name='robot_state_publisher',
+    package='robot_state_publisher',
+    executable='robot_state_publisher',
+    parameters=[{'robot_description': urdf}],
+)
+```
+
+## Installing Launch Files
+
+ * [Python](https://index.ros.org/doc/ros2/Tutorials/Launch-system/#python-packages)
+ * [C++/CMake](https://index.ros.org/doc/ros2/Tutorials/Launch-system/#c-packages)

+ 107 - 0
docs/codebook/pages/english/networking.md

@@ -0,0 +1,107 @@
+# Networking
+
+ROS2 uses DDS for message transport.
+
+Set the environment variable RMW_IMPLEMENTATION to select a DDS implementation
+(RMW = robotic middleware). For instance:
+
+```
+export RMW_IMPLEMENTATION=rmw_cyclonedds_cpp
+```
+
+To check which RMW implementation is being used:
+
+```
+ros2 doctor --report | grep middleware
+```
+
+## DDS Discovery
+
+There is no _rosmaster_ like in ROS1. Node discovery is peer-to-peer with nodes
+announcing their topics on startup and periodically after that. By default, any
+machines on the same network will see each other if they have the same
+ROS_DOMAIN_ID.
+
+ROS_DOMAIN_ID can be any number between 0 and 253, although it is recommended
+to use numbers less than 128.
+
+In addition to the ROS_DOMAIN_ID, CycloneDDS supports a domain tag, which allows
+nearly infinite partitioning of the network (see below).
+
+If you want to limit communication to the localhost set ROS_LOCALHOST_ONLY,
+which is [available since Eloquent](https://index.ros.org/doc/ros2/Releases/Release-Eloquent-Elusor/#new-features-in-this-ros-2-release).
+
+## CycloneDDS
+
+Cyclone can be configured with XML. This can be stored in a file or passed
+directly in the environment variable CYCLONEDDS_URI. A full list of
+supported options can be found in the
+[eclipse-cyclonedds repo](https://github.com/eclipse-cyclonedds/cyclonedds/blob/master/docs/manual/options.md).
+See also the
+[Guide to Configuration](https://github.com/eclipse-cyclonedds/cyclonedds/blob/master/docs/manual/config.rst).
+
+### CycloneDDS: Multiple Interfaces
+
+Cyclone currently only works with a single network interface. If you have multiple
+interfaces, specify which one to use in the NetworkInterfaceAddress:
+
+```xml
+<CycloneDDS>
+  <Domain>
+    <General>
+      <NetworkInterfaceAddress>wlp2s0</NetworkInterfaceAddress>
+    </General>
+  </Domain>
+</CycloneDDS>
+```
+
+### CycloneDDS: Disabling Multicast (Except Discovery)
+
+Some network hardware can perform poorly with multicast (especially with
+WIFI). You can limit multicast to just discovery:
+
+```xml
+<CycloneDDS>
+  <Domain>
+    <General>
+      <AllowMulticast>spdp</AllowMulticast>
+    </General>
+  </Domain>
+</CycloneDDS>
+```
+
+### CycloneDDS: Domain Tag
+
+CycloneDDS also defines a "Domain Tag" which allows to drastically partition
+the network with a custom string:
+
+```xml
+<CycloneDDS>
+  <Domain>
+    <Discovery>
+      <Tag>my_robot_name</Tag>
+    </Discovery>
+  </Domain>
+</CycloneDDS>
+```
+
+### Example
+
+The above tags can all be combined:
+
+```xml
+<CycloneDDS>
+  <Domain>
+    <General>
+      <!-- Explicitly set network interface -->
+      <NetworkInterfaceAddress>wlp2s0</NetworkInterfaceAddress>
+      <!-- Use multicast for discovery only -->
+      <AllowMulticast>spdp</AllowMulticast>
+    </General>
+    <Discovery>
+      <!-- This tag has to be the same on each machine -->
+      <Tag>my_robot_name</Tag>
+    </Discovery>
+  </Domain>
+</CycloneDDS>
+```

+ 15 - 0
docs/codebook/pages/english/packages.md

@@ -0,0 +1,15 @@
+# Package Documentation
+
+Currently, documentation is a bit all over the place. This list is certainly
+not extensive, but I find it helpful to have quick links to the frequently
+used packages.
+
+## Core Documentation
+
+ * [ROS 2 Overview](https://index.ros.org/doc/ros2/)
+ * [Ament CMake](https://index.ros.org/doc/ros2/Tutorials/Ament-CMake-Documentation/)
+ * [ROS2 Launch](https://github.com/ros2/launch/blob/master/launch/doc/source/architecture.rst)
+
+## Higher Level Packages
+
+ * [Navigation2](https://navigation.ros.org/)

+ 139 - 0
docs/codebook/pages/launch.md

@@ -0,0 +1,139 @@
+# roslaunch2
+
+## 基于 Python 的启动文件
+
+基于 python 的启动文件几乎都遵循相同的结构。注意,在 Foxy 之前,参数名称、名称空间和可执行文件的[前缀](https://index.ros.org/doc/ros2/Releases/Release-Foxy-Fitzroy/#launch-ros):
+
+
+```python
+from launch import LaunchDescription
+from launch_ros.actions import Node
+
+def generate_launch_description():
+    return LaunchDescription([
+        Node(
+            name='node_runtime_name',
+            package='ros2_package_name',
+            executable='name_of_executable',
+            parameters=[{'name_of_int_param': 1,
+                         'name_of_str_param': 'value'}],
+            remappings=[('from', 'to')],
+            output='screen',
+        ),
+        # More Nodes!
+    ])
+```
+
+## 使启动文件成为可执行文件
+
+正常情况下,启动文件的运行方式为:
+
+
+```
+ros2 launch pkg launch.py
+```
+
+但是,有时你需要一个可执行的启动文件(例如放入一个系统作业)。假设你遵循上面所示的默认模式,则只需添加以下内容:
+
+
+```python
+#!/usr/bin/env python3
+
+import sys
+from launch import LaunchService
+
+# define generate_launch_description() as above
+
+if __name__ == '__main__':
+    desc = generate_launch_description()
+    service = LaunchService(argv=sys.argv[1:])
+    service.include_launch_description(desc)
+    return service.run()
+```
+
+## 从文件加载参数
+
+有些节点有很多参数,将它们放在 YAML 文件中会更容易:
+
+
+```yaml
+node_name:
+  ros__parameters:
+      some_int_param: 1
+      some_str_param: "the_value"
+```
+
+要加载此文件,请执行以下操作:
+
+
+```python
+from ament_index_python.packages import get_package_share_directory
+
+# Assuming you have a file called package_name/config/params.yaml
+node_params = os.path.join(
+    get_package_share_directory('package_name'),
+    'config',
+    'params.yaml'
+)
+
+# Add this to your LaunchDescription
+Node(
+    name='node_runtime_name',
+    package='ros2_package_name',
+    executable='name_of_executable',
+    parameters=[{'another_param': 42.0},
+                 node_params]
+)
+```
+
+
+## 包括 Python 启动文件
+
+
+```python
+import os
+from ament_index_python.packages import get_package_share_directory
+from launch.actions import IncludeLaunchDescription
+from launch.launch_description_sources import PythonLaunchDescriptionSource
+
+# Assuming you have a file called package_name/launch/my_launch.launch.py
+my_launch_file = os.path.join(
+    get_package_share_directory('package_name'),
+    'launch',
+    'my_launch.launch.py'
+)
+
+# Add this to your LaunchDescription
+IncludeLaunchDescription(
+    PythonLaunchDescriptionSource([my_launch_file])
+),
+```
+
+## 加载 URDF
+
+大多数机器人需要将其 URDF 加载到 ROBTO_STATE_PUBLISHER 中,也可能需要加载到其他节点中:
+
+
+```python
+import os
+from ament_index_python.packages import get_package_share_directory
+from launch_ros.actions import Node
+
+# Load the URDF into a parameter
+desc_dir = get_package_share_directory('robot_description_pkg')
+urdf_path = os.path.join(desc_dir, 'robots', 'my_urdf.urdf')
+urdf = open(urdf_path).read()
+
+# Add this to your LaunchDescription
+Node(
+    name='robot_state_publisher',
+    package='robot_state_publisher',
+    executable='robot_state_publisher',
+    parameters=[{'robot_description': urdf}],
+)
+```
+
+## 安装启动文件
+
+ * [Python](https://index.ros.org/doc/ros2/Tutorials/Launch-system/#python-packages)
+ * [C++/CMake](https://index.ros.org/doc/ros2/Tutorials/Launch-system/#c-packages)

+ 98 - 0
docs/codebook/pages/networking.md

@@ -0,0 +1,98 @@
+# 联网
+
+ROS2 使用 DDS 进行消息传输。
+
+设置环境变量 RMW_IMPLICATION 以选择 DDS 实现(RMW= 机器人中间件)。例如:
+
+
+```
+export RMW_IMPLEMENTATION=rmw_cyclonedds_cpp
+```
+
+要检查正在使用的 RMW 实施,请执行以下操作:
+
+
+```
+ros2 doctor --report | grep middleware
+```
+
+## DDS 发现
+
+在 ROS1 中没有类似的。节点发现是对等的,节点在启动时宣布它们的主题,并在启动后定期发布主题。默认情况下,如果同一网络上的任何计算机具有相同的 ROS_DOMAIN_ID,则它们将相互查看。
+
+ROS_DOMAIN_ID 可以是 0 到 253 之间的任何数字,但建议使用小于 128 的数字。
+
+除了 ROS_DOMAIN_ID 之外,CycloneDDS 还支持域标签,允许对网络进行几乎无限的分区(见下文)。
+
+如果希望将通信限制在本地主机上,请设置 ROS_LOCALHOST_ONLY,它是。
+
+## CycloneDDS
+
+Cyclone 可以使用 XML 进行配置。它可以存储在文件中,也可以直接传递到环境变量 CYCLONEDDS_URI 中。可以在<>r=“9”/>中找到支持选项的完整列表。另请参阅<>r=“10”/>。
+
+### CycloneDDS:多个接口
+
+气旋目前只能与单一网络接口配合使用。如果你有多个接口,请指定要在 NetworkInterfaceAddress 中使用的接口:
+
+
+```xml
+<CycloneDDS>
+  <Domain>
+    <General>
+      <NetworkInterfaceAddress>wlp2s0</NetworkInterfaceAddress>
+    </General>
+  </Domain>
+</CycloneDDS>
+```
+
+### CycloneDDS:禁用组播(发现除外)
+
+某些网络硬件在使用多播时性能不佳(尤其是在使用 WiFi 时)。你可以将多播限制为仅发现:
+
+
+```xml
+<CycloneDDS>
+  <Domain>
+    <General>
+      <AllowMulticast>spdp</AllowMulticast>
+    </General>
+  </Domain>
+</CycloneDDS>
+```
+
+### CycloneDDS:域标签
+
+CycloneDDS 还定义了“域标签”,允许使用自定义字符串大幅划分网络:
+
+
+```xml
+<CycloneDDS>
+  <Domain>
+    <Discovery>
+      <Tag>my_robot_name</Tag>
+    </Discovery>
+  </Domain>
+</CycloneDDS>
+```
+
+### 示例
+
+以上标签都可以组合在一起:
+
+
+```xml
+<CycloneDDS>
+  <Domain>
+    <General>
+      <!-- Explicitly set network interface -->
+      <NetworkInterfaceAddress>wlp2s0</NetworkInterfaceAddress>
+      <!-- Use multicast for discovery only -->
+      <AllowMulticast>spdp</AllowMulticast>
+    </General>
+    <Discovery>
+      <!-- This tag has to be the same on each machine -->
+      <Tag>my_robot_name</Tag>
+    </Discovery>
+  </Domain>
+</CycloneDDS>
+```

+ 13 - 0
docs/codebook/pages/packages.md

@@ -0,0 +1,13 @@
+# 程序包文档
+
+目前,文档有点无处不在。这个列表当然不是很广泛,但我发现快速链接到常用的包是很有帮助的。
+
+## 核心文档
+ * [ROS2 中文网](http://ros2.fishros.com/)
+ * [ROS 2 概述](https://index.ros.org/doc/ros2/)
+ * [Ament CMake](https://index.ros.org/doc/ros2/Tutorials/Ament-CMake-Documentation/)
+ * [ROS2 Launch](https://github.com/ros2/launch/blob/master/launch/doc/source/architecture.rst)
+
+## 其他功能包
+
+ * [Nav2](http://nav2.fishros.com/)

+ 63 - 0
docs/codebook/rclcpp/english/nodes.md

@@ -0,0 +1,63 @@
+# rclcpp: Nodes and Components
+
+## Creating a Component
+
+```cpp
+#include <rclcpp/rclcpp.hpp>
+
+namespace my_pkg
+{
+
+class MyComponent : public rclcpp::Node
+{
+public:
+  MyComponent(const rclcpp::NodeOptions& options)
+  : rclcpp::Node("node_name", options)
+  {
+    // Note: you cannot use shared_from_this()
+    //       here because the node is not fully
+    //       initialized.
+  }
+};
+
+}  // namespace my_pkg
+
+#include "rclcpp_components/register_node_macro.hpp"
+RCLCPP_COMPONENTS_REGISTER_NODE(my_pkg::MyComponent)
+```
+
+The in the CMakeLists.txt:
+
+```cmake
+add_library(my_component SHARED
+  src/my_component.cpp
+)
+ament_target_dependencies(my_component
+  rclcpp
+  rclcpp_components
+)
+
+# Also add a node executable which simply loads the component
+rclcpp_components_register_node(my_component
+  PLUGIN "my_pkg::MyComponent"
+  EXECUTABLE my_node
+)
+```
+
+## Executors
+
+To run an executor in a thread:
+
+```cpp
+#include <thread>
+#include <rclcpp/rclcpp.hpp>
+#include <rclcpp/executors/single_threaded_executor.hpp>
+
+rclcpp::executors::SingleThreadedExecutor executor;
+
+// Node is rclcpp::Node::SharedPtr instance
+executor.add_node(node);
+std::thread executor_thread(
+  std::bind(&rclcpp::executors::SingleThreadedExecutor::spin,
+            &executor));
+```

+ 76 - 0
docs/codebook/rclcpp/english/parameters.md

@@ -0,0 +1,76 @@
+# rclcpp: Parameters
+
+Parameters need to be declared. At the same time, you can get the value if you
+are not planning to update the value again later:
+
+```cpp
+// node is an instance of rclcpp::Node
+// 42 is a great default for a parameter
+int param = node.declare_parameter<int>("my_param_name", 42);
+```
+
+To get the value:
+
+```cpp
+int param;
+node.get_parameter("my_param_name", param);
+```
+
+## Dynamic Parameters
+
+In ROS2, all parameters can be dynamically updated through a ROS2 service
+(there is no need to define duplicate stuff as with dynamic reconfigure).
+
+The example below works in Eloquent or later (earlier ROS2 releases supported
+only a single callback and had a slightly different API).
+See the documentation for
+[rclcpp::ParameterType](http://docs.ros2.org/latest/api/rclcpp/namespacerclcpp.html#enum-members)
+for valid types.
+
+```cpp
+#include <vector>
+#include <rclcpp/rclcpp.hpp>
+
+class MyNode : public rclcpp::Node
+{
+public:
+  MyNode()
+  {
+    // Declare parameters first
+
+    // Then create callback
+    param_cb_ = this->add_on_set_parameters_callback(
+      std::bind(&MyNode::updateCallback, this, std::placeholders::_1));
+  }
+
+private:
+  // This will get called whenever a parameter gets updated
+  rcl_interfaces::msg::SetParametersResult updateCallback(
+    const std::vector<rclcpp::Parameter> & parameters)
+  {
+    rcl_interfaces::msg::SetParametersResult result;
+    result.successful = true;
+
+    for (const rclcpp::Parameter & param : parameters)
+    {
+      if (param.get_name() == "my_param_name")
+      {
+        if (param.get_type() != rclcpp::ParameterType::PARAMETER_STRING)
+        {
+          result.successful = false;
+          result.reason = "my_param_name must be a string";
+          break;
+        }
+
+        // Optionally do something with parameter
+      }
+    }
+
+    return result;
+  }
+
+  // Need to hold a pointer to the callback
+  rclcpp::node_interfaces::OnSetParametersCallbackHandle::SharedPtr param_cb_;
+};
+
+```

+ 96 - 0
docs/codebook/rclcpp/english/pcl.md

@@ -0,0 +1,96 @@
+# rclcpp: Point Clouds
+
+The sensor_msgs/PointCloud2 is a very common type of ROS message for processing
+perception data in ROS. It is also one of the most complex messages to actually
+interpret.
+
+The complexity of the message derives from the fact that it holds arbitrary
+fields in a single giant data store. This allows the PointCloud2 message to
+work with any type of cloud (for instance, XYZ points only, XYZRGB, or even
+XYZI), but adds a bit of complexity in accessing the data in the cloud.
+
+In ROS1, there was a simpler PointCloud message, but this has been
+[deprecated](https://github.com/ros2/common_interfaces/issues/105) and will
+be removed in ROS2-G.
+
+## Using the PointCloud2Iterator
+
+The sensor_msgs package provides a C++ PointCloud2Iterator which can be used
+to create, modify and access PointCloud2 messages.
+
+To create a new message:
+
+```cpp
+#include "sensor_msgs/msg/point_cloud2.hpp"
+#include "sensor_msgs/point_cloud2_iterator.hpp"
+
+sensor_msgs::msg::PointCloud2 msg;
+
+// Fill in the size of the cloud
+msg.height = 480;
+msg.width = 640;
+
+// Create the modifier to setup the fields and memory
+sensor_msgs::PointCloud2Modifier mod(msg);
+
+// Set the fields that our cloud will have
+mod.setPointCloud2FieldsByString(2, "xyz", "rgb");
+
+// Set up memory for our points
+mod.resize(msg.height * msg.width);
+
+// Now create iterators for fields
+sensor_msgs::PointCloud2Iterator<float> iter_x(cloud_msg, "x");
+sensor_msgs::PointCloud2Iterator<float> iter_y(cloud_msg, "y");
+sensor_msgs::PointCloud2Iterator<float> iter_z(cloud_msg, "z");
+sensor_msgs::PointCloud2Iterator<uint8_t> iter_r(cloud_msg, "r");
+sensor_msgs::PointCloud2Iterator<uint8_t> iter_g(cloud_msg, "g");
+sensor_msgs::PointCloud2Iterator<uint8_t> iter_b(cloud_msg, "b");
+
+for (; iter_x != iter_x.end(); ++iter_x, ++iter_y, ++iter_z, ++iter_r, ++iter_g, ++iter_b)
+{
+  *iter_x = 0.0;
+  *iter_y = 0.0;
+  *iter_z = 0.0;
+  *iter_r = 0;
+  *iter_g = 255;
+  *iter_b = 0;
+}
+```
+
+## Using PCL
+
+For a number of operations, you might want to convert to a pcl::PointCloud
+in order to use the extensive API of the
+[Point Cloud Library](https://pointclouds.org).
+
+In ROS1, the pcl_ros package allowed you to write a subscriber whose callback
+took a pcl::PointCloud directly, however this package has not yet been
+ported to ROS2. Regardless, it is pretty straight forward to do the conversion
+yourself with the pcl_conversions package:
+
+```cpp
+#include "pcl_conversions/pcl_conversions.h"
+
+void cloud_callback(const sensor_msgs::msg::PointCloud2::SharedPtr msg)
+{
+  // PCL still uses boost::shared_ptr internally
+  pcl::PointCloud<pcl::PointXYZRGB>::Ptr cloud =
+    boost::make_shared<pcl::PointCloud<pcl::PointXYZRGB>>();
+
+  // This will convert the message into a pcl::PointCloud
+  pcl::fromROSMsg(*msg, *cloud);
+}
+```
+
+You can also go in the opposite direction:
+
+```cpp
+#include "pcl_conversions/pcl_conversions.h"
+
+pcl::PointCloud<pcl::PointXYZRGB>::Ptr cloud;
+sensor_msgs::msg::PointCloud2 msg;
+
+pcl::toROSMsg(*cloud, msg);
+cloud_publisher->publish(msg);
+```

+ 85 - 0
docs/codebook/rclcpp/english/tf2.md

@@ -0,0 +1,85 @@
+# rclcpp: TF2
+
+The TF2 library provides easy access to transformations. All of the examples below
+require a dependency on the _tf2_ros_ package.
+
+## Broadcasting Transforms
+
+```cpp
+#include <tf2_ros/transform_broadcaster.h>
+std::unique_ptr<tf2_ros::TransformBroadcaster> broadcaster;
+
+broadcaster = std::make_unique<tf2_ros::TransformBroadcaster>(nodePtr);
+
+geometry_msgs::msg::TransformStamped transform;
+transform.header.stamp = node->now();
+transform.header.frame_id = "odom";
+transform.child_frame_id = "base_link";
+
+// Fill in transform.transform.translation
+// Fill in transform.transform.rotation
+
+broadcaster->sendTransform(transform);
+```
+
+## Listening for Transforms
+
+```cpp
+#include "tf2_ros/transform_listener.h"
+
+std::shared_ptr<tf2_ros::Buffer> tf_buffer;
+std::shared_ptr<tf2_ros::TransformListener> tf_listener;
+
+rclcpp::Node node("name_of_node");
+
+tf_buffer.reset(new tf2_ros::Buffer(node.get_clock()));
+tf_listener.reset(new tf2_ros::TransformListener(*tf_buffer_));
+```
+
+## Applying Transforms
+
+TF2 can be extended by packages that provide implementations of _transform_.
+The _tf2_geometry_msgs package provides these for _geometry_msgs_. The example
+below uses _geometry_msgs::msg::PointStamped_ - but this should work for any
+data type in _geometry_msgs_:
+
+```cpp
+#include <tf2_geometry_msgs/tf2_geometry_msgs.h>
+
+geometry_msg::msgs::PointStamped in, out;
+in.header.frame_id = "source_frame";
+
+try
+{
+  tf_buffer->transform(in, out, "target_frame");
+}
+catch (const tf2::TransformException& ex)
+{
+  RCLCPP_ERROR(rclcpp::get_logger("logger_name"), "Could not transform point.");
+}
+```
+
+The _transform_ function can also take a timeout. It will then wait for the
+transform to be available up to that amount of time:
+
+```cpp
+tf_buffer->transform(in, out, "target_frame", tf2::durationFromSec(1.0));
+```
+
+## Get Latest Transform
+
+A common work flow is to get the "latest" transform. In ROS2, this can be
+accomplished using _tf2::TimePointZero_, but requires using _lookupTransform_
+and then calling _doTransform_ (which is basically what _transform_ does
+internally):
+
+```cpp
+geometry_msgs::msg::PointStamped in, out;
+
+geometry_msgs::msg::TransformStamped transform =
+   tf_buffer->lookupTransform("target_frame",
+                              in.header.frame_id,
+                              tf2::TimePointZero);
+
+tf2::doTransform(in, out, transform);
+```

+ 31 - 0
docs/codebook/rclcpp/english/time.md

@@ -0,0 +1,31 @@
+# rclcpp: Time
+
+The _rclcpp::Time_ and _rclcpp::Duration_ are a significant departure from
+their ROS1 equivalents, but are more closely related to
+[std::chrono](https://en.cppreference.com/w/cpp/chrono). For an in-depth
+discussion comparing with std::chrono, see this discussion on
+[ROS Discourse](https://discourse.ros.org/t/ros-2-time-vs-std-chrono/6293).
+
+When porting certain ROS1 libraries, there may be significant usage of
+timestamps as floating-point seconds. To get floating point seconds from
+an _rclcpp::Time_:
+
+```cpp
+// node is instance of rclcpp::Node
+rclcpp::Time t = node.now();
+double seconds = t.seconds();
+```
+
+There is no constructor for Time from seconds represented by a floating point,
+so you first need to convert to nanoseconds:
+
+```cpp
+rclcpp::Time t(static_cast<uin64_t>(seconds * 1e9));
+```
+
+_rclcpp::Duration_ does have functions to go both directions:
+
+```cpp
+rclcpp::Duration d = rclcpp::Duration::from_seconds(1.0);
+double seconds = d.seconds();
+```

+ 79 - 0
docs/codebook/rclcpp/english/workarounds.md

@@ -0,0 +1,79 @@
+# rclcpp: Workarounds
+
+## Lazy Publishers
+
+ROS2 does not yet have subscriber connect callbacks. This code creates
+a function which is called periodically to check if we need to start
+or stop subscribers:
+
+```cpp
+#include <rclcpp/rclcpp.hpp>
+#include <std_msgs/msg/float64.hpp>
+
+class LazyPublisherEx : rclcpp::Node
+{
+public:
+  LazyPublisherEx(const rclcpp::NodeOptions & options) :
+    Node("lazy_ex", options)
+  {
+    // Setup timer
+    timer = this->create_wall_timer(
+      std::chrono::seconds(1),
+      std::bind(&LazyPublisher::periodic, this));
+  }
+
+private:
+  void periodic()
+  {
+    if (pub_.get_subscription_count() > 0)
+    {
+        // We have a subscriber, do any setup required
+    }
+    else
+    {
+        // Subscriber has disconnected, do any shutdown
+    }
+  }
+
+  rclcpp::Publisher<std_msgs::msg::Float64>::SharedPtr pub_;
+  rclcpp::TimerBase::SharedPtr timer_;
+};
+```
+
+The same can be done when using image transport, you simply
+have to change from _get_subscription_count_ to
+_getNumSubscribers_:
+
+```cpp
+#include <rclcpp/rclcpp.hpp>
+#include <image_transport/image_transport.h>
+
+class LazyPublisherEx : rclcpp::Node
+{
+public:
+  LazyPublisherEx(const rclcpp::NodeOptions & options) :
+    Node("lazy_ex", options)
+  {
+    // Setup timer
+    timer = this->create_wall_timer(
+      std::chrono::seconds(1),
+      std::bind(&LazyPublisher::periodic, this));
+  }
+
+private:
+  void periodic()
+  {
+    if (pub_.getNumSubscribers() > 0)
+    {
+        // We have a subscriber, do any setup required
+    }
+    else
+    {
+        // Subscriber has disconnected, do any shutdown
+    }
+  }
+
+  image_transport::CameraPublisher pub_;
+  rclcpp::TimerBase::SharedPtr timer_;
+};
+```

+ 66 - 0
docs/codebook/rclcpp/nodes.md

@@ -0,0 +1,66 @@
+# Rclcpp:节点和组件
+
+## 创建组件
+
+
+```cpp
+#include <rclcpp/rclcpp.hpp>
+
+namespace my_pkg
+{
+
+class MyComponent : public rclcpp::Node
+{
+public:
+  MyComponent(const rclcpp::NodeOptions& options)
+  : rclcpp::Node("node_name", options)
+  {
+    // Note: you cannot use shared_from_this()
+    //       here because the node is not fully
+    //       initialized.
+  }
+};
+
+}  // namespace my_pkg
+
+#include "rclcpp_components/register_node_macro.hpp"
+RCLCPP_COMPONENTS_REGISTER_NODE(my_pkg::MyComponent)
+```
+
+CMakeLists.txt 中的:
+
+
+```cmake
+add_library(my_component SHARED
+  src/my_component.cpp
+)
+ament_target_dependencies(my_component
+  rclcpp
+  rclcpp_components
+)
+
+# Also add a node executable which simply loads the component
+rclcpp_components_register_node(my_component
+  PLUGIN "my_pkg::MyComponent"
+  EXECUTABLE my_node
+)
+```
+
+## Executors
+
+要在线程中运行执行器,请执行以下操作:
+
+
+```cpp
+#include <thread>
+#include <rclcpp/rclcpp.hpp>
+#include <rclcpp/executors/single_threaded_executor.hpp>
+
+rclcpp::executors::SingleThreadedExecutor executor;
+
+// Node is rclcpp::Node::SharedPtr instance
+executor.add_node(node);
+std::thread executor_thread(
+  std::bind(&rclcpp::executors::SingleThreadedExecutor::spin,
+            &executor));
+```

+ 73 - 0
docs/codebook/rclcpp/parameters.md

@@ -0,0 +1,73 @@
+# Rclcpp:参数
+
+需要声明参数。同时,如果你不打算稍后再次更新值,则可以获得该值:
+
+
+```cpp
+// node is an instance of rclcpp::Node
+// 42 is a great default for a parameter
+int param = node.declare_parameter<int>("my_param_name", 42);
+```
+
+要获取值,请执行以下操作:
+
+
+```cpp
+int param;
+node.get_parameter("my_param_name", param);
+```
+
+## 动态参数
+
+在 ROS2 中,所有参数都可以通过 ROS2 服务动态更新(不需要像动态重新配置那样定义重复内容)。
+
+下面的例子适用于 eloquent 或更高版本(较早的 ROS2 版本只支持单个回调,并且有一个略有不同的 API)。有关有效类型的信息,请参阅的文档。
+
+
+```cpp
+#include <vector>
+#include <rclcpp/rclcpp.hpp>
+
+class MyNode : public rclcpp::Node
+{
+public:
+  MyNode()
+  {
+    // Declare parameters first
+
+    // Then create callback
+    param_cb_ = this->add_on_set_parameters_callback(
+      std::bind(&MyNode::updateCallback, this, std::placeholders::_1));
+  }
+
+private:
+  // This will get called whenever a parameter gets updated
+  rcl_interfaces::msg::SetParametersResult updateCallback(
+    const std::vector<rclcpp::Parameter> & parameters)
+  {
+    rcl_interfaces::msg::SetParametersResult result;
+    result.successful = true;
+
+    for (const rclcpp::Parameter & param : parameters)
+    {
+      if (param.get_name() == "my_param_name")
+      {
+        if (param.get_type() != rclcpp::ParameterType::PARAMETER_STRING)
+        {
+          result.successful = false;
+          result.reason = "my_param_name must be a string";
+          break;
+        }
+
+        // Optionally do something with parameter
+      }
+    }
+
+    return result;
+  }
+
+  // Need to hold a pointer to the callback
+  rclcpp::node_interfaces::OnSetParametersCallbackHandle::SharedPtr param_cb_;
+};
+
+```

+ 86 - 0
docs/codebook/rclcpp/pcl.md

@@ -0,0 +1,86 @@
+# rclcpp: Point Clouds
+
+`sensor_msgs/PointCloud2`  是一种非常常见的 ROS 消息类型,用于处理 ROS 中的感知数据。这也是实际要解释的最复杂的信息之一。
+
+消息的复杂性源于它在单个巨型数据存储中包含任意字段这一事实。这允许 PointCloud2 消息与任何类型的云(例如,仅 XYZ 点、XYZRGB,甚至 XYZI)一起工作,但在访问云中的数据时增加了一些复杂性。
+
+在 ROS1 中,有一个更简单的 PointCloud 消息,但这已经被删除,并将在 ROS2-G 中删除。
+
+## 使用 PointCloud2 迭代器
+
+sensor_msgs 包提供了一个 C++ PointCloud2Iterator,可用于创建、修改和访问 PointCloud2 消息。
+
+要创建新消息:
+
+
+```cpp
+#include "sensor_msgs/msg/point_cloud2.hpp"
+#include "sensor_msgs/point_cloud2_iterator.hpp"
+
+sensor_msgs::msg::PointCloud2 msg;
+
+// Fill in the size of the cloud
+msg.height = 480;
+msg.width = 640;
+
+// Create the modifier to setup the fields and memory
+sensor_msgs::PointCloud2Modifier mod(msg);
+
+// Set the fields that our cloud will have
+mod.setPointCloud2FieldsByString(2, "xyz", "rgb");
+
+// Set up memory for our points
+mod.resize(msg.height * msg.width);
+
+// Now create iterators for fields
+sensor_msgs::PointCloud2Iterator<float> iter_x(cloud_msg, "x");
+sensor_msgs::PointCloud2Iterator<float> iter_y(cloud_msg, "y");
+sensor_msgs::PointCloud2Iterator<float> iter_z(cloud_msg, "z");
+sensor_msgs::PointCloud2Iterator<uint8_t> iter_r(cloud_msg, "r");
+sensor_msgs::PointCloud2Iterator<uint8_t> iter_g(cloud_msg, "g");
+sensor_msgs::PointCloud2Iterator<uint8_t> iter_b(cloud_msg, "b");
+
+for (; iter_x != iter_x.end(); ++iter_x, ++iter_y, ++iter_z, ++iter_r, ++iter_g, ++iter_b)
+{
+  *iter_x = 0.0;
+  *iter_y = 0.0;
+  *iter_z = 0.0;
+  *iter_r = 0;
+  *iter_g = 255;
+  *iter_b = 0;
+}
+```
+
+## 使用 PCL
+
+对于许多操作,你可能希望转换为 pcl::PointCloud 以便使用的扩展 API [Point Cloud Library](https://pointclouds.org)。
+
+在 ROS1 ,pcl_ros 包允许你编写一个订阅者,它的回调直接接受 pcl::PointCloud,但是这个包还没有被移植到 ROS2. 无论如何,使用 pcl_conversions 包自己进行转换是非常简单的:
+
+
+```cpp
+#include "pcl_conversions/pcl_conversions.h"
+
+void cloud_callback(const sensor_msgs::msg::PointCloud2::SharedPtr msg)
+{
+  // PCL still uses boost::shared_ptr internally
+  pcl::PointCloud<pcl::PointXYZRGB>::Ptr cloud =
+    boost::make_shared<pcl::PointCloud<pcl::PointXYZRGB>>();
+
+  // This will convert the message into a pcl::PointCloud
+  pcl::fromROSMsg(*msg, *cloud);
+}
+```
+
+反之,你也可以反方向转换:
+
+
+```cpp
+#include "pcl_conversions/pcl_conversions.h"
+
+pcl::PointCloud<pcl::PointXYZRGB>::Ptr cloud;
+sensor_msgs::msg::PointCloud2 msg;
+
+pcl::toROSMsg(*cloud, msg);
+cloud_publisher->publish(msg);
+```

+ 80 - 0
docs/codebook/rclcpp/tf2.md

@@ -0,0 +1,80 @@
+# Rclcpp:TF2
+
+TF2 库提供了对转换的轻松访问。以下所有示例都需要对 _tf2_ros_ 的依赖关系。
+
+## 发布TF
+
+
+```cpp
+#include <tf2_ros/transform_broadcaster.h>
+std::unique_ptr<tf2_ros::TransformBroadcaster> broadcaster;
+
+broadcaster = std::make_unique<tf2_ros::TransformBroadcaster>(nodePtr);
+
+geometry_msgs::msg::TransformStamped transform;
+transform.header.stamp = node->now();
+transform.header.frame_id = "odom";
+transform.child_frame_id = "base_link";
+
+// Fill in transform.transform.translation
+// Fill in transform.transform.rotation
+
+broadcaster->sendTransform(transform);
+```
+
+## 监听TF
+
+
+```cpp
+#include "tf2_ros/transform_listener.h"
+
+std::shared_ptr<tf2_ros::Buffer> tf_buffer;
+std::shared_ptr<tf2_ros::TransformListener> tf_listener;
+
+rclcpp::Node node("name_of_node");
+
+tf_buffer.reset(new tf2_ros::Buffer(node.get_clock()));
+tf_listener.reset(new tf2_ros::TransformListener(*tf_buffer_));
+```
+
+## TF变换
+
+TF2 可以通过提供实现的包进行扩展。GEOMETRY_msgs 程序包为 msgs_ 提供这些。下面的示例使用 msgs::msg::PointStamed_,但这应该适用于 msgs_ 中的任何数据类型:
+
+
+```cpp
+#include <tf2_geometry_msgs/tf2_geometry_msgs.h>
+
+geometry_msg::msgs::PointStamped in, out;
+in.header.frame_id = "source_frame";
+
+try
+{
+  tf_buffer->transform(in, out, "target_frame");
+}
+catch (const tf2::TransformException& ex)
+{
+  RCLCPP_ERROR(rclcpp::get_logger("logger_name"), "Could not transform point.");
+}
+```
+
+_transform_ 函数还可以接受超时。然后它将等待转换可用的时间达到这个时间量:
+
+```cpp
+tf_buffer->transform(in, out, "target_frame", tf2::durationFromSec(1.0));
+```
+
+## 获取最新TF
+
+常见的工作方式是获得“最新”转换。在 ros2中,这可以使用 tf2::TimePointZero 来实现,但是需要使用 lookupTransform 然后调用 doTransform (基本上就是在内部进行转换) :
+
+```cpp
+geometry_msgs::msg::PointStamped in, out;
+
+geometry_msgs::msg::TransformStamped transform =
+   tf_buffer->lookupTransform("target_frame",
+                              in.header.frame_id,
+                              tf2::TimePointZero);
+
+tf2::doTransform(in, out, transform);
+```

+ 27 - 0
docs/codebook/rclcpp/time.md

@@ -0,0 +1,27 @@
+# rclcpp: Time
+
+_rclcpp::Time_ 和  _rclcpp::Duration_ 和ROS1中的用法偏差较大,但与[std::chrono](https://en.cppreference.com/w/cpp/chrono) 的关系更为密切。[ROS Discourse](https://discourse.ros.org/t/ros-2-time-vs-std-chrono/6293) 可以看到与其有关的比较深入的讨论。
+
+在移植某些ros1库时,时间戳可能会被大量用作浮点秒。从 rclcpp 获取浮点秒 _rclcpp::Time_:
+
+
+```cpp
+// node is instance of rclcpp::Node
+rclcpp::Time t = node.now();
+double seconds = t.seconds();
+```
+
+没有用于从浮点表示的秒开始的时间的构造函数,因此你首先需要转换为纳秒:
+
+
+```cpp
+rclcpp::Time t(static_cast<uin64_t>(seconds * 1e9));
+```
+
+确实具有双向功能:
+
+
+```cpp
+rclcpp::Duration d = rclcpp::Duration::from_seconds(1.0);
+double seconds = d.seconds();
+```

+ 77 - 0
docs/codebook/rclcpp/workarounds.md

@@ -0,0 +1,77 @@
+# rclcpp: Workarounds
+
+## 懒订阅
+
+ROS2 还没有订户连接回叫。此代码创建一个函数,定期调用该函数来检查我们是否需要启动或停止订阅者:
+
+
+```cpp
+#include <rclcpp/rclcpp.hpp>
+#include <std_msgs/msg/float64.hpp>
+
+class LazyPublisherEx : rclcpp::Node
+{
+public:
+  LazyPublisherEx(const rclcpp::NodeOptions & options) :
+    Node("lazy_ex", options)
+  {
+    // Setup timer
+    timer = this->create_wall_timer(
+      std::chrono::seconds(1),
+      std::bind(&LazyPublisher::periodic, this));
+  }
+
+private:
+  void periodic()
+  {
+    if (pub_.get_subscription_count() > 0)
+    {
+        // We have a subscriber, do any setup required
+    }
+    else
+    {
+        // Subscriber has disconnected, do any shutdown
+    }
+  }
+
+  rclcpp::Publisher<std_msgs::msg::Float64>::SharedPtr pub_;
+  rclcpp::TimerBase::SharedPtr timer_;
+};
+```
+
+
+在使用图像传输时也可以这样做,你只需要将 _get_subscription_count_ 更改为 _getNumSubscribers_:
+
+```cpp
+#include <rclcpp/rclcpp.hpp>
+#include <image_transport/image_transport.h>
+
+class LazyPublisherEx : rclcpp::Node
+{
+public:
+  LazyPublisherEx(const rclcpp::NodeOptions & options) :
+    Node("lazy_ex", options)
+  {
+    // Setup timer
+    timer = this->create_wall_timer(
+      std::chrono::seconds(1),
+      std::bind(&LazyPublisher::periodic, this));
+  }
+
+private:
+  void periodic()
+  {
+    if (pub_.getNumSubscribers() > 0)
+    {
+        // We have a subscriber, do any setup required
+    }
+    else
+    {
+        // Subscriber has disconnected, do any shutdown
+    }
+  }
+
+  image_transport::CameraPublisher pub_;
+  rclcpp::TimerBase::SharedPtr timer_;
+};
+```

+ 51 - 0
docs/codebook/rclpy/english/nodes.md

@@ -0,0 +1,51 @@
+# rclpy: Node Basics
+
+Most nodes have publishers and subscribers, both of which are creating by
+calling functions of the _Node_ instance:
+
+```python
+import rclpy
+from rclpy.node import Node
+
+from std_msgs.msg import String
+
+class MyNode(Node):
+
+    def __init__(self):
+        super().__init__('my_node_name')
+
+        self.publisher = self.create_publisher(String, 'output_topic', 10)
+        self.subscription = self.create_subscription(
+            String,
+            'input_topic',
+            self.callback,
+            10)
+
+    def callback(self, msg):
+        self.get_logger().info("Recieved: %s" % msg.data)
+        self.publisher.publish(msg)
+
+if __name___ == "__main__":
+    rclpy.init()
+    my_node = MyNode()
+    rclpy.spin(my_node)
+    my_node.destroy_node()  # cleans up pub-subs, etc
+    rclpy.shutdown()
+```
+
+## Shutdown Hooks
+
+ROS1 had rospy.on_shutdown() - but there is
+[not an equivalent in ROS2](https://github.com/ros2/rclpy/issues/244). It really is not needed though, since we manually shut things down rather than
+was the case in rospy which used many global variables:
+
+```python
+try:
+    rclpy.spin(my_node)
+except KeyboardInterrupt:
+    pass
+finally:
+    my_node.on_shutdown()  # do any custom cleanup
+    my_node.destroy_node()
+    rclpy.shutdown()
+```

+ 53 - 0
docs/codebook/rclpy/english/parameters.md

@@ -0,0 +1,53 @@
+# rclpy: Parameters
+
+```python
+# node is rclpy.node.Node instance
+# 42 is a great default for a parameter
+node.declare_parameter("my_param_name", 42)
+
+# To get the value:
+param = node.get_parameter("my_param_name").value
+```
+
+## Dynamic Parameters
+
+In ROS2, all parameters can be dynamically updated through a ROS2 service
+(there is no need to define duplicate stuff as with dynamic reconfigure).
+
+```python
+from rcl_interfaces.msg import SetParametersResult
+
+import rclpy
+from rclpy.node import Node
+
+class MyNode(Node):
+
+    def __init__(self):
+        super().__init__('my_node_name')
+
+        # Declare a parameter
+        self.declare_parameter("my_param_name", 42)
+
+        # Then create callback
+        self.set_parameters_callback(self.callback)
+    
+    def callback(self, parameters):
+        result = SetParametersResult(successful=True)
+
+        for p in parameters:
+            if p.name == "my_param_name":
+                if p.type_ != p.Type.INTEGER:
+                    result.successful = False
+                    result.reason = 'my_param_name must be an Integer'
+                    return result
+                if p.value < 20:
+                    result.successful = False
+                    result.reason = "my_param_name must be >= 20"
+                    return result
+
+        # Return success, so updates are seen via get_parameter()
+        return result
+```
+
+For an example of calling the set_parameters service, see
+[ROS Answers](https://answers.ros.org/question/308541/ros2-rclpy-set-parameter-example/)

+ 71 - 0
docs/codebook/rclpy/english/tf2.md

@@ -0,0 +1,71 @@
+# rclpy: TF2
+
+The TF2 library provides easy access to transformations. All of the examples below
+require a dependency on the _tf2_ros_ package.
+
+## Listening for Transforms
+
+```python
+import rclpy
+from rclpy.node import Node
+from tf2_ros.buffer import Buffer
+from tf2_ros.transform_listener import TransformListener
+
+class MyNode(Node):
+    def __init__(self):
+        super().__init__("my_node")
+
+        self.buffer = Buffer()
+        self.listener = TransformListener(self.buffer, self)
+```
+
+## Applying Transforms
+
+TF2 can be extended by packages that provide implementations of _transform_.
+The _tf2_geometry_msgs package provides these for _geometry_msgs_. The example
+below uses _geometry_msgs.msg.PointStamped_ - but this should work for any
+data type in _geometry_msgs_:
+
+```python
+from geometry_msgs.msg import PointStamped
+from tf2_ros.buffer import Buffer
+from tf2_ros.transform_listener import TransformListener
+import tf2_geometry_msgs
+
+# Setup buffer/listener as above
+
+p1 = PointStamped()
+p1.header.frame_id = "source_frame"
+# fill in p1
+
+p2 = buffer.transform(p1, "target_frame")
+```
+
+## Transformations
+
+In ROS1, tf included the _transformations_ module. tf2 has no similar module.
+It is recommended to use transforms3d Python package, which is available through
+pip:
+
+```
+sudo pip3 install transforms3d
+```
+
+For instance, to rotate a point:
+
+```python
+import numpy as np
+from transforms3d.quaternion import quat2mat
+
+# Create rotation matrix from quaternion
+R = quat2mat([w, x, y, z])
+# Create a vector to rotate
+V = np.array([x, y, z]).reshape((3, 1))
+# Rotate the vector
+M = np.dot(R, V)
+
+p = PointStamped()
+p.point.x = M[0, 0]
+p.point.y = M[1, 0]
+p.point.z = M[2, 0]
+```

+ 42 - 0
docs/codebook/rclpy/english/time.md

@@ -0,0 +1,42 @@
+# rclpy: Time
+
+To get the equivalent of rospy.Time.now(), you now need a ROS2 node:
+
+```python
+import rclpy
+from rclpy.node import Node
+
+class MyNode(Node):
+
+    def some_func(self):
+        t = self.get_clock().now()
+        msg.header.stamp = t.to_msg()
+```
+
+Converting from Duration to messages is common:
+
+```python
+import rclpy
+from rclpy.duration import Duration
+
+msg.duration = Duration(seconds=1).to_msg()
+```
+
+Timers are created from the Node:
+
+```python
+import rclpy
+from rclpy.node import Node
+
+class MyNode(Node):
+
+    def __init__(self):
+        super().__init__("my_node")
+
+        # Create a timer that fires every quarter second
+        timer_period = 0.25
+        self.timer = self.create_timer(timer_period, self.callback)
+
+    def callback(self):
+        self.get_logger().info("timer has fired")
+```

+ 49 - 0
docs/codebook/rclpy/nodes.md

@@ -0,0 +1,49 @@
+# rclpy:节点基础知识
+
+大多数节点都有发布者和订阅者,它们都是通过调用实例的函数创建的:
+
+
+```python
+import rclpy
+from rclpy.node import Node
+
+from std_msgs.msg import String
+
+class MyNode(Node):
+
+    def __init__(self):
+        super().__init__('my_node_name')
+
+        self.publisher = self.create_publisher(String, 'output_topic', 10)
+        self.subscription = self.create_subscription(
+            String,
+            'input_topic',
+            self.callback,
+            10)
+
+    def callback(self, msg):
+        self.get_logger().info("Recieved: %s" % msg.data)
+        self.publisher.publish(msg)
+
+if __name___ == "__main__":
+    rclpy.init()
+    my_node = MyNode()
+    rclpy.spin(my_node)
+    my_node.destroy_node()  # cleans up pub-subs, etc
+    rclpy.shutdown()
+```
+
+## 关闭Handle
+
+ROS1 有rospy.on_shutdown() - 但是 [不等于ROS2也有](https://github.com/ros2/rclpy/issues/244),不过,这真的不需要,因为我们手动关闭了一些东西,而不是像 rospy 那样使用了许多全局变量:
+
+```python
+try:
+    rclpy.spin(my_node)
+except KeyboardInterrupt:
+    pass
+finally:
+    my_node.on_shutdown()  # do any custom cleanup
+    my_node.destroy_node()
+    rclpy.shutdown()
+```

+ 53 - 0
docs/codebook/rclpy/parameters.md

@@ -0,0 +1,53 @@
+# rclpy:参数
+
+
+```python
+# node is rclpy.node.Node instance
+# 42 is a great default for a parameter
+node.declare_parameter("my_param_name", 42)
+
+# To get the value:
+param = node.get_parameter("my_param_name").value
+```
+
+## 动态参数
+
+在 ROS2 中,所有参数都可以通过 ROS2 服务动态更新(不需要像动态重新配置那样定义重复内容)。
+
+
+```python
+from rcl_interfaces.msg import SetParametersResult
+
+import rclpy
+from rclpy.node import Node
+
+class MyNode(Node):
+
+    def __init__(self):
+        super().__init__('my_node_name')
+
+        # Declare a parameter
+        self.declare_parameter("my_param_name", 42)
+
+        # Then create callback
+        self.set_parameters_callback(self.callback)
+    
+    def callback(self, parameters):
+        result = SetParametersResult(successful=True)
+
+        for p in parameters:
+            if p.name == "my_param_name":
+                if p.type_ != p.Type.INTEGER:
+                    result.successful = False
+                    result.reason = 'my_param_name must be an Integer'
+                    return result
+                if p.value < 20:
+                    result.successful = False
+                    result.reason = "my_param_name must be >= 20"
+                    return result
+
+        # Return success, so updates are seen via get_parameter()
+        return result
+```
+
+有关调用 SET_PARAMETERS 服务的示例,请参阅 [ROS Answers](https://answers.ros.org/question/308541/ros2-rclpy-set-parameter-example/)

+ 69 - 0
docs/codebook/rclpy/tf2.md

@@ -0,0 +1,69 @@
+# RCLPY:TF2
+
+TF2 库提供了对转换的轻松访问。以下所有示例都需要对 ros_Package 的依赖关系。
+
+## 监听转换
+
+
+```python
+import rclpy
+from rclpy.node import Node
+from tf2_ros.buffer import Buffer
+from tf2_ros.transform_listener import TransformListener
+
+class MyNode(Node):
+    def __init__(self):
+        super().__init__("my_node")
+
+        self.buffer = Buffer()
+        self.listener = TransformListener(self.buffer, self)
+```
+
+## 应用变换
+
+TF2 可以通过提供实现的包进行扩展。GEOMETRY_msgs 程序包为 msgs_ 提供这些。下面的示例使用 msgs.msg.PointStamed_,但这应该适用于 msgs_ 中的任何数据类型:
+
+
+```python
+from geometry_msgs.msg import PointStamped
+from tf2_ros.buffer import Buffer
+from tf2_ros.transform_listener import TransformListener
+import tf2_geometry_msgs
+
+# Setup buffer/listener as above
+
+p1 = PointStamped()
+p1.header.frame_id = "source_frame"
+# fill in p1
+
+p2 = buffer.transform(p1, "target_frame")
+```
+
+## 变换
+
+在 ROS1 中,Tf 包括模块。TF2 没有类似的模块。建议使用 Transforms3d Python 包,可通过 pip 获取:
+
+
+```
+sudo pip3 install transforms3d
+```
+
+例如,要旋转点:
+
+
+```python
+import numpy as np
+from transforms3d.quaternion import quat2mat
+
+# Create rotation matrix from quaternion
+R = quat2mat([w, x, y, z])
+# Create a vector to rotate
+V = np.array([x, y, z]).reshape((3, 1))
+# Rotate the vector
+M = np.dot(R, V)
+
+p = PointStamped()
+p.point.x = M[0, 0]
+p.point.y = M[1, 0]
+p.point.z = M[2, 0]
+```

+ 45 - 0
docs/codebook/rclpy/time.md

@@ -0,0 +1,45 @@
+# rclpy: Time
+
+要获得相当于 rospy.Time.now()的内容,你现在需要一个 ROS2 节点:
+
+
+```python
+import rclpy
+from rclpy.node import Node
+
+class MyNode(Node):
+
+    def some_func(self):
+        t = self.get_clock().now()
+        msg.header.stamp = t.to_msg()
+```
+
+从持续时间转换为消息很常见:
+
+
+```python
+import rclpy
+from rclpy.duration import Duration
+
+msg.duration = Duration(seconds=1).to_msg()
+```
+
+计时器是从节点创建的:
+
+
+```python
+import rclpy
+from rclpy.node import Node
+
+class MyNode(Node):
+
+    def __init__(self):
+        super().__init__("my_node")
+
+        # Create a timer that fires every quarter second
+        timer_period = 0.25
+        self.timer = self.create_timer(timer_period, self.callback)
+
+    def callback(self):
+        self.get_logger().info("timer has fired")
+```