From a0f1c6d0dcb8718221a89b617511332e292cf2c2 Mon Sep 17 00:00:00 2001 From: TWang Date: Sun, 13 Jul 2025 20:55:29 +0800 Subject: [PATCH 01/20] initial: ros humble tutorial --- docker/Dockerfile | 30 +- docker/compose.yaml | 30 + docker/docker-compose.yaml | 34 - src/communication/CMakeLists.txt | 209 ----- src/communication/package.xml | 68 -- src/communication/src/stm_communication.cpp | 26 - src/message/CMakeLists.txt | 221 ----- src/message/msg/example.msg | 3 - src/message/package.xml | 74 -- src/message/scripts/listener.py | 19 - src/message/scripts/talker.py | 18 - src/message/src/a.in | 6 - src/message/src/day2_listener.cpp | 22 - src/message/src/day2_talker.cpp | 24 - src/message/src/day3_practice_listener.cpp | 23 - src/message/src/day3_practice_talker.cpp | 37 - src/message/src/turtle_homework.cpp | 73 -- src/oop/CMakeLists.txt | 216 ----- src/oop/include/chassis.h | 27 - src/oop/package.xml | 68 -- src/oop/src/chassis.cpp | 25 - src/oop/src/oop_ex1.cpp | 52 -- src/oop/src/oop_ex2.cpp | 36 - src/oop/src/oop_ex3.cpp | 87 -- src/rosserial_msgs/CHANGELOG.rst | 105 --- src/rosserial_msgs/CMakeLists.txt | 21 - src/rosserial_msgs/msg/Log.msg | 10 - src/rosserial_msgs/msg/TopicInfo.msg | 21 - src/rosserial_msgs/package.xml | 18 - src/rosserial_msgs/srv/RequestParam.srv | 7 - src/rosserial_python/CHANGELOG.rst | 175 ---- src/rosserial_python/CMakeLists.txt | 12 - .../nodes/message_info_service.py | 47 - src/rosserial_python/nodes/serial_node.py | 109 --- src/rosserial_python/package.xml | 19 - src/rosserial_python/setup.py | 11 - .../src/rosserial_python/SerialClient.py | 821 ------------------ .../src/rosserial_python/__init__.py | 1 - .../__pycache__/SerialClient.cpython-38.pyc | Bin 23691 -> 0 bytes src/service/CMakeLists.txt | 209 ----- src/service/config/Practice-Param.yaml | 4 - src/service/launch/Practice-Launch.launch | 5 - src/service/package.xml | 68 -- src/service/src/day6_practice.cpp | 90 -- src/tf/CMakeLists.txt | 206 ----- src/tf/package.xml | 68 -- 46 files changed, 50 insertions(+), 3405 deletions(-) create mode 100644 docker/compose.yaml delete mode 100755 docker/docker-compose.yaml delete mode 100644 src/communication/CMakeLists.txt delete mode 100644 src/communication/package.xml delete mode 100644 src/communication/src/stm_communication.cpp delete mode 100755 src/message/CMakeLists.txt delete mode 100644 src/message/msg/example.msg delete mode 100755 src/message/package.xml delete mode 100644 src/message/scripts/listener.py delete mode 100644 src/message/scripts/talker.py delete mode 100644 src/message/src/a.in delete mode 100644 src/message/src/day2_listener.cpp delete mode 100644 src/message/src/day2_talker.cpp delete mode 100644 src/message/src/day3_practice_listener.cpp delete mode 100644 src/message/src/day3_practice_talker.cpp delete mode 100644 src/message/src/turtle_homework.cpp delete mode 100755 src/oop/CMakeLists.txt delete mode 100755 src/oop/include/chassis.h delete mode 100755 src/oop/package.xml delete mode 100755 src/oop/src/chassis.cpp delete mode 100755 src/oop/src/oop_ex1.cpp delete mode 100755 src/oop/src/oop_ex2.cpp delete mode 100755 src/oop/src/oop_ex3.cpp delete mode 100644 src/rosserial_msgs/CHANGELOG.rst delete mode 100644 src/rosserial_msgs/CMakeLists.txt delete mode 100644 src/rosserial_msgs/msg/Log.msg delete mode 100644 src/rosserial_msgs/msg/TopicInfo.msg delete mode 100644 src/rosserial_msgs/package.xml delete mode 100644 src/rosserial_msgs/srv/RequestParam.srv delete mode 100644 src/rosserial_python/CHANGELOG.rst delete mode 100644 src/rosserial_python/CMakeLists.txt delete mode 100644 src/rosserial_python/nodes/message_info_service.py delete mode 100644 src/rosserial_python/nodes/serial_node.py delete mode 100644 src/rosserial_python/package.xml delete mode 100644 src/rosserial_python/setup.py delete mode 100644 src/rosserial_python/src/rosserial_python/SerialClient.py delete mode 100644 src/rosserial_python/src/rosserial_python/__init__.py delete mode 100644 src/rosserial_python/src/rosserial_python/__pycache__/SerialClient.cpython-38.pyc delete mode 100755 src/service/CMakeLists.txt delete mode 100644 src/service/config/Practice-Param.yaml delete mode 100644 src/service/launch/Practice-Launch.launch delete mode 100755 src/service/package.xml delete mode 100644 src/service/src/day6_practice.cpp delete mode 100755 src/tf/CMakeLists.txt delete mode 100755 src/tf/package.xml diff --git a/docker/Dockerfile b/docker/Dockerfile index 02c02dc..c6c23d1 100755 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -1,11 +1,25 @@ -FROM osrf/ros:noetic-desktop-full +FROM osrf/ros:humble-desktop-full + +LABEL shell=bash + +ARG USERNAME=user +ARG USER_UID=1000 +ARG USER_GID=$USER_UID + +# Create the user +# add user with default bash +RUN groupadd --gid $USER_GID $USERNAME \ + && useradd --uid $USER_UID --gid $USER_GID -m $USERNAME \ + && apt-get update \ + && apt-get install -y sudo \ + && echo $USERNAME ALL=\(root\) NOPASSWD:ALL > /etc/sudoers.d/$USERNAME \ + && chmod 0440 /etc/sudoers.d/$USERNAME \ + && rm -rf /var/lib/apt/lists/* # Install common tools RUN apt-get update && apt-get install -y \ curl \ git \ - git-extras \ - htop \ tree \ net-tools \ tmux \ @@ -14,12 +28,6 @@ RUN apt-get update && apt-get install -y \ python3-pip \ bash-completion -RUN apt-get update && apt-get install -y \ - ros-noetic-dynamixel-sdk \ - ros-noetic-turtlebot3-msgs \ - ros-noetic-turtlebot3 \ - ros-noetic-gazebo-ros-pkgs - RUN apt-get update && apt-get dist-upgrade -y \ && apt-get autoremove -y \ && apt-get autoclean -y \ @@ -28,4 +36,6 @@ RUN apt-get update && apt-get dist-upgrade -y \ ENV SHELL /bin/bash ENV TERM=xterm-256color -CMD ["/bin/bash"] \ No newline at end of file +USER $USERNAME + +CMD ["/bin/bash"] diff --git a/docker/compose.yaml b/docker/compose.yaml new file mode 100644 index 0000000..3470b1b --- /dev/null +++ b/docker/compose.yaml @@ -0,0 +1,30 @@ +name: ros2 +services: + ros2: + build: + context: . + args: + USERNAME: user + image: dit/ros2_tutorial:latest + container_name: ros2_tutorial + stdin_open: true + tty: true + privileged: true + stop_grace_period: 1s + + network_mode: host + working_dir: /home/user/ros2_ws + environment: + - DISPLAY=$DISPLAY + - XAUTHORITY=/root/.Xauthority + - ROS_WS=/home/user/tutorial_ws + volumes: + - /etc/timezone:/etc/timezone:ro + - /etc/localtime:/etc/localtime:ro + # Mount X11 server + - /d/.Xauthority:/root/.Xauthority + - /tmp/.X11-unix:/tmp/.X11-unix:rw + # Direct Rendering Infrastructure + - /dev:/dev + - ../ros2_ws:/home/user/ros2_ws + command: bash diff --git a/docker/docker-compose.yaml b/docker/docker-compose.yaml deleted file mode 100755 index 882d54f..0000000 --- a/docker/docker-compose.yaml +++ /dev/null @@ -1,34 +0,0 @@ -version: '3' -services: - ros1-tutorial-ws: - build: - context: . - dockerfile: Dockerfile - args: - image: dit/tutorial-ws-noetic:latest - container_name: tutorial-ws-ros1 - stdin_open: true - tty: true - privileged: true - - network_mode: host - working_dir: /home/user/tutorial_ws - environment: - - DISPLAY=host.docker.internal:0.0 - - XAUTHORITY=/root/.Xauthority - - ROS_WS=/home/user/tutorial_ws - volumes: - # Mount local timezone into container. ( Readonly ) - # Reference: https://stackoverflow.com/questions/57607381/how-do-i-change-timezone-in-a-docker-container - - /etc/timezone:/etc/timezone:ro - - /etc/localtime:/etc/localtime:ro - # Mount X11 server - - /d/.Xauthority:/root/.Xauthority - - /tmp/.X11-unix:/tmp/.X11-unix:rw - # Direct Rendering Infrastructure - - /dev:/dev - - ../:/home/user/tutorial_ws - devices: - - /dev/bus/usb - - command: /bin/bash -c "source /opt/ros/noetic/setup.bash && exec bash" diff --git a/src/communication/CMakeLists.txt b/src/communication/CMakeLists.txt deleted file mode 100644 index 6032742..0000000 --- a/src/communication/CMakeLists.txt +++ /dev/null @@ -1,209 +0,0 @@ -cmake_minimum_required(VERSION 3.0.2) -project(communication) - -## Compile as C++11, supported in ROS Kinetic and newer -# add_compile_options(-std=c++11) - -## Find catkin macros and libraries -## if COMPONENTS list like find_package(catkin REQUIRED COMPONENTS xyz) -## is used, also find other catkin packages -find_package(catkin REQUIRED COMPONENTS - roscpp - rospy - std_msgs -) - -## System dependencies are found with CMake's conventions -# find_package(Boost REQUIRED COMPONENTS system) - - -## Uncomment this if the package has a setup.py. This macro ensures -## modules and global scripts declared therein get installed -## See http://ros.org/doc/api/catkin/html/user_guide/setup_dot_py.html -# catkin_python_setup() - -################################################ -## Declare ROS messages, services and actions ## -################################################ - -## To declare and build messages, services or actions from within this -## package, follow these steps: -## * Let MSG_DEP_SET be the set of packages whose message types you use in -## your messages/services/actions (e.g. std_msgs, actionlib_msgs, ...). -## * In the file package.xml: -## * add a build_depend tag for "message_generation" -## * add a build_depend and a exec_depend tag for each package in MSG_DEP_SET -## * If MSG_DEP_SET isn't empty the following dependency has been pulled in -## but can be declared for certainty nonetheless: -## * add a exec_depend tag for "message_runtime" -## * In this file (CMakeLists.txt): -## * add "message_generation" and every package in MSG_DEP_SET to -## find_package(catkin REQUIRED COMPONENTS ...) -## * add "message_runtime" and every package in MSG_DEP_SET to -## catkin_package(CATKIN_DEPENDS ...) -## * uncomment the add_*_files sections below as needed -## and list every .msg/.srv/.action file to be processed -## * uncomment the generate_messages entry below -## * add every package in MSG_DEP_SET to generate_messages(DEPENDENCIES ...) - -## Generate messages in the 'msg' folder -# add_message_files( -# FILES -# Message1.msg -# Message2.msg -# ) - -## Generate services in the 'srv' folder -# add_service_files( -# FILES -# Service1.srv -# Service2.srv -# ) - -## Generate actions in the 'action' folder -# add_action_files( -# FILES -# Action1.action -# Action2.action -# ) - -## Generate added messages and services with any dependencies listed here -# generate_messages( -# DEPENDENCIES -# std_msgs -# ) - -################################################ -## Declare ROS dynamic reconfigure parameters ## -################################################ - -## To declare and build dynamic reconfigure parameters within this -## package, follow these steps: -## * In the file package.xml: -## * add a build_depend and a exec_depend tag for "dynamic_reconfigure" -## * In this file (CMakeLists.txt): -## * add "dynamic_reconfigure" to -## find_package(catkin REQUIRED COMPONENTS ...) -## * uncomment the "generate_dynamic_reconfigure_options" section below -## and list every .cfg file to be processed - -## Generate dynamic reconfigure parameters in the 'cfg' folder -# generate_dynamic_reconfigure_options( -# cfg/DynReconf1.cfg -# cfg/DynReconf2.cfg -# ) - -################################### -## catkin specific configuration ## -################################### -## The catkin_package macro generates cmake config files for your package -## Declare things to be passed to dependent projects -## INCLUDE_DIRS: uncomment this if your package contains header files -## LIBRARIES: libraries you create in this project that dependent projects also need -## CATKIN_DEPENDS: catkin_packages dependent projects also need -## DEPENDS: system dependencies of this project that dependent projects also need -catkin_package( -# INCLUDE_DIRS include -# LIBRARIES communication -# CATKIN_DEPENDS roscpp rospy std_msgs -# DEPENDS system_lib -) - -########### -## Build ## -########### - -## Specify additional locations of header files -## Your package locations should be listed before other locations -include_directories( -# include - ${catkin_INCLUDE_DIRS} -) - -## Declare a C++ library -# add_library(${PROJECT_NAME} -# src/${PROJECT_NAME}/communication.cpp -# ) - -## Add cmake target dependencies of the library -## as an example, code may need to be generated before libraries -## either from message generation or dynamic reconfigure -# add_dependencies(${PROJECT_NAME} ${${PROJECT_NAME}_EXPORTED_TARGETS} ${catkin_EXPORTED_TARGETS}) - -## Declare a C++ executable -## With catkin_make all packages are built within a single CMake context -## The recommended prefix ensures that target names across packages don't collide -# add_executable(${PROJECT_NAME}_node src/communication_node.cpp) - -add_executable(stm_communication src/stm_communication.cpp) -target_link_libraries(stm_communication ${catkin_LIBRARIES}) - -## Rename C++ executable without prefix -## The above recommended prefix causes long target names, the following renames the -## target back to the shorter version for ease of user use -## e.g. "rosrun someones_pkg node" instead of "rosrun someones_pkg someones_pkg_node" -# set_target_properties(${PROJECT_NAME}_node PROPERTIES OUTPUT_NAME node PREFIX "") - -## Add cmake target dependencies of the executable -## same as for the library above -# add_dependencies(${PROJECT_NAME}_node ${${PROJECT_NAME}_EXPORTED_TARGETS} ${catkin_EXPORTED_TARGETS}) - -## Specify libraries to link a library or executable target against -# target_link_libraries(${PROJECT_NAME}_node -# ${catkin_LIBRARIES} -# ) - -############# -## Install ## -############# - -# all install targets should use catkin DESTINATION variables -# See http://ros.org/doc/api/catkin/html/adv_user_guide/variables.html - -## Mark executable scripts (Python etc.) for installation -## in contrast to setup.py, you can choose the destination -# catkin_install_python(PROGRAMS -# scripts/my_python_script -# DESTINATION ${CATKIN_PACKAGE_BIN_DESTINATION} -# ) - -## Mark executables for installation -## See http://docs.ros.org/melodic/api/catkin/html/howto/format1/building_executables.html -# install(TARGETS ${PROJECT_NAME}_node -# RUNTIME DESTINATION ${CATKIN_PACKAGE_BIN_DESTINATION} -# ) - -## Mark libraries for installation -## See http://docs.ros.org/melodic/api/catkin/html/howto/format1/building_libraries.html -# install(TARGETS ${PROJECT_NAME} -# ARCHIVE DESTINATION ${CATKIN_PACKAGE_LIB_DESTINATION} -# LIBRARY DESTINATION ${CATKIN_PACKAGE_LIB_DESTINATION} -# RUNTIME DESTINATION ${CATKIN_GLOBAL_BIN_DESTINATION} -# ) - -## Mark cpp header files for installation -# install(DIRECTORY include/${PROJECT_NAME}/ -# DESTINATION ${CATKIN_PACKAGE_INCLUDE_DESTINATION} -# FILES_MATCHING PATTERN "*.h" -# PATTERN ".svn" EXCLUDE -# ) - -## Mark other files for installation (e.g. launch and bag files, etc.) -# install(FILES -# # myfile1 -# # myfile2 -# DESTINATION ${CATKIN_PACKAGE_SHARE_DESTINATION} -# ) - -############# -## Testing ## -############# - -## Add gtest based cpp test target and link libraries -# catkin_add_gtest(${PROJECT_NAME}-test test/test_communication.cpp) -# if(TARGET ${PROJECT_NAME}-test) -# target_link_libraries(${PROJECT_NAME}-test ${PROJECT_NAME}) -# endif() - -## Add folders to be run by python nosetests -# catkin_add_nosetests(test) diff --git a/src/communication/package.xml b/src/communication/package.xml deleted file mode 100644 index dbbe5a5..0000000 --- a/src/communication/package.xml +++ /dev/null @@ -1,68 +0,0 @@ - - - communication - 0.0.0 - The communication package - - - - - user - - - - - - TODO - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - catkin - roscpp - rospy - std_msgs - roscpp - rospy - std_msgs - roscpp - rospy - std_msgs - - - - - - - - diff --git a/src/communication/src/stm_communication.cpp b/src/communication/src/stm_communication.cpp deleted file mode 100644 index e6620b2..0000000 --- a/src/communication/src/stm_communication.cpp +++ /dev/null @@ -1,26 +0,0 @@ -//stm_communication.cpp -#include -#include "ros/ros.h" -#include - -using namespace std; - -int main(int argc, char** argv){ - ros::init(argc, argv, "stm_communication"); - ros::NodeHandle nh; - - ros::Publisher pub = nh.advertise("counting", 1); - - std_msgs::Int64 hello; - hello.data = 0; - - while(ros::ok()){ - pub.publish(hello); - hello.data++; - ROS_INFO("publish %d", hello.data); - ros::Duration(0.5).sleep(); - } - - ROS_INFO("== Terminate ! =="); - return 0; -} diff --git a/src/message/CMakeLists.txt b/src/message/CMakeLists.txt deleted file mode 100755 index 4de6791..0000000 --- a/src/message/CMakeLists.txt +++ /dev/null @@ -1,221 +0,0 @@ -cmake_minimum_required(VERSION 3.0.2) -project(message) - -## Compile as C++11, supported in ROS Kinetic and newer -# add_compile_options(-std=c++11) - -## Find catkin macros and libraries -## if COMPONENTS list like find_package(catkin REQUIRED COMPONENTS xyz) -## is used, also find other catkin packages -find_package(catkin REQUIRED COMPONENTS - geometry_msgs - roscpp - rospy - std_msgs - turtlesim - message_generation -) - -## System dependencies are found with CMake's conventions -# find_package(Boost REQUIRED COMPONENTS system) - - -## Uncomment this if the package has a setup.py. This macro ensures -## modules and global scripts declared therein get installed -## See http://ros.org/doc/api/catkin/html/user_guide/setup_dot_py.html -# catkin_python_setup() - -################################################ -## Declare ROS messages, services and actions ## -################################################ - -## To declare and build messages, services or actions from within this -## package, follow these steps: -## * Let MSG_DEP_SET be the set of packages whose message types you use in -## your messages/services/actions (e.g. std_msgs, actionlib_msgs, ...). -## * In the file package.xml: -## * add a build_depend tag for "message_generation" -## * add a build_depend and a exec_depend tag for each package in MSG_DEP_SET -## * If MSG_DEP_SET isn't empty the following dependency has been pulled in -## but can be declared for certainty nonetheless: -## * add a exec_depend tag for "message_runtime" -## * In this file (CMakeLists.txt): -## * add "message_generation" and every package in MSG_DEP_SET to -## find_package(catkin REQUIRED COMPONENTS ...) -## * add "message_runtime" and every package in MSG_DEP_SET to -## catkin_package(CATKIN_DEPENDS ...) -## * uncomment the add_*_files sections below as needed -## and list every .msg/.srv/.action file to be processed -## * uncomment the generate_messages entry below -## * add every package in MSG_DEP_SET to generate_messages(DEPENDENCIES ...) - -## Generate messages in the 'msg' folder -add_message_files( - FILES - example.msg -) - -## Generate actions in the 'action' folder -# add_action_files( -# FILES -# Action1.action -# Action2.action -# ) - -## Generate added messages and services with any dependencies listed here -generate_messages( - DEPENDENCIES - std_msgs -) - -################################################ -## Declare ROS dynamic reconfigure parameters ## -################################################ - -## To declare and build dynamic reconfigure parameters within this -## package, follow these steps: -## * In the file package.xml: -## * add a build_depend and a exec_depend tag for "dynamic_reconfigure" -## * In this file (CMakeLists.txt): -## * add "dynamic_reconfigure" to -## find_package(catkin REQUIRED COMPONENTS ...) -## * uncomment the "generate_dynamic_reconfigure_options" section below -## and list every .cfg file to be processed - -## Generate dynamic reconfigure parameters in the 'cfg' folder -# generate_dynamic_reconfigure_options( -# cfg/DynReconf1.cfg -# cfg/DynReconf2.cfg -# ) - -################################### -## catkin specific configuration ## -################################### -## The catkin_package macro generates cmake config files for your package -## Declare things to be passed to dependent projects -## INCLUDE_DIRS: uncomment this if your package contains header files -## LIBRARIES: libraries you create in this project that dependent projects also need -## CATKIN_DEPENDS: catkin_packages dependent projects also need -## DEPENDS: system dependencies of this project that dependent projects also need -catkin_package( -# INCLUDE_DIRS include -# LIBRARIES beginner_tutorials -# CATKIN_DEPENDS roscpp rospy std_msgs -# DEPENDS system_lib - CATKIN_DEPENDS roscpp rospy std_msgs message_runtime -) - -########### -## Build ## -########### - -## Specify additional locations of header files -## Your package locations should be listed before other locations -include_directories( -# include - ${catkin_INCLUDE_DIRS} -) - -## Declare a C++ library -# add_library(${PROJECT_NAME} -# src/${PROJECT_NAME}/message.cpp -# ) - -## Add cmake target dependencies of the library -## as an example, code may need to be generated before libraries -## either from message generation or dynamic reconfigure - -## Declare a C++ executable -## With catkin_make all packages are built within a single CMake context -## The recommended prefix ensures that target names across packages don't collide -# add_executable(${PROJECT_NAME}_node src/message_node.cpp) - -## Rename C++ executable without prefix -## The above recommended prefix causes long target names, the following renames the -## target back to the shorter version for ease of user use -## e.g. "rosrun someones_pkg node" instead of "rosrun someones_pkg someones_pkg_node" -# set_target_properties(${PROJECT_NAME}_node PROPERTIES OUTPUT_NAME node PREFIX "") - -## Add cmake target dependencies of the executable -## same as for the library above -# add_dependencies(${PROJECT_NAME}_node ${${PROJECT_NAME}_EXPORTED_TARGETS} ${catkin_EXPORTED_TARGETS}) -add_executable(day2_talker src/day2_talker.cpp) -target_link_libraries(day2_talker ${catkin_LIBRARIES}) -add_executable(day2_listener src/day2_listener.cpp) -target_link_libraries(day2_listener ${catkin_LIBRARIES}) -add_executable(turtle_homework src/turtle_homework.cpp) -target_link_libraries(turtle_homework ${catkin_LIBRARIES}) - -add_executable(day3_practice_talker src/day3_practice_talker.cpp) -target_link_libraries(day3_practice_talker ${catkin_LIBRARIES}) -add_dependencies(day3_practice_talker ${${PROJECT_NAME}_EXPORTED_TARGETS} ${catkin_EXPORTED_TARGETS}) - -add_executable(day3_practice_listener src/day3_practice_listener.cpp) -target_link_libraries(day3_practice_listener ${catkin_LIBRARIES}) -add_dependencies(day3_practice_listener ${${PROJECT_NAME}_EXPORTED_TARGETS} ${catkin_EXPORTED_TARGETS}) - -## Specify libraries to link a library or executable target against -# target_link_libraries(${PROJECT_NAME}_node -# ${catkin_LIBRARIES} -# ) - -############# -## Install ## -############# - -# all install targets should use catkin DESTINATION variables -# See http://ros.org/doc/api/catkin/html/adv_user_guide/variables.html - -## Mark executable scripts (Python etc.) for installation -## in contrast to setup.py, you can choose the destination -# catkin_install_python(PROGRAMS -# scripts/my_python_script -# DESTINATION ${CATKIN_PACKAGE_BIN_DESTINATION} -# ) - -install(PROGRAMS - scripts/talker.py - scripts/listener.py - DESTINATION ${CATKIN_PACKAGE_BIN_DESTINATION} -) - -## Mark executables for installation -## See http://docs.ros.org/melodic/api/catkin/html/howto/format1/building_executables.html -install(TARGETS day2_talker - RUNTIME DESTINATION ${CATKIN_PACKAGE_BIN_DESTINATION} -) - -## Mark libraries for installation -## See http://docs.ros.org/melodic/api/catkin/html/howto/format1/building_libraries.html -# install(TARGETS ${PROJECT_NAME} -# ARCHIVE DESTINATION ${CATKIN_PACKAGE_LIB_DESTINATION} -# LIBRARY DESTINATION ${CATKIN_PACKAGE_LIB_DESTINATION} -# RUNTIME DESTINATION ${CATKIN_GLOBAL_BIN_DESTINATION} -# ) - -## Mark cpp header files for installation -# install(DIRECTORY include/${PROJECT_NAME}/ -# DESTINATION ${CATKIN_PACKAGE_INCLUDE_DESTINATION} -# FILES_MATCHING PATTERN "*.h" -# PATTERN ".svn" EXCLUDE -# ) - -## Mark other files for installation (e.g. launch and bag files, etc.) -# install(FILES -# # myfile1 -# # myfile2 -# DESTINATION ${CATKIN_PACKAGE_SHARE_DESTINATION} -# ) - -############# -## Testing ## -############# - -## Add gtest based cpp test target and link libraries -# catkin_add_gtest(${PROJECT_NAME}-test test/test_message.cpp) -# if(TARGET ${PROJECT_NAME}-test) -# target_link_libraries(${PROJECT_NAME}-test ${PROJECT_NAME}) -# endif() - -## Add folders to be run by python nosetests -# catkin_add_nosetests(test) diff --git a/src/message/msg/example.msg b/src/message/msg/example.msg deleted file mode 100644 index 7ac72a8..0000000 --- a/src/message/msg/example.msg +++ /dev/null @@ -1,3 +0,0 @@ -int64 id -string name -uint16 age \ No newline at end of file diff --git a/src/message/package.xml b/src/message/package.xml deleted file mode 100755 index 3878571..0000000 --- a/src/message/package.xml +++ /dev/null @@ -1,74 +0,0 @@ - - - message - 0.0.0 - The message package - - - - - root - - - - - - TODO - - - - - - - - - - - - - - - - - - - - - - - - message_generation - - - - - - message_runtime - - - - - catkin - geometry_msgs - roscpp - rospy - std_msgs - turtlesim - geometry_msgs - roscpp - rospy - std_msgs - turtlesim - geometry_msgs - roscpp - rospy - std_msgs - turtlesim - - - - - - - - diff --git a/src/message/scripts/listener.py b/src/message/scripts/listener.py deleted file mode 100644 index 2c919ed..0000000 --- a/src/message/scripts/listener.py +++ /dev/null @@ -1,19 +0,0 @@ -#!/usr/bin/env python3 -#coding:utf-8 - -import rospy -from std_msgs.msg import Int64 - -def callbackfunc(msg): - rospy.loginfo("%d\n",msg.data) - -def subscriber(): - rospy.init_node('listener') - rospy.Subscriber('counter', Int64, callbackfunc) - rospy.spin() - -if __name__=='__main__': - try: - subscriber() - except rospy.ROSInterruptException: - pass diff --git a/src/message/scripts/talker.py b/src/message/scripts/talker.py deleted file mode 100644 index c1eb0d5..0000000 --- a/src/message/scripts/talker.py +++ /dev/null @@ -1,18 +0,0 @@ -# !/usr/bin/env python3 -# coding:utf-8 -import rospy -from std_msgs.msg import Int64 -def publisher(): - rospy.init_node('talker') - pub = rospy.Publisher('counter', Int64, queue_size = 10) - count = 0 - while not rospy.is_shutdown(): - pub.publish(count) - rospy.sleep(1) - count+=1 - -if __name__=='__main__': - try: - publisher() - except rospy.ROSInterruptException: - pass diff --git a/src/message/src/a.in b/src/message/src/a.in deleted file mode 100644 index c37dc67..0000000 --- a/src/message/src/a.in +++ /dev/null @@ -1,6 +0,0 @@ -114000730 Sean 20 -114000731 Sing 80 -114000732 Seen 64 -114000733 Sea 57 -114000734 Soon 3 -114000735 San 19 \ No newline at end of file diff --git a/src/message/src/day2_listener.cpp b/src/message/src/day2_listener.cpp deleted file mode 100644 index 85b231b..0000000 --- a/src/message/src/day2_listener.cpp +++ /dev/null @@ -1,22 +0,0 @@ -#include "ros/ros.h" -#include "std_msgs/Int64.h" - -void Callback(const std_msgs::Int64::ConstPtr& msg) -{ - std::cout<<"msg.data = "<data<<"\n"; -} - -int main(int argc, char **argv) -{ - ros::init(argc, argv, "listener"); - ros::NodeHandle nh; - ros::Subscriber sub = nh.subscribe("counter",10,Callback); - ros::Rate loop_rate(1); - - while(ros::ok()) - { - ros::spinOnce(); - loop_rate.sleep(); - } - return 0; -} diff --git a/src/message/src/day2_talker.cpp b/src/message/src/day2_talker.cpp deleted file mode 100644 index 4d94a5e..0000000 --- a/src/message/src/day2_talker.cpp +++ /dev/null @@ -1,24 +0,0 @@ -#include "ros/ros.h" -#include "std_msgs/Int64.h" - -int main(int argc, char **argv) -{ - ros::init(argc, argv, "talker"); - ros::NodeHandle nh; - ros::Publisher pub = nh.advertise("counter", 10); - ros::Rate loop_rate(1); - - int number = 0; - std_msgs::Int64 msg; - - while(ros::ok()) - { - msg.data = number; - // ROS_INFO("%ld", msg.data); - pub.publish(msg); - - number++; - loop_rate.sleep(); - } - return 0; -} diff --git a/src/message/src/day3_practice_listener.cpp b/src/message/src/day3_practice_listener.cpp deleted file mode 100644 index 67d9359..0000000 --- a/src/message/src/day3_practice_listener.cpp +++ /dev/null @@ -1,23 +0,0 @@ -#include "ros/ros.h" -#include "std_msgs/Int64.h" -#include "message/example.h" - -void Callback(const message::example::ConstPtr& msg) -{ - printf("(%ld, %s, %d)\n",msg->id,msg->name.c_str(),msg->age); -} - -int main(int argc, char **argv) -{ - ros::init(argc, argv, "listener"); - ros::NodeHandle nh; - ros::Subscriber sub = nh.subscribe("counter",10,Callback); - ros::Rate loop_rate(1); - - while(ros::ok()) - { - ros::spinOnce(); - loop_rate.sleep(); - } - return 0; -} diff --git a/src/message/src/day3_practice_talker.cpp b/src/message/src/day3_practice_talker.cpp deleted file mode 100644 index de3f19e..0000000 --- a/src/message/src/day3_practice_talker.cpp +++ /dev/null @@ -1,37 +0,0 @@ -#include "ros/ros.h" -#include "std_msgs/Int64.h" -#include "message/example.h" -#include - -typedef struct student{ - int id; - std::string name; - int age; -}st; - -int main(int argc, char **argv) -{ - ros::init(argc, argv, "talker"); - ros::NodeHandle nh; - ros::Publisher pub = nh.advertise("counter", 10); - ros::Rate loop_rate(1); - - message::example msg; - std::queue stlist; - st tmp; - while(std::cin>>tmp.id>>tmp.name>>tmp.age){ - stlist.push(tmp); - } - while(ros::ok() && !stlist.empty()) - { - msg.age = stlist.front().age; - msg.name = stlist.front().name; - msg.id = stlist.front().id; - stlist.pop(); - // ROS_INFO("%ld", msg.data); - pub.publish(msg); - - loop_rate.sleep(); - } - return 0; -} diff --git a/src/message/src/turtle_homework.cpp b/src/message/src/turtle_homework.cpp deleted file mode 100644 index 5fdd40a..0000000 --- a/src/message/src/turtle_homework.cpp +++ /dev/null @@ -1,73 +0,0 @@ -#include -#include -#include - - -turtlesim::Pose current_pose; - -void callback(const turtlesim::Pose::ConstPtr& pose_data) -{ - std::cout<<"current pose = "<<*pose_data<<"\n"; - current_pose = *pose_data; -} - -int main(int argc, char **argv) -{ - ros::init(argc,argv,"move_control"); - ros::NodeHandle nh; - ros::Publisher vel_pub = nh.advertise("/turtle1/cmd_vel",1); - ros::Subscriber pose_sub = nh.subscribe("/turtle1/pose",10,callback); - ros::Rate rate(10); - geometry_msgs::Twist vel_msg; - - ros::Rate loop_rate(10); - while(ros::ok() && current_pose.x<= 9.5) - { - vel_msg.linear.x = 2; - vel_msg.angular.z = 0.0; - vel_pub.publish(vel_msg); - ros::spinOnce(); - loop_rate.sleep(); - } - while(ros::ok() && current_pose.x<= 10) - { - vel_msg.linear.x = 0.1; - vel_msg.angular.z = 0.0; - vel_pub.publish(vel_msg); - ros::spinOnce(); - loop_rate.sleep(); - } - while(ros::ok() && current_pose.theta>=-1.4) - { - vel_msg.linear.x = 0.0; - vel_msg.angular.z = -0.5; - vel_pub.publish(vel_msg); - ros::spinOnce(); - loop_rate.sleep(); - } - while(ros::ok() && current_pose.theta>=-1.57079) - { - vel_msg.linear.x = 0.0; - vel_msg.angular.z = -0.02; - vel_pub.publish(vel_msg); - ros::spinOnce(); - loop_rate.sleep(); - } - while(ros::ok() && current_pose.y>=0.5) - { - vel_msg.linear.x = 2; - vel_msg.angular.z = 0.0; - vel_pub.publish(vel_msg); - ros::spinOnce(); - loop_rate.sleep(); - } - while(ros::ok() && current_pose.y>0) - { - vel_msg.linear.x = 0.1; - vel_msg.angular.z = 0.0; - vel_pub.publish(vel_msg); - ros::spinOnce(); - loop_rate.sleep(); - } - return 0; -} \ No newline at end of file diff --git a/src/oop/CMakeLists.txt b/src/oop/CMakeLists.txt deleted file mode 100755 index b3d306b..0000000 --- a/src/oop/CMakeLists.txt +++ /dev/null @@ -1,216 +0,0 @@ -cmake_minimum_required(VERSION 3.0.2) -project(oop) - -## Compile as C++11, supported in ROS Kinetic and newer -# add_compile_options(-std=c++11) - -## Find catkin macros and libraries -## if COMPONENTS list like find_package(catkin REQUIRED COMPONENTS xyz) -## is used, also find other catkin packages -find_package(catkin REQUIRED COMPONENTS - roscpp - rospy - std_msgs -) - -## System dependencies are found with CMake's conventions -# find_package(Boost REQUIRED COMPONENTS system) - - -## Uncomment this if the package has a setup.py. This macro ensures -## modules and global scripts declared therein get installed -## See http://ros.org/doc/api/catkin/html/user_guide/setup_dot_py.html -# catkin_python_setup() - -################################################ -## Declare ROS messages, services and actions ## -################################################ - -## To declare and build messages, services or actions from within this -## package, follow these steps: -## * Let MSG_DEP_SET be the set of packages whose message types you use in -## your messages/services/actions (e.g. std_msgs, actionlib_msgs, ...). -## * In the file package.xml: -## * add a build_depend tag for "message_generation" -## * add a build_depend and a exec_depend tag for each package in MSG_DEP_SET -## * If MSG_DEP_SET isn't empty the following dependency has been pulled in -## but can be declared for certainty nonetheless: -## * add a exec_depend tag for "message_runtime" -## * In this file (CMakeLists.txt): -## * add "message_generation" and every package in MSG_DEP_SET to -## find_package(catkin REQUIRED COMPONENTS ...) -## * add "message_runtime" and every package in MSG_DEP_SET to -## catkin_package(CATKIN_DEPENDS ...) -## * uncomment the add_*_files sections below as needed -## and list every .msg/.srv/.action file to be processed -## * uncomment the generate_messages entry below -## * add every package in MSG_DEP_SET to generate_messages(DEPENDENCIES ...) - -## Generate messages in the 'msg' folder -# add_message_files( -# FILES -# Message1.msg -# Message2.msg -# ) - -## Generate services in the 'srv' folder -# add_service_files( -# FILES -# Service1.srv -# Service2.srv -# ) - -## Generate actions in the 'action' folder -# add_action_files( -# FILES -# Action1.action -# Action2.action -# ) - -## Generate added messages and services with any dependencies listed here -# generate_messages( -# DEPENDENCIES -# std_msgs -# ) - -################################################ -## Declare ROS dynamic reconfigure parameters ## -################################################ - -## To declare and build dynamic reconfigure parameters within this -## package, follow these steps: -## * In the file package.xml: -## * add a build_depend and a exec_depend tag for "dynamic_reconfigure" -## * In this file (CMakeLists.txt): -## * add "dynamic_reconfigure" to -## find_package(catkin REQUIRED COMPONENTS ...) -## * uncomment the "generate_dynamic_reconfigure_options" section below -## and list every .cfg file to be processed - -## Generate dynamic reconfigure parameters in the 'cfg' folder -# generate_dynamic_reconfigure_options( -# cfg/DynReconf1.cfg -# cfg/DynReconf2.cfg -# ) - -################################### -## catkin specific configuration ## -################################### -## The catkin_package macro generates cmake config files for your package -## Declare things to be passed to dependent projects -## INCLUDE_DIRS: uncomment this if your package contains header files -## LIBRARIES: libraries you create in this project that dependent projects also need -## CATKIN_DEPENDS: catkin_packages dependent projects also need -## DEPENDS: system dependencies of this project that dependent projects also need -catkin_package( -# INCLUDE_DIRS include -# LIBRARIES oop -# CATKIN_DEPENDS roscpp rospy std_msgs -# DEPENDS system_lib -) - -########### -## Build ## -########### - -## Specify additional locations of header files -## Your package locations should be listed before other locations -include_directories( - include - ${catkin_INCLUDE_DIRS} -) -add_library(chassis src/chassis.cpp) - -## Declare a C++ library -# add_library(${PROJECT_NAME} -# src/${PROJECT_NAME}/oop.cpp -# ) - -## Add cmake target dependencies of the library -## as an example, code may need to be generated before libraries -## either from message generation or dynamic reconfigure -# add_dependencies(${PROJECT_NAME} ${${PROJECT_NAME}_EXPORTED_TARGETS} ${catkin_EXPORTED_TARGETS}) - -## Declare a C++ executable -## With catkin_make all packages are built within a single CMake context -## The recommended prefix ensures that target names across packages don't collide -# add_executable(${PROJECT_NAME}_node src/oop_node.cpp) - -add_executable(oop_ex1 src/oop_ex1.cpp) -target_link_libraries(oop_ex1 ${catkin_LIBRARIES}) - -add_executable(oop_ex2 src/oop_ex2.cpp) -target_link_libraries(oop_ex2 ${catkin_LIBRARIES}) - -add_executable(oop_ex3 src/oop_ex3.cpp) -target_link_libraries(oop_ex3 ${catkin_LIBRARIES}) - -## Rename C++ executable without prefix -## The above recommended prefix causes long target names, the following renames the -## target back to the shorter version for ease of user use -## e.g. "rosrun someones_pkg node" instead of "rosrun someones_pkg someones_pkg_node" -# set_target_properties(${PROJECT_NAME}_node PROPERTIES OUTPUT_NAME node PREFIX "") - -## Add cmake target dependencies of the executable -## same as for the library above -# add_dependencies(${PROJECT_NAME}_node ${${PROJECT_NAME}_EXPORTED_TARGETS} ${catkin_EXPORTED_TARGETS}) - -## Specify libraries to link a library or executable target against -# target_link_libraries(${PROJECT_NAME}_node -# ${catkin_LIBRARIES} -# ) - -############# -## Install ## -############# - -# all install targets should use catkin DESTINATION variables -# See http://ros.org/doc/api/catkin/html/adv_user_guide/variables.html - -## Mark executable scripts (Python etc.) for installation -## in contrast to setup.py, you can choose the destination -# catkin_install_python(PROGRAMS -# scripts/my_python_script -# DESTINATION ${CATKIN_PACKAGE_BIN_DESTINATION} -# ) - -## Mark executables for installation -## See http://docs.ros.org/melodic/api/catkin/html/howto/format1/building_executables.html -# install(TARGETS ${PROJECT_NAME}_node -# RUNTIME DESTINATION ${CATKIN_PACKAGE_BIN_DESTINATION} -# ) - -## Mark libraries for installation -## See http://docs.ros.org/melodic/api/catkin/html/howto/format1/building_libraries.html -# install(TARGETS ${PROJECT_NAME} -# ARCHIVE DESTINATION ${CATKIN_PACKAGE_LIB_DESTINATION} -# LIBRARY DESTINATION ${CATKIN_PACKAGE_LIB_DESTINATION} -# RUNTIME DESTINATION ${CATKIN_GLOBAL_BIN_DESTINATION} -# ) - -## Mark cpp header files for installation -# install(DIRECTORY include/${PROJECT_NAME}/ -# DESTINATION ${CATKIN_PACKAGE_INCLUDE_DESTINATION} -# FILES_MATCHING PATTERN "*.h" -# PATTERN ".svn" EXCLUDE -# ) - -## Mark other files for installation (e.g. launch and bag files, etc.) -# install(FILES -# # myfile1 -# # myfile2 -# DESTINATION ${CATKIN_PACKAGE_SHARE_DESTINATION} -# ) - -############# -## Testing ## -############# - -## Add gtest based cpp test target and link libraries -# catkin_add_gtest(${PROJECT_NAME}-test test/test_oop.cpp) -# if(TARGET ${PROJECT_NAME}-test) -# target_link_libraries(${PROJECT_NAME}-test ${PROJECT_NAME}) -# endif() - -## Add folders to be run by python nosetests -# catkin_add_nosetests(test) diff --git a/src/oop/include/chassis.h b/src/oop/include/chassis.h deleted file mode 100755 index dd648b0..0000000 --- a/src/oop/include/chassis.h +++ /dev/null @@ -1,27 +0,0 @@ -#include -#include - -#ifndef CHASSIS_H -#define CHASSIS_H - -class Chassis{ - public: - Chassis(float x_0, float y_0, float theta_0, float v_wheels_0[2]); - void forwardKinematics(); - void poseUpdate(); - void output(); - - private: - float x; - float y; - float theta; - float v_x=0.0; - float v_y=0.0; - float omega=0.0; - float v_wheels[2]={0.0, 0.0}; - float radius=100; - float dt=0.01; - float time=0.0; -}; - -#endif \ No newline at end of file diff --git a/src/oop/package.xml b/src/oop/package.xml deleted file mode 100755 index f4911e8..0000000 --- a/src/oop/package.xml +++ /dev/null @@ -1,68 +0,0 @@ - - - oop - 0.0.0 - The oop package - - - - - user - - - - - - TODO - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - catkin - roscpp - rospy - std_msgs - roscpp - rospy - std_msgs - roscpp - rospy - std_msgs - - - - - - - - diff --git a/src/oop/src/chassis.cpp b/src/oop/src/chassis.cpp deleted file mode 100755 index b1621bb..0000000 --- a/src/oop/src/chassis.cpp +++ /dev/null @@ -1,25 +0,0 @@ -#include "chassis.h" - -Chassis::Chassis(float x_0, float y_0, float theta_0, float v_wheels_0[2]) - :x(x_0), y(y_0), theta(theta_0){ - v_wheels[0]=v_wheels_0[0]; - v_wheels[1]=v_wheels_0[1]; - } - -void Chassis::forwardKinematics(){ - v_x=(v_wheels[0]+v_wheels[1])*cos(theta)/2; - v_y=(v_wheels[0]+v_wheels[1])*sin(theta)/2; - omega=(v_wheels[0]+v_wheels[1])/(radius*2); - } - -void Chassis::poseUpdate(){ - time+=dt; - x+=v_x*dt; - y+=v_y*dt; - theta+=omega*dt; - } - -void Chassis::output(){ - std::cout<<"at "< -#include - -class Chassis{ - public: - Chassis(float x_0, float y_0, float theta_0, float v_wheels_0[2]) - :x(x_0), y(y_0), theta(theta_0){ - v_wheels[0]=v_wheels_0[0]; - v_wheels[1]=v_wheels_0[1]; - } - - void forwardKinematics(){ - v_x=(v_wheels[0]+v_wheels[1])*cos(theta)/2; - v_y=(v_wheels[0]+v_wheels[1])*sin(theta)/2; - omega=(v_wheels[0]+v_wheels[1])/(radius*2); - } - - void poseUpdate(){ - time+=dt; - x+=v_x*dt; - y+=v_y*dt; - theta+=omega*dt; - } - void output(){ - std::cout<<"at "< -#include - -class Chassis{ - public: - Chassis(float x_0, float y_0, float theta_0) - :x(x_0), y(y_0), theta(theta_0){} - - virtual void forwardKinematics()=0; - - void poseUpdate(){ - time+=dt; - x+=v_x*dt; - y+=v_y*dt; - theta+=omega*dt; - } - void output(){ - std::cout<<"at "<`_) -* Contributors: Mike Purvis - -0.8.0 (2018-10-11) ------------------- - -0.7.7 (2017-11-29) ------------------- -* Fix catkin lint errors (`#296 `_) -* Contributors: Bei Chen Liu - -0.7.6 (2017-03-01) ------------------- - -0.7.5 (2016-11-22) ------------------- - -0.7.4 (2016-09-21) ------------------- - -0.7.3 (2016-08-05) ------------------- - -0.7.2 (2016-07-15) ------------------- - -0.7.1 (2015-07-06) ------------------- - -0.7.0 (2015-04-23) ------------------- - -0.6.3 (2014-11-05) ------------------- - -0.6.2 (2014-09-10) ------------------- - -0.6.1 (2014-06-30) ------------------- - -0.6.0 (2014-06-11) ------------------- -* Uncomment ID_TX_STOP constant, per `#111 `_ -* Contributors: Mike Purvis - -0.5.6 (2014-06-11) ------------------- -* Add Mike Purvis as maintainer to all but xbee. -* remove ID_TX_STOP from rosserial_msgs/msg/TopicInfo.msg, using hardcode modification. fix the dupilcated registration problem of subscriber -* modified rosserial -* Contributors: Mike Purvis, Moju Zhao - -0.5.5 (2014-01-14) ------------------- - -0.5.4 (2013-10-17) ------------------- - -0.5.3 (2013-09-21) ------------------- -* Add message info service and stub of node to provide it, for -rosserial_server support. - -0.5.2 (2013-07-17) ------------------- - -* Fix release version - -0.5.1 (2013-07-15) ------------------- - -0.4.5 (2013-07-02) ------------------- - -0.4.4 (2013-03-20) ------------------- - -0.4.3 (2013-03-13 14:08) ------------------------- - -0.4.2 (2013-03-13 01:15) ------------------------- - -0.4.1 (2013-03-09) ------------------- - -0.4.0 (2013-03-08) ------------------- -* Changed DEBUG log level to ROSDEBUG. -* initial catkin version on github diff --git a/src/rosserial_msgs/CMakeLists.txt b/src/rosserial_msgs/CMakeLists.txt deleted file mode 100644 index c525612..0000000 --- a/src/rosserial_msgs/CMakeLists.txt +++ /dev/null @@ -1,21 +0,0 @@ -cmake_minimum_required(VERSION 3.7.2) -project(rosserial_msgs) - -find_package(catkin REQUIRED COMPONENTS - message_generation -) - -add_message_files(FILES - Log.msg - TopicInfo.msg -) - -add_service_files(FILES - RequestParam.srv -) - -generate_messages() - -catkin_package(CATKIN_DEPENDS - message_runtime -) diff --git a/src/rosserial_msgs/msg/Log.msg b/src/rosserial_msgs/msg/Log.msg deleted file mode 100644 index 7239876..0000000 --- a/src/rosserial_msgs/msg/Log.msg +++ /dev/null @@ -1,10 +0,0 @@ - -#ROS Logging Levels -uint8 ROSDEBUG=0 -uint8 INFO=1 -uint8 WARN=2 -uint8 ERROR=3 -uint8 FATAL=4 - -uint8 level -string msg diff --git a/src/rosserial_msgs/msg/TopicInfo.msg b/src/rosserial_msgs/msg/TopicInfo.msg deleted file mode 100644 index dafd6b0..0000000 --- a/src/rosserial_msgs/msg/TopicInfo.msg +++ /dev/null @@ -1,21 +0,0 @@ -# special topic_ids -uint16 ID_PUBLISHER=0 -uint16 ID_SUBSCRIBER=1 -uint16 ID_SERVICE_SERVER=2 -uint16 ID_SERVICE_CLIENT=4 -uint16 ID_PARAMETER_REQUEST=6 -uint16 ID_LOG=7 -uint16 ID_TIME=10 -uint16 ID_TX_STOP=11 - -# The endpoint ID for this topic -uint16 topic_id - -string topic_name -string message_type - -# MD5 checksum for this message type -string md5sum - -# size of the buffer message must fit in -int32 buffer_size diff --git a/src/rosserial_msgs/package.xml b/src/rosserial_msgs/package.xml deleted file mode 100644 index 9f8ac6d..0000000 --- a/src/rosserial_msgs/package.xml +++ /dev/null @@ -1,18 +0,0 @@ - - rosserial_msgs - 0.9.2 - - Messages for automatic topic configuration using rosserial. - - Michael Ferguson - Paul Bouchier - Mike Purvis - BSD - http://ros.org/wiki/rosserial_msgs - - catkin - - message_generation - - message_runtime - diff --git a/src/rosserial_msgs/srv/RequestParam.srv b/src/rosserial_msgs/srv/RequestParam.srv deleted file mode 100644 index ca605e8..0000000 --- a/src/rosserial_msgs/srv/RequestParam.srv +++ /dev/null @@ -1,7 +0,0 @@ -string name - ---- - -int32[] ints -float32[] floats -string[] strings diff --git a/src/rosserial_python/CHANGELOG.rst b/src/rosserial_python/CHANGELOG.rst deleted file mode 100644 index 28a2bf3..0000000 --- a/src/rosserial_python/CHANGELOG.rst +++ /dev/null @@ -1,175 +0,0 @@ -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -Changelog for package rosserial_python -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -0.9.2 (2021-04-02) ------------------- -* Adds a shutdown hook for sending txStopRequest (`#551 `_) -* Gracefully handle socket server end. (`#550 `_) -* Contributors: Can Altineller, Karl Kangur - -0.9.1 (2020-09-09) ------------------- -* rosserial_python py3 fix (`#514 `_) -* Contributors: BriceRenaudeau - -0.9.0 (2020-08-25) ------------------- -* Python 3 and GCC7+ fixes (`#508 `_) - * Port of rosserial_python to py3. - * Throw from inside the BrokenPipeError. -* Fix Travis for Noetic + Python 3 -* Bump minimum CMake version to 3.7.2 (Melodic). -* Update pyserial rosdep. -* Use time.sleep instead of rospy.sleep. (`#489 `_) -* Make deprecation message a warning and more specific (`#479 `_) -* Properly initialize message_info stub, drop from test. -* Fix py3 print usages and trailing whitespaces (`#469 `_) -* Drop separate node for message service (`#446 `_) -* Fix reconnection of rosserial-python (`#445 `_) -* Contributors: Asuki Kono, Daisuke Sato, Hermann von Kleist, Hikaru Sugiura, Mike Purvis, acxz - -0.8.0 (2018-10-11) ------------------- -* fix no attribute message_cache issue in message_info_service (`#393 `_) -* Added service to force an Arduino hard reset in serial_node.py (`#349 `_) - * Added hard_reset service call to serial_node - * Refactored SerialClient to use a write thread, working around deadlock when both Arduino and serial_node.py get stuck writing to each other. - * Updated cmakelists and package.xml to include dependencies. Removed unnecessary tcp functionality from arduino-specific serial_node.py -* Add support for boolean parameters (`#355 `_) -* [python] fix an unboundlocalerror (`#346 `_) -* Retry opening the serial port every 3 seconds (`#342 `_) - * Retry opening the serial port every 3 seconds - * Break out of the retry loop if we've been shut down -* Contributors: Chris Spencer, Kenta Yonekura, Pikrass, dlguo-cpr - -0.7.7 (2017-11-29) ------------------- -* Fix catkin lint errors (`#296 `_) -* pyserial bug workaround to fix rosserial_test test against SerialClient.py (`#313 `_) -* Add requestTopics to correct publish before setup (`#308 `_) -* Contributors: Bei Chen Liu, Tom O'Connell - -0.7.6 (2017-03-01) ------------------- -* Fix typo in serial error message (`#253 `_) -* Contributors: Jonathan Binney - -0.7.5 (2016-11-22) ------------------- - -0.7.4 (2016-09-21) ------------------- -* Try to read more serial bytes in a loop (`#248 `_) -* Add missing "import errno" to rosserial_python -* Integration tests for rosserial (`#243 `_) -* rosserial_python tcp server allowing socket address reuse (`#242 `_) -* Contributors: Mike Purvis, Vitor Matos, davidshin172 - -0.7.3 (2016-08-05) ------------------- - -0.7.2 (2016-07-15) ------------------- - -0.7.1 (2015-07-06) ------------------- - -0.7.0 (2015-04-23) ------------------- -* Adds default queue_size of 10 for rosserial_python publisher. -* Fixed queue size warning with diagnostics publisher. -* We don't need roslib.load_manifest any more under catkin. -* Contributors: Basheer Subei, David Lavoie-Boutin, Mike Purvis, eisoku9618 - -0.6.3 (2014-11-05) ------------------- - -0.6.2 (2014-09-10) ------------------- -* Added MD5 verification for request and response messags upon ServiceClient registration. -* Enabled registration of service clients -* Contributors: Jonathan Jekir - -0.6.1 (2014-06-30) ------------------- - -0.6.0 (2014-06-11) ------------------- - -0.5.6 (2014-06-11) ------------------- -* Add Mike Purvis as maintainer to all but xbee. -* Added the missing inWaiting() to RosSerialServer -* improvement: inform user of mismatched checksum for topic_id and msg -* Fix indent on if-block. -* Check for data in buffer before attempting to tryRead. Insert a 1ms sleep to avoid pegging the processor. -* Better warning message for tryRead. -* fix two points: 1. the number of bytes to read for chk_byte, 2. the wrong indentation about the defination of sendDiagnostics() -* Try-block to handle IOErrors thrown from tryRead -* Merge from hydro-devel. -* fix the dupilcated registration problem of subscriber -* remove ID_TX_STOP from rosserial_msgs/msg/TopicInfo.msg, using hardcode modification. fix the dupilcated registration problem of subscriber -* modified rosserial -* modified rosserial -* Contributors: Girts Linde, Mike Purvis, Moju Zhao, bakui, denis - -0.5.5 (2014-01-14) ------------------- - -0.5.4 (2013-10-17) ------------------- - -0.5.3 (2013-09-21) ------------------- -* De-register subscribers and service clients upon disconnect. - This prevents callbacks being called after a client program - terminates a connection. -* Fill out package.xml properly, include docstring in helper Python node. -* Add message info helper script that supports rosserial_server - -0.5.2 (2013-07-17) ------------------- - -* Fix release version - -0.5.1 (2013-07-15) ------------------- -* Merge branch 'rosserial_bakui' of git://github.com/tongtybj/rosserial into tongtybj-rosserial_bakui - * Modified the frame structure for serial communication, particularly add the checksum for msg_len -* Incorporate protocol version in message. Try to detect protocol version mismatch and give appropriate hints. - -0.4.5 (2013-07-02) ------------------- -* Fix SeviceServer member names in error message - 'm' prefix was omitted, causing an exception while trying to print - an error about md5 mismatches. Fix this to allow the error to be - presented to the user. -* Allow service calls with empty requests - std_srvs::Empty has a request message of size zero. SerialClient.send - returns the size of the sent message, which is checked to ensure - data crossed the serial line. Accommodate services with empty requests - by modifying the check to acknowledge all transmissions of zero or - more bytes as valid. -* revert name of node, add a few comments/spacing -* fix private parameters - temporary fix breaks fork_server for tcp -* Fix `#35 `_ - -0.4.4 (2013-03-20) ------------------- -* Fixed "Lost sync" message at initial connection that happens on both Arduino & - embeddedLinux. Problem was last_sync initialized to epoch and compared against - Time.now() always times out on first compare. - -0.4.3 (2013-03-13 14:08) ------------------------- - -0.4.2 (2013-03-13 01:15) ------------------------- - -0.4.1 (2013-03-09) ------------------- - -0.4.0 (2013-03-08) ------------------- -* initial catkin version on github diff --git a/src/rosserial_python/CMakeLists.txt b/src/rosserial_python/CMakeLists.txt deleted file mode 100644 index ce8cb35..0000000 --- a/src/rosserial_python/CMakeLists.txt +++ /dev/null @@ -1,12 +0,0 @@ -cmake_minimum_required(VERSION 3.7.2) -project(rosserial_python) - -find_package(catkin REQUIRED) -catkin_package() - -catkin_python_setup() - -catkin_install_python( - PROGRAMS nodes/message_info_service.py nodes/serial_node.py - DESTINATION ${CATKIN_PACKAGE_BIN_DESTINATION} -) diff --git a/src/rosserial_python/nodes/message_info_service.py b/src/rosserial_python/nodes/message_info_service.py deleted file mode 100644 index 2d21148..0000000 --- a/src/rosserial_python/nodes/message_info_service.py +++ /dev/null @@ -1,47 +0,0 @@ -#!/usr/bin/env python - -##################################################################### -# Software License Agreement (BSD License) -# -# Copyright (c) 2013, Clearpath Robotics -# All rights reserved. -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions -# are met: -# -# * Redistributions of source code must retain the above copyright -# notice, this list of conditions and the following disclaimer. -# * Redistributions in binary form must reproduce the above -# copyright notice, this list of conditions and the following -# disclaimer in the documentation and/or other materials provided -# with the distribution. -# * Neither the name of Willow Garage, Inc. nor the names of its -# contributors may be used to endorse or promote products derived -# from this software without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS -# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE -# COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, -# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, -# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; -# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT -# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN -# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE -# POSSIBILITY OF SUCH DAMAGE. - -""" This functionality is no longer needed, as it's now handled - by accessing the Python interpreter directly from rosserial_server. - The file remains to avoid breaking launch files which include it.""" - -import rospy - - -if __name__=="__main__": - rospy.init_node('message_info_service') - rospy.logwarn("The message info service node is no longer needed and will be " - "removed in a future release of rosserial.") - rospy.spin() diff --git a/src/rosserial_python/nodes/serial_node.py b/src/rosserial_python/nodes/serial_node.py deleted file mode 100644 index ddbcf83..0000000 --- a/src/rosserial_python/nodes/serial_node.py +++ /dev/null @@ -1,109 +0,0 @@ -#!/usr/bin/env python - -##################################################################### -# Software License Agreement (BSD License) -# -# Copyright (c) 2011, Willow Garage, Inc. -# All rights reserved. -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions -# are met: -# -# * Redistributions of source code must retain the above copyright -# notice, this list of conditions and the following disclaimer. -# * Redistributions in binary form must reproduce the above -# copyright notice, this list of conditions and the following -# disclaimer in the documentation and/or other materials provided -# with the distribution. -# * Neither the name of Willow Garage, Inc. nor the names of its -# contributors may be used to endorse or promote products derived -# from this software without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS -# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE -# COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, -# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, -# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; -# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT -# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN -# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE -# POSSIBILITY OF SUCH DAMAGE. - -__author__ = "mferguson@willowgarage.com (Michael Ferguson)" - -import rospy -from rosserial_python import SerialClient, RosSerialServer -from serial import SerialException -from time import sleep -import multiprocessing - -import sys - -if __name__=="__main__": - rospy.init_node("serial_node") - rospy.loginfo("ROS Serial Python Node") - - port_name = rospy.get_param('~port','/dev/ttyUSB0') - baud = int(rospy.get_param('~baud','57600')) - - # for systems where pyserial yields errors in the fcntl.ioctl(self.fd, TIOCMBIS, \ - # TIOCM_DTR_str) line, which causes an IOError, when using simulated port - fix_pyserial_for_test = rospy.get_param('~fix_pyserial_for_test', False) - - # Allows for assigning local parameters for tcp_port and fork_server with - # global parameters as fallback to prevent breaking changes - if(rospy.has_param('~tcp_port')): - tcp_portnum = int(rospy.get_param('~tcp_port')) - else: - tcp_portnum = int(rospy.get_param('/rosserial_embeddedlinux/tcp_port', '11411')) - - if(rospy.has_param('~fork_server')): - fork_server = rospy.get_param('~fork_server') - else: - fork_server = rospy.get_param('/rosserial_embeddedlinux/fork_server', False) - - # TODO: do we really want command line params in addition to parameter server params? - sys.argv = rospy.myargv(argv=sys.argv) - if len(sys.argv) >= 2 : - port_name = sys.argv[1] - if len(sys.argv) == 3 : - tcp_portnum = int(sys.argv[2]) - - if port_name == "tcp" : - server = RosSerialServer(tcp_portnum, fork_server) - rospy.loginfo("Waiting for socket connections on port %d" % tcp_portnum) - try: - server.listen() - except KeyboardInterrupt: - rospy.loginfo("got keyboard interrupt") - finally: - rospy.loginfo("Shutting down") - for process in multiprocessing.active_children(): - rospy.loginfo("Shutting down process %r", process) - process.terminate() - process.join() - rospy.loginfo("All done") - - else : # Use serial port - while not rospy.is_shutdown(): - rospy.loginfo("Connecting to %s at %d baud" % (port_name,baud) ) - try: - client = SerialClient(port_name, baud, fix_pyserial_for_test=fix_pyserial_for_test) - client.run() - except KeyboardInterrupt: - break - except SerialException: - sleep(1.0) - continue - except OSError: - sleep(1.0) - continue - except: - rospy.logwarn("Unexpected Error: %s", sys.exc_info()[0]) - client.port.close() - sleep(1.0) - continue diff --git a/src/rosserial_python/package.xml b/src/rosserial_python/package.xml deleted file mode 100644 index e5a4f98..0000000 --- a/src/rosserial_python/package.xml +++ /dev/null @@ -1,19 +0,0 @@ - - rosserial_python - 0.9.2 - - A Python-based implementation of the rosserial protocol. - - Michael Ferguson - Paul Bouchier - Mike Purvis - BSD - http://ros.org/wiki/rosserial_python - - catkin - - rospy - rosserial_msgs - diagnostic_msgs - python3-serial - diff --git a/src/rosserial_python/setup.py b/src/rosserial_python/setup.py deleted file mode 100644 index 1376939..0000000 --- a/src/rosserial_python/setup.py +++ /dev/null @@ -1,11 +0,0 @@ -#!/usr/bin/env python - -from distutils.core import setup -from catkin_pkg.python_setup import generate_distutils_setup - -d = generate_distutils_setup( - packages=['rosserial_python'], - package_dir={'': 'src'}, - ) - -setup(**d) diff --git a/src/rosserial_python/src/rosserial_python/SerialClient.py b/src/rosserial_python/src/rosserial_python/SerialClient.py deleted file mode 100644 index 6b1b875..0000000 --- a/src/rosserial_python/src/rosserial_python/SerialClient.py +++ /dev/null @@ -1,821 +0,0 @@ -##################################################################### -# Software License Agreement (BSD License) -# -# Copyright (c) 2011, Willow Garage, Inc. -# All rights reserved. -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions -# are met: -# -# * Redistributions of source code must retain the above copyright -# notice, this list of conditions and the following disclaimer. -# * Redistributions in binary form must reproduce the above -# copyright notice, this list of conditions and the following -# disclaimer in the documentation and/or other materials provided -# with the distribution. -# * Neither the name of Willow Garage, Inc. nor the names of its -# contributors may be used to endorse or promote products derived -# from this software without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS -# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE -# COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, -# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, -# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; -# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT -# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN -# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE -# POSSIBILITY OF SUCH DAMAGE. - -__author__ = "mferguson@willowgarage.com (Michael Ferguson)" - -import array -import errno -import imp -import io -import multiprocessing -import queue -import socket -import struct -import sys -import threading -import time - -from serial import Serial, SerialException, SerialTimeoutException - -import roslib -import rospy -from std_msgs.msg import Time -from rosserial_msgs.msg import TopicInfo, Log -from rosserial_msgs.srv import RequestParamRequest, RequestParamResponse - -import diagnostic_msgs.msg - -ERROR_MISMATCHED_PROTOCOL = "Mismatched protocol version in packet: lost sync or rosserial_python is from different ros release than the rosserial client" -ERROR_NO_SYNC = "no sync with device" -ERROR_PACKET_FAILED = "Packet Failed : Failed to read msg data" - -def load_pkg_module(package, directory): - #check if its in the python path - path = sys.path - try: - imp.find_module(package) - except ImportError: - roslib.load_manifest(package) - try: - m = __import__( package + '.' + directory ) - except ImportError: - rospy.logerr( "Cannot import package : %s"% package ) - rospy.logerr( "sys.path was " + str(path) ) - return None - return m - -def load_message(package, message): - m = load_pkg_module(package, 'msg') - m2 = getattr(m, 'msg') - return getattr(m2, message) - -def load_service(package,service): - s = load_pkg_module(package, 'srv') - s = getattr(s, 'srv') - srv = getattr(s, service) - mreq = getattr(s, service+"Request") - mres = getattr(s, service+"Response") - return srv,mreq,mres - -class Publisher: - """ - Publisher forwards messages from the serial device to ROS. - """ - def __init__(self, topic_info): - """ Create a new publisher. """ - self.topic = topic_info.topic_name - - # find message type - package, message = topic_info.message_type.split('/') - self.message = load_message(package, message) - if self.message._md5sum == topic_info.md5sum: - self.publisher = rospy.Publisher(self.topic, self.message, queue_size=10) - else: - raise Exception('Checksum does not match: ' + self.message._md5sum + ',' + topic_info.md5sum) - - def handlePacket(self, data): - """ Forward message to ROS network. """ - m = self.message() - m.deserialize(data) - self.publisher.publish(m) - - -class Subscriber: - """ - Subscriber forwards messages from ROS to the serial device. - """ - - def __init__(self, topic_info, parent): - self.topic = topic_info.topic_name - self.id = topic_info.topic_id - self.parent = parent - - # find message type - package, message = topic_info.message_type.split('/') - self.message = load_message(package, message) - if self.message._md5sum == topic_info.md5sum: - self.subscriber = rospy.Subscriber(self.topic, self.message, self.callback) - else: - raise Exception('Checksum does not match: ' + self.message._md5sum + ',' + topic_info.md5sum) - - def callback(self, msg): - """ Forward message to serial device. """ - data_buffer = io.BytesIO() - msg.serialize(data_buffer) - self.parent.send(self.id, data_buffer.getvalue()) - - def unregister(self): - rospy.loginfo("Removing subscriber: %s", self.topic) - self.subscriber.unregister() - -class ServiceServer: - """ - ServiceServer responds to requests from ROS. - """ - - def __init__(self, topic_info, parent): - self.topic = topic_info.topic_name - self.parent = parent - - # find message type - package, service = topic_info.message_type.split('/') - s = load_pkg_module(package, 'srv') - s = getattr(s, 'srv') - self.mreq = getattr(s, service+"Request") - self.mres = getattr(s, service+"Response") - srv = getattr(s, service) - self.service = rospy.Service(self.topic, srv, self.callback) - - # response message - self.data = None - - def unregister(self): - rospy.loginfo("Removing service: %s", self.topic) - self.service.shutdown() - - def callback(self, req): - """ Forward request to serial device. """ - data_buffer = io.BytesIO() - req.serialize(data_buffer) - self.response = None - self.parent.send(self.id, data_buffer.getvalue()) - while self.response is None: - pass - return self.response - - def handlePacket(self, data): - """ Forward response to ROS network. """ - r = self.mres() - r.deserialize(data) - self.response = r - - -class ServiceClient: - """ - ServiceServer responds to requests from ROS. - """ - - def __init__(self, topic_info, parent): - self.topic = topic_info.topic_name - self.parent = parent - - # find message type - package, service = topic_info.message_type.split('/') - s = load_pkg_module(package, 'srv') - s = getattr(s, 'srv') - self.mreq = getattr(s, service+"Request") - self.mres = getattr(s, service+"Response") - srv = getattr(s, service) - rospy.loginfo("Starting service client, waiting for service '" + self.topic + "'") - rospy.wait_for_service(self.topic) - self.proxy = rospy.ServiceProxy(self.topic, srv) - - def handlePacket(self, data): - """ Forward request to ROS network. """ - req = self.mreq() - req.deserialize(data) - # call service proxy - resp = self.proxy(req) - # serialize and publish - data_buffer = io.BytesIO() - resp.serialize(data_buffer) - self.parent.send(self.id, data_buffer.getvalue()) - -class RosSerialServer: - """ - RosSerialServer waits for a socket connection then passes itself, forked as a - new process, to SerialClient which uses it as a serial port. It continues to listen - for additional connections. Each forked process is a new ros node, and proxies ros - operations (e.g. publish/subscribe) from its connection to the rest of ros. - """ - def __init__(self, tcp_portnum, fork_server=False): - rospy.loginfo("Fork_server is: %s" % fork_server) - self.tcp_portnum = tcp_portnum - self.fork_server = fork_server - - def listen(self): - self.serversocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) - self.serversocket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) - #bind the socket to a public host, and a well-known port - self.serversocket.bind(("", self.tcp_portnum)) #become a server socket - self.serversocket.listen(1) - self.serversocket.settimeout(1) - - #accept connections - rospy.loginfo("Waiting for socket connection") - while not rospy.is_shutdown(): - try: - clientsocket, address = self.serversocket.accept() - except socket.timeout: - continue - - #now do something with the clientsocket - rospy.loginfo("Established a socket connection from %s on port %s" % address) - self.socket = clientsocket - self.isConnected = True - - if self.fork_server: # if configured to launch server in a separate process - rospy.loginfo("Forking a socket server process") - process = multiprocessing.Process(target=self.startSocketServer, args=address) - process.daemon = True - process.start() - rospy.loginfo("launched startSocketServer") - else: - rospy.loginfo("calling startSerialClient") - self.startSerialClient() - rospy.loginfo("startSerialClient() exited") - - def startSerialClient(self): - client = SerialClient(self) - try: - client.run() - except KeyboardInterrupt: - pass - except RuntimeError: - rospy.loginfo("RuntimeError exception caught") - self.isConnected = False - except socket.error: - rospy.loginfo("socket.error exception caught") - self.isConnected = False - finally: - rospy.loginfo("Client has exited, closing socket.") - self.socket.close() - for sub in client.subscribers.values(): - sub.unregister() - for srv in client.services.values(): - srv.unregister() - - def startSocketServer(self, port, address): - rospy.loginfo("starting ROS Serial Python Node serial_node-%r" % address) - rospy.init_node("serial_node_%r" % address) - self.startSerialClient() - - def flushInput(self): - pass - - def write(self, data): - if not self.isConnected: - return - length = len(data) - totalsent = 0 - - while totalsent < length: - try: - totalsent += self.socket.send(data[totalsent:]) - except BrokenPipeError: - raise RuntimeError("RosSerialServer.write() socket connection broken") - - def read(self, rqsted_length): - self.msg = b'' - if not self.isConnected: - return self.msg - - while len(self.msg) < rqsted_length: - chunk = self.socket.recv(rqsted_length - len(self.msg)) - if chunk == b'': - raise RuntimeError("RosSerialServer.read() socket connection broken") - self.msg = self.msg + chunk - return self.msg - - def inWaiting(self): - try: # the caller checks just for <1, so we'll peek at just one byte - chunk = self.socket.recv(1, socket.MSG_DONTWAIT|socket.MSG_PEEK) - if chunk == b'': - raise RuntimeError("RosSerialServer.inWaiting() socket connection broken") - return len(chunk) - except BlockingIOError: - return 0 - -class SerialClient(object): - """ - ServiceServer responds to requests from the serial device. - """ - header = b'\xff' - - # hydro introduces protocol ver2 which must match node_handle.h - # The protocol version is sent as the 2nd sync byte emitted by each end - protocol_ver1 = b'\xff' - protocol_ver2 = b'\xfe' - protocol_ver = protocol_ver2 - - def __init__(self, port=None, baud=57600, timeout=5.0, fix_pyserial_for_test=False): - """ Initialize node, connect to bus, attempt to negotiate topics. """ - - self.read_lock = threading.RLock() - - self.write_lock = threading.RLock() - self.write_queue = queue.Queue() - self.write_thread = None - - self.lastsync = rospy.Time(0) - self.lastsync_lost = rospy.Time(0) - self.lastsync_success = rospy.Time(0) - self.last_read = rospy.Time(0) - self.last_write = rospy.Time(0) - self.timeout = timeout - self.synced = False - self.fix_pyserial_for_test = fix_pyserial_for_test - - self.publishers = dict() # id:Publishers - self.subscribers = dict() # topic:Subscriber - self.services = dict() # topic:Service - - def shutdown(): - self.txStopRequest() - rospy.loginfo('shutdown hook activated') - rospy.on_shutdown(shutdown) - - self.pub_diagnostics = rospy.Publisher('/diagnostics', diagnostic_msgs.msg.DiagnosticArray, queue_size=10) - - if port is None: - # no port specified, listen for any new port? - pass - elif hasattr(port, 'read'): - #assume its a filelike object - self.port=port - else: - # open a specific port - while not rospy.is_shutdown(): - try: - if self.fix_pyserial_for_test: - # see https://github.com/pyserial/pyserial/issues/59 - self.port = Serial(port, baud, timeout=self.timeout, write_timeout=10, rtscts=True, dsrdtr=True) - else: - self.port = Serial(port, baud, timeout=self.timeout, write_timeout=10) - break - except SerialException as e: - rospy.logerr("Error opening serial: %s", e) - time.sleep(3) - - if rospy.is_shutdown(): - return - - time.sleep(0.1) # Wait for ready (patch for Uno) - - self.buffer_out = -1 - self.buffer_in = -1 - - self.callbacks = dict() - # endpoints for creating new pubs/subs - self.callbacks[TopicInfo.ID_PUBLISHER] = self.setupPublisher - self.callbacks[TopicInfo.ID_SUBSCRIBER] = self.setupSubscriber - # service client/servers have 2 creation endpoints (a publisher and a subscriber) - self.callbacks[TopicInfo.ID_SERVICE_SERVER+TopicInfo.ID_PUBLISHER] = self.setupServiceServerPublisher - self.callbacks[TopicInfo.ID_SERVICE_SERVER+TopicInfo.ID_SUBSCRIBER] = self.setupServiceServerSubscriber - self.callbacks[TopicInfo.ID_SERVICE_CLIENT+TopicInfo.ID_PUBLISHER] = self.setupServiceClientPublisher - self.callbacks[TopicInfo.ID_SERVICE_CLIENT+TopicInfo.ID_SUBSCRIBER] = self.setupServiceClientSubscriber - # custom endpoints - self.callbacks[TopicInfo.ID_PARAMETER_REQUEST] = self.handleParameterRequest - self.callbacks[TopicInfo.ID_LOG] = self.handleLoggingRequest - self.callbacks[TopicInfo.ID_TIME] = self.handleTimeRequest - - rospy.sleep(2.0) - self.requestTopics() - self.lastsync = rospy.Time.now() - - def requestTopics(self): - """ Determine topics to subscribe/publish. """ - rospy.loginfo('Requesting topics...') - - # TODO remove if possible - if not self.fix_pyserial_for_test: - with self.read_lock: - self.port.flushInput() - - # request topic sync - self.write_queue.put(self.header + self.protocol_ver + b"\x00\x00\xff\x00\x00\xff") - - def txStopRequest(self): - """ Send stop tx request to client before the node exits. """ - if not self.fix_pyserial_for_test: - with self.read_lock: - self.port.flushInput() - - self.write_queue.put(self.header + self.protocol_ver + b"\x00\x00\xff\x0b\x00\xf4") - rospy.loginfo("Sending tx stop request") - - def tryRead(self, length): - try: - read_start = time.time() - bytes_remaining = length - result = bytearray() - while bytes_remaining != 0 and time.time() - read_start < self.timeout: - with self.read_lock: - received = self.port.read(bytes_remaining) - if len(received) != 0: - self.last_read = rospy.Time.now() - result.extend(received) - bytes_remaining -= len(received) - - if bytes_remaining != 0: - raise IOError("Returned short (expected %d bytes, received %d instead)." % (length, length - bytes_remaining)) - - return bytes(result) - except Exception as e: - raise IOError("Serial Port read failure: %s" % e) - - def run(self): - """ Forward recieved messages to appropriate publisher. """ - - # Launch write thread. - if self.write_thread is None: - self.write_thread = threading.Thread(target=self.processWriteQueue) - self.write_thread.daemon = True - self.write_thread.start() - - # Handle reading. - data = '' - read_step = None - while self.write_thread.is_alive() and not rospy.is_shutdown(): - if (rospy.Time.now() - self.lastsync).to_sec() > (self.timeout * 3): - if self.synced: - rospy.logerr("Lost sync with device, restarting...") - else: - rospy.logerr("Unable to sync with device; possible link problem or link software version mismatch such as hydro rosserial_python with groovy Arduino") - self.lastsync_lost = rospy.Time.now() - self.sendDiagnostics(diagnostic_msgs.msg.DiagnosticStatus.ERROR, ERROR_NO_SYNC) - self.requestTopics() - self.lastsync = rospy.Time.now() - - # This try-block is here because we make multiple calls to read(). Any one of them can throw - # an IOError if there's a serial problem or timeout. In that scenario, a single handler at the - # bottom attempts to reconfigure the topics. - try: - with self.read_lock: - if self.port.inWaiting() < 1: - time.sleep(0.001) - continue - - # Find sync flag. - flag = [0, 0] - read_step = 'syncflag' - flag[0] = self.tryRead(1) - if (flag[0] != self.header): - continue - - # Find protocol version. - read_step = 'protocol' - flag[1] = self.tryRead(1) - if flag[1] != self.protocol_ver: - self.sendDiagnostics(diagnostic_msgs.msg.DiagnosticStatus.ERROR, ERROR_MISMATCHED_PROTOCOL) - rospy.logerr("Mismatched protocol version in packet (%s): lost sync or rosserial_python is from different ros release than the rosserial client" % repr(flag[1])) - protocol_ver_msgs = { - self.protocol_ver1: 'Rev 0 (rosserial 0.4 and earlier)', - self.protocol_ver2: 'Rev 1 (rosserial 0.5+)', - b'\xfd': 'Some future rosserial version' - } - if flag[1] in protocol_ver_msgs: - found_ver_msg = 'Protocol version of client is ' + protocol_ver_msgs[flag[1]] - else: - found_ver_msg = "Protocol version of client is unrecognized" - rospy.loginfo("%s, expected %s" % (found_ver_msg, protocol_ver_msgs[self.protocol_ver])) - continue - - # Read message length, checksum (3 bytes) - read_step = 'message length' - msg_len_bytes = self.tryRead(3) - msg_length, _ = struct.unpack(" 0 and length > self.buffer_in: - rospy.logerr("Message from ROS network dropped: message larger than buffer.\n%s" % msg) - return -1 - else: - # frame : header (1b) + version (1b) + msg_len(2b) + msg_len_chk(1b) + topic_id(2b) + msg(nb) + msg_topic_id_chk(1b) - length_bytes = struct.pack('0: - status.values[0].value=time.ctime(self.lastsync.to_sec()) - else: - status.values[0].value="never" - - status.values.append(diagnostic_msgs.msg.KeyValue()) - status.values[1].key="last sync lost" - status.values[1].value=time.ctime(self.lastsync_lost.to_sec()) - - self.pub_diagnostics.publish(msg) diff --git a/src/rosserial_python/src/rosserial_python/__init__.py b/src/rosserial_python/src/rosserial_python/__init__.py deleted file mode 100644 index e4f3ed8..0000000 --- a/src/rosserial_python/src/rosserial_python/__init__.py +++ /dev/null @@ -1 +0,0 @@ -from .SerialClient import * diff --git a/src/rosserial_python/src/rosserial_python/__pycache__/SerialClient.cpython-38.pyc b/src/rosserial_python/src/rosserial_python/__pycache__/SerialClient.cpython-38.pyc deleted file mode 100644 index 0088f81bb537d8f9779f4c21cc9004aab6555532..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 23691 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 - - - - \ No newline at end of file diff --git a/src/service/package.xml b/src/service/package.xml deleted file mode 100755 index e0c5f6e..0000000 --- a/src/service/package.xml +++ /dev/null @@ -1,68 +0,0 @@ - - - service - 0.0.0 - The service package - - - - - user - - - - - - TODO - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - catkin - roscpp - rospy - std_msgs - roscpp - rospy - std_msgs - roscpp - rospy - std_msgs - - - - - - - - diff --git a/src/service/src/day6_practice.cpp b/src/service/src/day6_practice.cpp deleted file mode 100644 index b442de5..0000000 --- a/src/service/src/day6_practice.cpp +++ /dev/null @@ -1,90 +0,0 @@ -#include -#include -#include -#include -#include - - -/*Global Variable*/ -turtlesim::Pose CurrentPose; -turtlesim::Pose TargetPose; -double footprint_x = 0; -double footprint_y = 0; -geometry_msgs::Twist current_vel; -double linear_vel = 0.0; -double angular_vel = 0.0; -double target_theta = 0; -double m = 0; -bool get = 0; -/*Global Variable*/ - -/*function definition*/ -void Callback(const turtlesim::Pose::ConstPtr& pose); -/*function definition*/ - - -int main(int argc, char *argv[]) -{ - ros::init(argc, argv, "practice_main"); - ros::NodeHandle nh; - ros::Publisher turtle_vel_pub = nh.advertise("/turtle1/cmd_vel", 100); - ros::Subscriber turtle_pose_sub = nh.subscribe("/turtle1/pose", 100, Callback); - ros::Rate loop_rate(1); - - if(nh.getParam("/linear_velocity", linear_vel) && nh.getParam("/angular_velocity", angular_vel)){ - - }else{ - ROS_ERROR("FAIL_TO_GET_PARAM(VEL)"); - return 0; - } - - - if(nh.getParam("footprint_x", footprint_x) && nh.getParam("footprint_y", footprint_y)){ - TargetPose.x = footprint_x; - TargetPose.y = footprint_y; - }else{ - ROS_ERROR("FAIL_TO_GET_PARAM(FOOTPRINT)"); - return 0; - } - - - - while (ros::ok()) - { - current_vel.angular.z = 0; - if(abs(CurrentPose.x-footprint_x)>=0.15){ - if(CurrentPose.x-footprint_x > 0){ - current_vel.linear.x = -abs(linear_vel); - }else{ - current_vel.linear.x = abs(linear_vel); - } - }else{ - current_vel.linear.x = 0; - } - if(abs(CurrentPose.y-footprint_y)>=0.15){ - if(CurrentPose.y-footprint_x > 0){ - current_vel.linear.y = -abs(linear_vel); - }else{ - current_vel.linear.y = abs(linear_vel); - } - }else{ - current_vel.linear.y = 0; - } - - turtle_vel_pub.publish(current_vel); - - ros::spinOnce(); - loop_rate.sleep(); - } - - return 0; -} - -void Callback(const turtlesim::Pose::ConstPtr& pose){ - CurrentPose.x = pose->x; - CurrentPose.y = pose->y; - CurrentPose.theta = pose->theta; - ROS_ERROR("%f", CurrentPose.x); - ROS_ERROR("%f", CurrentPose.y); - ROS_ERROR("%f", CurrentPose.theta); -} \ No newline at end of file diff --git a/src/tf/CMakeLists.txt b/src/tf/CMakeLists.txt deleted file mode 100755 index dbad98e..0000000 --- a/src/tf/CMakeLists.txt +++ /dev/null @@ -1,206 +0,0 @@ -cmake_minimum_required(VERSION 3.0.2) -project(tf) - -## Compile as C++11, supported in ROS Kinetic and newer -# add_compile_options(-std=c++11) - -## Find catkin macros and libraries -## if COMPONENTS list like find_package(catkin REQUIRED COMPONENTS xyz) -## is used, also find other catkin packages -find_package(catkin REQUIRED COMPONENTS - roscpp - rospy - std_msgs -) - -## System dependencies are found with CMake's conventions -# find_package(Boost REQUIRED COMPONENTS system) - - -## Uncomment this if the package has a setup.py. This macro ensures -## modules and global scripts declared therein get installed -## See http://ros.org/doc/api/catkin/html/user_guide/setup_dot_py.html -# catkin_python_setup() - -################################################ -## Declare ROS messages, services and actions ## -################################################ - -## To declare and build messages, services or actions from within this -## package, follow these steps: -## * Let MSG_DEP_SET be the set of packages whose message types you use in -## your messages/services/actions (e.g. std_msgs, actionlib_msgs, ...). -## * In the file package.xml: -## * add a build_depend tag for "message_generation" -## * add a build_depend and a exec_depend tag for each package in MSG_DEP_SET -## * If MSG_DEP_SET isn't empty the following dependency has been pulled in -## but can be declared for certainty nonetheless: -## * add a exec_depend tag for "message_runtime" -## * In this file (CMakeLists.txt): -## * add "message_generation" and every package in MSG_DEP_SET to -## find_package(catkin REQUIRED COMPONENTS ...) -## * add "message_runtime" and every package in MSG_DEP_SET to -## catkin_package(CATKIN_DEPENDS ...) -## * uncomment the add_*_files sections below as needed -## and list every .msg/.srv/.action file to be processed -## * uncomment the generate_messages entry below -## * add every package in MSG_DEP_SET to generate_messages(DEPENDENCIES ...) - -## Generate messages in the 'msg' folder -# add_message_files( -# FILES -# Message1.msg -# Message2.msg -# ) - -## Generate services in the 'srv' folder -# add_service_files( -# FILES -# Service1.srv -# Service2.srv -# ) - -## Generate actions in the 'action' folder -# add_action_files( -# FILES -# Action1.action -# Action2.action -# ) - -## Generate added messages and services with any dependencies listed here -# generate_messages( -# DEPENDENCIES -# std_msgs -# ) - -################################################ -## Declare ROS dynamic reconfigure parameters ## -################################################ - -## To declare and build dynamic reconfigure parameters within this -## package, follow these steps: -## * In the file package.xml: -## * add a build_depend and a exec_depend tag for "dynamic_reconfigure" -## * In this file (CMakeLists.txt): -## * add "dynamic_reconfigure" to -## find_package(catkin REQUIRED COMPONENTS ...) -## * uncomment the "generate_dynamic_reconfigure_options" section below -## and list every .cfg file to be processed - -## Generate dynamic reconfigure parameters in the 'cfg' folder -# generate_dynamic_reconfigure_options( -# cfg/DynReconf1.cfg -# cfg/DynReconf2.cfg -# ) - -################################### -## catkin specific configuration ## -################################### -## The catkin_package macro generates cmake config files for your package -## Declare things to be passed to dependent projects -## INCLUDE_DIRS: uncomment this if your package contains header files -## LIBRARIES: libraries you create in this project that dependent projects also need -## CATKIN_DEPENDS: catkin_packages dependent projects also need -## DEPENDS: system dependencies of this project that dependent projects also need -catkin_package( -# INCLUDE_DIRS include -# LIBRARIES tf -# CATKIN_DEPENDS roscpp rospy std_msgs -# DEPENDS system_lib -) - -########### -## Build ## -########### - -## Specify additional locations of header files -## Your package locations should be listed before other locations -include_directories( -# include - ${catkin_INCLUDE_DIRS} -) - -## Declare a C++ library -# add_library(${PROJECT_NAME} -# src/${PROJECT_NAME}/tf.cpp -# ) - -## Add cmake target dependencies of the library -## as an example, code may need to be generated before libraries -## either from message generation or dynamic reconfigure -# add_dependencies(${PROJECT_NAME} ${${PROJECT_NAME}_EXPORTED_TARGETS} ${catkin_EXPORTED_TARGETS}) - -## Declare a C++ executable -## With catkin_make all packages are built within a single CMake context -## The recommended prefix ensures that target names across packages don't collide -# add_executable(${PROJECT_NAME}_node src/tf_node.cpp) - -## Rename C++ executable without prefix -## The above recommended prefix causes long target names, the following renames the -## target back to the shorter version for ease of user use -## e.g. "rosrun someones_pkg node" instead of "rosrun someones_pkg someones_pkg_node" -# set_target_properties(${PROJECT_NAME}_node PROPERTIES OUTPUT_NAME node PREFIX "") - -## Add cmake target dependencies of the executable -## same as for the library above -# add_dependencies(${PROJECT_NAME}_node ${${PROJECT_NAME}_EXPORTED_TARGETS} ${catkin_EXPORTED_TARGETS}) - -## Specify libraries to link a library or executable target against -# target_link_libraries(${PROJECT_NAME}_node -# ${catkin_LIBRARIES} -# ) - -############# -## Install ## -############# - -# all install targets should use catkin DESTINATION variables -# See http://ros.org/doc/api/catkin/html/adv_user_guide/variables.html - -## Mark executable scripts (Python etc.) for installation -## in contrast to setup.py, you can choose the destination -# catkin_install_python(PROGRAMS -# scripts/my_python_script -# DESTINATION ${CATKIN_PACKAGE_BIN_DESTINATION} -# ) - -## Mark executables for installation -## See http://docs.ros.org/melodic/api/catkin/html/howto/format1/building_executables.html -# install(TARGETS ${PROJECT_NAME}_node -# RUNTIME DESTINATION ${CATKIN_PACKAGE_BIN_DESTINATION} -# ) - -## Mark libraries for installation -## See http://docs.ros.org/melodic/api/catkin/html/howto/format1/building_libraries.html -# install(TARGETS ${PROJECT_NAME} -# ARCHIVE DESTINATION ${CATKIN_PACKAGE_LIB_DESTINATION} -# LIBRARY DESTINATION ${CATKIN_PACKAGE_LIB_DESTINATION} -# RUNTIME DESTINATION ${CATKIN_GLOBAL_BIN_DESTINATION} -# ) - -## Mark cpp header files for installation -# install(DIRECTORY include/${PROJECT_NAME}/ -# DESTINATION ${CATKIN_PACKAGE_INCLUDE_DESTINATION} -# FILES_MATCHING PATTERN "*.h" -# PATTERN ".svn" EXCLUDE -# ) - -## Mark other files for installation (e.g. launch and bag files, etc.) -# install(FILES -# # myfile1 -# # myfile2 -# DESTINATION ${CATKIN_PACKAGE_SHARE_DESTINATION} -# ) - -############# -## Testing ## -############# - -## Add gtest based cpp test target and link libraries -# catkin_add_gtest(${PROJECT_NAME}-test test/test_tf.cpp) -# if(TARGET ${PROJECT_NAME}-test) -# target_link_libraries(${PROJECT_NAME}-test ${PROJECT_NAME}) -# endif() - -## Add folders to be run by python nosetests -# catkin_add_nosetests(test) diff --git a/src/tf/package.xml b/src/tf/package.xml deleted file mode 100755 index 820f8ff..0000000 --- a/src/tf/package.xml +++ /dev/null @@ -1,68 +0,0 @@ - - - tf - 0.0.0 - The tf package - - - - - user - - - - - - TODO - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - catkin - roscpp - rospy - std_msgs - roscpp - rospy - std_msgs - roscpp - rospy - std_msgs - - - - - - - - From ab88a3ec90e4d825f275d01cbae2448b45f0cbc9 Mon Sep 17 00:00:00 2001 From: TWang Date: Sun, 13 Jul 2025 22:01:25 +0800 Subject: [PATCH 02/20] update: new packages --- .gitignore | 5 ++-- docker/Dockerfile | 3 +++ docker/compose.yaml | 6 ++--- ros2_ws/cpp_pubsub/CMakeLists.txt | 26 +++++++++++++++++++ ros2_ws/cpp_pubsub/package.xml | 18 +++++++++++++ ros2_ws/cpp_srvcli/CMakeLists.txt | 25 ++++++++++++++++++ ros2_ws/cpp_srvcli/package.xml | 21 +++++++++++++++ ros2_ws/python_launch_examples/package.xml | 18 +++++++++++++ .../python_launch_examples/__init__.py | 0 .../resource/python_launch_examples | 0 ros2_ws/python_launch_examples/setup.cfg | 4 +++ ros2_ws/python_launch_examples/setup.py | 25 ++++++++++++++++++ .../test/test_copyright.py | 25 ++++++++++++++++++ .../test/test_flake8.py | 25 ++++++++++++++++++ .../test/test_pep257.py | 23 ++++++++++++++++ 15 files changed, 219 insertions(+), 5 deletions(-) create mode 100644 ros2_ws/cpp_pubsub/CMakeLists.txt create mode 100644 ros2_ws/cpp_pubsub/package.xml create mode 100644 ros2_ws/cpp_srvcli/CMakeLists.txt create mode 100644 ros2_ws/cpp_srvcli/package.xml create mode 100644 ros2_ws/python_launch_examples/package.xml create mode 100644 ros2_ws/python_launch_examples/python_launch_examples/__init__.py create mode 100644 ros2_ws/python_launch_examples/resource/python_launch_examples create mode 100644 ros2_ws/python_launch_examples/setup.cfg create mode 100644 ros2_ws/python_launch_examples/setup.py create mode 100644 ros2_ws/python_launch_examples/test/test_copyright.py create mode 100644 ros2_ws/python_launch_examples/test/test_flake8.py create mode 100644 ros2_ws/python_launch_examples/test/test_pep257.py diff --git a/.gitignore b/.gitignore index abdf307..7a7a0e0 100755 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ -/build -/devel +*/build +*/install +*/log /.vscode /src/CmakeLists.txt *.catkin_workspace \ No newline at end of file diff --git a/docker/Dockerfile b/docker/Dockerfile index c6c23d1..5a094c1 100755 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -28,6 +28,9 @@ RUN apt-get update && apt-get install -y \ python3-pip \ bash-completion +RUN echo "source /opt/ros/humble/setup.bash" >> /etc/bash.bashrc \ + && echo "source /home/user/ros_tutorial/ros2_ws/install/setup.bash" >> /etc/bash.bashrc + RUN apt-get update && apt-get dist-upgrade -y \ && apt-get autoremove -y \ && apt-get autoclean -y \ diff --git a/docker/compose.yaml b/docker/compose.yaml index 3470b1b..9b4a9ad 100644 --- a/docker/compose.yaml +++ b/docker/compose.yaml @@ -13,11 +13,11 @@ services: stop_grace_period: 1s network_mode: host - working_dir: /home/user/ros2_ws + working_dir: /home/user/ros_tutorial environment: - DISPLAY=$DISPLAY - XAUTHORITY=/root/.Xauthority - - ROS_WS=/home/user/tutorial_ws + - ROS_WS=/home/user/ros_tutorial/ros2_ws volumes: - /etc/timezone:/etc/timezone:ro - /etc/localtime:/etc/localtime:ro @@ -26,5 +26,5 @@ services: - /tmp/.X11-unix:/tmp/.X11-unix:rw # Direct Rendering Infrastructure - /dev:/dev - - ../ros2_ws:/home/user/ros2_ws + - ../:/home/user/ros_tutorial command: bash diff --git a/ros2_ws/cpp_pubsub/CMakeLists.txt b/ros2_ws/cpp_pubsub/CMakeLists.txt new file mode 100644 index 0000000..8ec4a22 --- /dev/null +++ b/ros2_ws/cpp_pubsub/CMakeLists.txt @@ -0,0 +1,26 @@ +cmake_minimum_required(VERSION 3.8) +project(cpp_pubsub) + +if(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_CXX_COMPILER_ID MATCHES "Clang") + add_compile_options(-Wall -Wextra -Wpedantic) +endif() + +# find dependencies +find_package(ament_cmake REQUIRED) +# uncomment the following section in order to fill in +# further dependencies manually. +# find_package( 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() + +ament_package() diff --git a/ros2_ws/cpp_pubsub/package.xml b/ros2_ws/cpp_pubsub/package.xml new file mode 100644 index 0000000..f791024 --- /dev/null +++ b/ros2_ws/cpp_pubsub/package.xml @@ -0,0 +1,18 @@ + + + + cpp_pubsub + 0.0.0 + TODO: Package description + user + TODO: License declaration + + ament_cmake + + ament_lint_auto + ament_lint_common + + + ament_cmake + + diff --git a/ros2_ws/cpp_srvcli/CMakeLists.txt b/ros2_ws/cpp_srvcli/CMakeLists.txt new file mode 100644 index 0000000..5a4bea2 --- /dev/null +++ b/ros2_ws/cpp_srvcli/CMakeLists.txt @@ -0,0 +1,25 @@ +cmake_minimum_required(VERSION 3.8) +project(cpp_srvcli) + +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(rclcpp REQUIRED) +find_package(example_interfaces 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() + +ament_package() diff --git a/ros2_ws/cpp_srvcli/package.xml b/ros2_ws/cpp_srvcli/package.xml new file mode 100644 index 0000000..a95ce80 --- /dev/null +++ b/ros2_ws/cpp_srvcli/package.xml @@ -0,0 +1,21 @@ + + + + cpp_srvcli + 0.0.0 + TODO: Package description + user + TODO: License declaration + + ament_cmake + + rclcpp + example_interfaces + + ament_lint_auto + ament_lint_common + + + ament_cmake + + diff --git a/ros2_ws/python_launch_examples/package.xml b/ros2_ws/python_launch_examples/package.xml new file mode 100644 index 0000000..3db37f0 --- /dev/null +++ b/ros2_ws/python_launch_examples/package.xml @@ -0,0 +1,18 @@ + + + + python_launch_examples + 0.0.0 + TODO: Package description + user + TODO: License declaration + + ament_copyright + ament_flake8 + ament_pep257 + python3-pytest + + + ament_python + + diff --git a/ros2_ws/python_launch_examples/python_launch_examples/__init__.py b/ros2_ws/python_launch_examples/python_launch_examples/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/ros2_ws/python_launch_examples/resource/python_launch_examples b/ros2_ws/python_launch_examples/resource/python_launch_examples new file mode 100644 index 0000000..e69de29 diff --git a/ros2_ws/python_launch_examples/setup.cfg b/ros2_ws/python_launch_examples/setup.cfg new file mode 100644 index 0000000..abbf227 --- /dev/null +++ b/ros2_ws/python_launch_examples/setup.cfg @@ -0,0 +1,4 @@ +[develop] +script_dir=$base/lib/python_launch_examples +[install] +install_scripts=$base/lib/python_launch_examples diff --git a/ros2_ws/python_launch_examples/setup.py b/ros2_ws/python_launch_examples/setup.py new file mode 100644 index 0000000..583e854 --- /dev/null +++ b/ros2_ws/python_launch_examples/setup.py @@ -0,0 +1,25 @@ +from setuptools import find_packages, setup + +package_name = 'python_launch_examples' + +setup( + name=package_name, + version='0.0.0', + packages=find_packages(exclude=['test']), + data_files=[ + ('share/ament_index/resource_index/packages', + ['resource/' + package_name]), + ('share/' + package_name, ['package.xml']), + ], + install_requires=['setuptools'], + zip_safe=True, + maintainer='user', + maintainer_email='user@todo.todo', + description='TODO: Package description', + license='TODO: License declaration', + tests_require=['pytest'], + entry_points={ + 'console_scripts': [ + ], + }, +) diff --git a/ros2_ws/python_launch_examples/test/test_copyright.py b/ros2_ws/python_launch_examples/test/test_copyright.py new file mode 100644 index 0000000..97a3919 --- /dev/null +++ b/ros2_ws/python_launch_examples/test/test_copyright.py @@ -0,0 +1,25 @@ +# Copyright 2015 Open Source Robotics Foundation, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from ament_copyright.main import main +import pytest + + +# Remove the `skip` decorator once the source file(s) have a copyright header +@pytest.mark.skip(reason='No copyright header has been placed in the generated source file.') +@pytest.mark.copyright +@pytest.mark.linter +def test_copyright(): + rc = main(argv=['.', 'test']) + assert rc == 0, 'Found errors' diff --git a/ros2_ws/python_launch_examples/test/test_flake8.py b/ros2_ws/python_launch_examples/test/test_flake8.py new file mode 100644 index 0000000..27ee107 --- /dev/null +++ b/ros2_ws/python_launch_examples/test/test_flake8.py @@ -0,0 +1,25 @@ +# Copyright 2017 Open Source Robotics Foundation, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from ament_flake8.main import main_with_errors +import pytest + + +@pytest.mark.flake8 +@pytest.mark.linter +def test_flake8(): + rc, errors = main_with_errors(argv=[]) + assert rc == 0, \ + 'Found %d code style errors / warnings:\n' % len(errors) + \ + '\n'.join(errors) diff --git a/ros2_ws/python_launch_examples/test/test_pep257.py b/ros2_ws/python_launch_examples/test/test_pep257.py new file mode 100644 index 0000000..b234a38 --- /dev/null +++ b/ros2_ws/python_launch_examples/test/test_pep257.py @@ -0,0 +1,23 @@ +# Copyright 2015 Open Source Robotics Foundation, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from ament_pep257.main import main +import pytest + + +@pytest.mark.linter +@pytest.mark.pep257 +def test_pep257(): + rc = main(argv=['.', 'test']) + assert rc == 0, 'Found code style errors / warnings' From 81c7c169eb387a014281377d9c4dde8269adf275 Mon Sep 17 00:00:00 2001 From: TWang Date: Sun, 13 Jul 2025 22:04:34 +0800 Subject: [PATCH 03/20] structure: folder --- ros2_ws/{ => src}/cpp_pubsub/CMakeLists.txt | 0 ros2_ws/{ => src}/cpp_pubsub/package.xml | 0 ros2_ws/{ => src}/cpp_srvcli/CMakeLists.txt | 0 ros2_ws/{ => src}/cpp_srvcli/package.xml | 0 ros2_ws/{ => src}/python_launch_examples/package.xml | 0 .../python_launch_examples/python_launch_examples/__init__.py | 0 .../python_launch_examples/resource/python_launch_examples | 0 ros2_ws/{ => src}/python_launch_examples/setup.cfg | 0 ros2_ws/{ => src}/python_launch_examples/setup.py | 0 ros2_ws/{ => src}/python_launch_examples/test/test_copyright.py | 0 ros2_ws/{ => src}/python_launch_examples/test/test_flake8.py | 0 ros2_ws/{ => src}/python_launch_examples/test/test_pep257.py | 0 12 files changed, 0 insertions(+), 0 deletions(-) rename ros2_ws/{ => src}/cpp_pubsub/CMakeLists.txt (100%) rename ros2_ws/{ => src}/cpp_pubsub/package.xml (100%) rename ros2_ws/{ => src}/cpp_srvcli/CMakeLists.txt (100%) rename ros2_ws/{ => src}/cpp_srvcli/package.xml (100%) rename ros2_ws/{ => src}/python_launch_examples/package.xml (100%) rename ros2_ws/{ => src}/python_launch_examples/python_launch_examples/__init__.py (100%) rename ros2_ws/{ => src}/python_launch_examples/resource/python_launch_examples (100%) rename ros2_ws/{ => src}/python_launch_examples/setup.cfg (100%) rename ros2_ws/{ => src}/python_launch_examples/setup.py (100%) rename ros2_ws/{ => src}/python_launch_examples/test/test_copyright.py (100%) rename ros2_ws/{ => src}/python_launch_examples/test/test_flake8.py (100%) rename ros2_ws/{ => src}/python_launch_examples/test/test_pep257.py (100%) diff --git a/ros2_ws/cpp_pubsub/CMakeLists.txt b/ros2_ws/src/cpp_pubsub/CMakeLists.txt similarity index 100% rename from ros2_ws/cpp_pubsub/CMakeLists.txt rename to ros2_ws/src/cpp_pubsub/CMakeLists.txt diff --git a/ros2_ws/cpp_pubsub/package.xml b/ros2_ws/src/cpp_pubsub/package.xml similarity index 100% rename from ros2_ws/cpp_pubsub/package.xml rename to ros2_ws/src/cpp_pubsub/package.xml diff --git a/ros2_ws/cpp_srvcli/CMakeLists.txt b/ros2_ws/src/cpp_srvcli/CMakeLists.txt similarity index 100% rename from ros2_ws/cpp_srvcli/CMakeLists.txt rename to ros2_ws/src/cpp_srvcli/CMakeLists.txt diff --git a/ros2_ws/cpp_srvcli/package.xml b/ros2_ws/src/cpp_srvcli/package.xml similarity index 100% rename from ros2_ws/cpp_srvcli/package.xml rename to ros2_ws/src/cpp_srvcli/package.xml diff --git a/ros2_ws/python_launch_examples/package.xml b/ros2_ws/src/python_launch_examples/package.xml similarity index 100% rename from ros2_ws/python_launch_examples/package.xml rename to ros2_ws/src/python_launch_examples/package.xml diff --git a/ros2_ws/python_launch_examples/python_launch_examples/__init__.py b/ros2_ws/src/python_launch_examples/python_launch_examples/__init__.py similarity index 100% rename from ros2_ws/python_launch_examples/python_launch_examples/__init__.py rename to ros2_ws/src/python_launch_examples/python_launch_examples/__init__.py diff --git a/ros2_ws/python_launch_examples/resource/python_launch_examples b/ros2_ws/src/python_launch_examples/resource/python_launch_examples similarity index 100% rename from ros2_ws/python_launch_examples/resource/python_launch_examples rename to ros2_ws/src/python_launch_examples/resource/python_launch_examples diff --git a/ros2_ws/python_launch_examples/setup.cfg b/ros2_ws/src/python_launch_examples/setup.cfg similarity index 100% rename from ros2_ws/python_launch_examples/setup.cfg rename to ros2_ws/src/python_launch_examples/setup.cfg diff --git a/ros2_ws/python_launch_examples/setup.py b/ros2_ws/src/python_launch_examples/setup.py similarity index 100% rename from ros2_ws/python_launch_examples/setup.py rename to ros2_ws/src/python_launch_examples/setup.py diff --git a/ros2_ws/python_launch_examples/test/test_copyright.py b/ros2_ws/src/python_launch_examples/test/test_copyright.py similarity index 100% rename from ros2_ws/python_launch_examples/test/test_copyright.py rename to ros2_ws/src/python_launch_examples/test/test_copyright.py diff --git a/ros2_ws/python_launch_examples/test/test_flake8.py b/ros2_ws/src/python_launch_examples/test/test_flake8.py similarity index 100% rename from ros2_ws/python_launch_examples/test/test_flake8.py rename to ros2_ws/src/python_launch_examples/test/test_flake8.py diff --git a/ros2_ws/python_launch_examples/test/test_pep257.py b/ros2_ws/src/python_launch_examples/test/test_pep257.py similarity index 100% rename from ros2_ws/python_launch_examples/test/test_pep257.py rename to ros2_ws/src/python_launch_examples/test/test_pep257.py From 9a98ab16679228b8e297d3b61943c074f608878f Mon Sep 17 00:00:00 2001 From: TWang Date: Sun, 13 Jul 2025 22:53:58 +0800 Subject: [PATCH 04/20] add: example codes --- .../src/cpp_launch_examples/CMakeLists.txt | 25 +++++++++++ .../config/pub_params.yaml | 9 ++++ .../launch/examples_launch.py | 21 +++++++++ .../launch/examples_launch.xml | 0 ros2_ws/src/cpp_launch_examples/package.xml | 21 +++++++++ ros2_ws/src/cpp_pubsub/CMakeLists.txt | 34 +++++++------- ros2_ws/src/cpp_pubsub/package.xml | 14 +++--- .../src/publisher_member_function.cpp | 44 +++++++++++++++++++ .../src/subscriber_member_function.cpp | 31 +++++++++++++ ros2_ws/src/cpp_srvcli/CMakeLists.txt | 30 ++++++------- ros2_ws/src/cpp_srvcli/package.xml | 31 ++++++------- .../cpp_srvcli/src/add_two_ints_client.cpp | 32 ++++++++++++++ .../cpp_srvcli/src/add_two_ints_server.cpp | 35 +++++++++++++++ ros2_ws/src/example_interfaces/CMakeLists.txt | 22 ++++++++++ .../action/Fibonacci.action | 5 +++ ros2_ws/src/example_interfaces/package.xml | 30 +++++++++++++ .../src/example_interfaces/srv/AddTwoInts.srv | 4 ++ ros2_ws/src/py_pubsub/package.xml | 15 +++++++ ros2_ws/src/py_pubsub/py_pubsub/__init__.py | 0 .../py_pubsub/publisher_member_function.py | 39 ++++++++++++++++ .../py_pubsub/subscriber_member_function.py | 37 ++++++++++++++++ ros2_ws/src/py_pubsub/resource/py_pubsub | 0 ros2_ws/src/py_pubsub/setup.cfg | 4 ++ ros2_ws/src/py_pubsub/setup.py | 25 +++++++++++ ros2_ws/src/py_pubsub/test/test_copyright.py | 25 +++++++++++ ros2_ws/src/py_pubsub/test/test_flake8.py | 25 +++++++++++ ros2_ws/src/py_pubsub/test/test_pep257.py | 23 ++++++++++ ros2_ws/src/py_srvcli/package.xml | 15 +++++++ ros2_ws/src/py_srvcli/py_srvcli/__init__.py | 0 .../py_srvcli/add_two_ints_client.py | 32 ++++++++++++++ .../py_srvcli/add_two_ints_server.py | 26 +++++++++++ ros2_ws/src/py_srvcli/resource/py_srvcli | 0 ros2_ws/src/py_srvcli/setup.cfg | 4 ++ ros2_ws/src/py_srvcli/setup.py | 18 ++++++++ ros2_ws/src/py_srvcli/test/test_copyright.py | 25 +++++++++++ ros2_ws/src/py_srvcli/test/test_flake8.py | 25 +++++++++++ ros2_ws/src/py_srvcli/test/test_pep257.py | 23 ++++++++++ .../config/pub_params.yaml | 9 ++++ .../launch/launch_arg_examples.py | 41 +++++++++++++++++ .../launch/python_examples_launch.py | 21 +++++++++ .../launch/talker_with_params.py | 20 +++++++++ ros2_ws/src/python_launch_examples/setup.py | 23 ++++------ 42 files changed, 788 insertions(+), 75 deletions(-) create mode 100644 ros2_ws/src/cpp_launch_examples/CMakeLists.txt create mode 100644 ros2_ws/src/cpp_launch_examples/config/pub_params.yaml create mode 100644 ros2_ws/src/cpp_launch_examples/launch/examples_launch.py create mode 100644 ros2_ws/src/cpp_launch_examples/launch/examples_launch.xml create mode 100644 ros2_ws/src/cpp_launch_examples/package.xml create mode 100644 ros2_ws/src/cpp_pubsub/src/publisher_member_function.cpp create mode 100644 ros2_ws/src/cpp_pubsub/src/subscriber_member_function.cpp create mode 100644 ros2_ws/src/cpp_srvcli/src/add_two_ints_client.cpp create mode 100644 ros2_ws/src/cpp_srvcli/src/add_two_ints_server.cpp create mode 100644 ros2_ws/src/example_interfaces/CMakeLists.txt create mode 100644 ros2_ws/src/example_interfaces/action/Fibonacci.action create mode 100644 ros2_ws/src/example_interfaces/package.xml create mode 100644 ros2_ws/src/example_interfaces/srv/AddTwoInts.srv create mode 100644 ros2_ws/src/py_pubsub/package.xml create mode 100644 ros2_ws/src/py_pubsub/py_pubsub/__init__.py create mode 100644 ros2_ws/src/py_pubsub/py_pubsub/publisher_member_function.py create mode 100644 ros2_ws/src/py_pubsub/py_pubsub/subscriber_member_function.py create mode 100644 ros2_ws/src/py_pubsub/resource/py_pubsub create mode 100644 ros2_ws/src/py_pubsub/setup.cfg create mode 100644 ros2_ws/src/py_pubsub/setup.py create mode 100644 ros2_ws/src/py_pubsub/test/test_copyright.py create mode 100644 ros2_ws/src/py_pubsub/test/test_flake8.py create mode 100644 ros2_ws/src/py_pubsub/test/test_pep257.py create mode 100644 ros2_ws/src/py_srvcli/package.xml create mode 100644 ros2_ws/src/py_srvcli/py_srvcli/__init__.py create mode 100644 ros2_ws/src/py_srvcli/py_srvcli/add_two_ints_client.py create mode 100644 ros2_ws/src/py_srvcli/py_srvcli/add_two_ints_server.py create mode 100644 ros2_ws/src/py_srvcli/resource/py_srvcli create mode 100644 ros2_ws/src/py_srvcli/setup.cfg create mode 100644 ros2_ws/src/py_srvcli/setup.py create mode 100644 ros2_ws/src/py_srvcli/test/test_copyright.py create mode 100644 ros2_ws/src/py_srvcli/test/test_flake8.py create mode 100644 ros2_ws/src/py_srvcli/test/test_pep257.py create mode 100644 ros2_ws/src/python_launch_examples/config/pub_params.yaml create mode 100644 ros2_ws/src/python_launch_examples/launch/launch_arg_examples.py create mode 100644 ros2_ws/src/python_launch_examples/launch/python_examples_launch.py create mode 100644 ros2_ws/src/python_launch_examples/launch/talker_with_params.py diff --git a/ros2_ws/src/cpp_launch_examples/CMakeLists.txt b/ros2_ws/src/cpp_launch_examples/CMakeLists.txt new file mode 100644 index 0000000..abbe594 --- /dev/null +++ b/ros2_ws/src/cpp_launch_examples/CMakeLists.txt @@ -0,0 +1,25 @@ +cmake_minimum_required(VERSION 3.8) +project(cpp_launch_examples) + +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(rclcpp REQUIRED) +find_package(example_interfaces 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() + +ament_package() diff --git a/ros2_ws/src/cpp_launch_examples/config/pub_params.yaml b/ros2_ws/src/cpp_launch_examples/config/pub_params.yaml new file mode 100644 index 0000000..285036a --- /dev/null +++ b/ros2_ws/src/cpp_launch_examples/config/pub_params.yaml @@ -0,0 +1,9 @@ +cpp_pubsub: +ros__parameters: + message_prefix: "Hello from CPP" # string + publish_frequency: 2.0 # floating + +py_pubsub: +ros__parameters: + message_prefix: "Hello from Python" # string + publish_frequency: 1.0 # floating \ No newline at end of file diff --git a/ros2_ws/src/cpp_launch_examples/launch/examples_launch.py b/ros2_ws/src/cpp_launch_examples/launch/examples_launch.py new file mode 100644 index 0000000..d1f850c --- /dev/null +++ b/ros2_ws/src/cpp_launch_examples/launch/examples_launch.py @@ -0,0 +1,21 @@ +from launch import LaunchDescription +from launch_ros.actions import Node + +def generate_launch_description(): + return LaunchDescription([ + # C++ Pub-Sub + Node(package='cpp_pubsub', executable='talker', name='cpp_talker', output='screen'), + Node(package='cpp_pubsub', executable='listener', name='cpp_listener', output='screen'), + + # Python Pub-Sub + Node(package='py_pubsub', executable='talker', name='py_talker', output='screen'), + Node(package='py_pubsub', executable='listener', name='py_listener', output='screen'), + + # C++ Service + Node(package='cpp_srvcli', executable='add_two_ints_server', name='cpp_srv_server', output='screen'), + Node(package='cpp_srvcli', executable='add_two_ints_client', name='cpp_srv_client', output='screen'), + + # Python Service + Node(package='py_srvcli', executable='add_two_ints_server', name='py_srv_server', output='screen'), + Node(package='py_srvcli', executable='add_two_ints_client', name='py_srv_client', output='screen'), + ]) \ No newline at end of file diff --git a/ros2_ws/src/cpp_launch_examples/launch/examples_launch.xml b/ros2_ws/src/cpp_launch_examples/launch/examples_launch.xml new file mode 100644 index 0000000..e69de29 diff --git a/ros2_ws/src/cpp_launch_examples/package.xml b/ros2_ws/src/cpp_launch_examples/package.xml new file mode 100644 index 0000000..4134bc5 --- /dev/null +++ b/ros2_ws/src/cpp_launch_examples/package.xml @@ -0,0 +1,21 @@ + + + + cpp_launch_examples + 0.0.0 + TODO: Package description + user + TODO: License declaration + + ament_cmake + + rclcpp + example_interfaces + + ament_lint_auto + ament_lint_common + + + ament_cmake + + diff --git a/ros2_ws/src/cpp_pubsub/CMakeLists.txt b/ros2_ws/src/cpp_pubsub/CMakeLists.txt index 8ec4a22..480cfc0 100644 --- a/ros2_ws/src/cpp_pubsub/CMakeLists.txt +++ b/ros2_ws/src/cpp_pubsub/CMakeLists.txt @@ -1,26 +1,22 @@ cmake_minimum_required(VERSION 3.8) project(cpp_pubsub) -if(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_CXX_COMPILER_ID MATCHES "Clang") - add_compile_options(-Wall -Wextra -Wpedantic) -endif() - -# find dependencies +# Find dependencies find_package(ament_cmake REQUIRED) -# uncomment the following section in order to fill in -# further dependencies manually. -# find_package( REQUIRED) +find_package(rclcpp REQUIRED) +find_package(std_msgs REQUIRED) + +# Add executables +add_executable(talker src/publisher_member_function.cpp) # 將"src/publisher_member_function.cpp"這個檔案命名為"talker"的可執行目標(executable target),並加入編譯列表 +ament_target_dependencies(talker rclcpp std_msgs) + +add_executable(listener src/subscriber_member_function.cpp) # 將"src/publisher_member_function.cpp"這個檔案命名為"listener"的可執行目標(executable target),並加入編譯列表 +ament_target_dependencies(listener rclcpp std_msgs) -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() +# Install executables +install(TARGETS + talker + listener + DESTINATION lib/${PROJECT_NAME}) ament_package() diff --git a/ros2_ws/src/cpp_pubsub/package.xml b/ros2_ws/src/cpp_pubsub/package.xml index f791024..b459a05 100644 --- a/ros2_ws/src/cpp_pubsub/package.xml +++ b/ros2_ws/src/cpp_pubsub/package.xml @@ -1,18 +1,16 @@ - cpp_pubsub 0.0.0 - TODO: Package description - user - TODO: License declaration + C++ pub/sub example + your_name + Apache-2.0 ament_cmake - - ament_lint_auto - ament_lint_common + rclcpp + std_msgs ament_cmake - + \ No newline at end of file diff --git a/ros2_ws/src/cpp_pubsub/src/publisher_member_function.cpp b/ros2_ws/src/cpp_pubsub/src/publisher_member_function.cpp new file mode 100644 index 0000000..2157f70 --- /dev/null +++ b/ros2_ws/src/cpp_pubsub/src/publisher_member_function.cpp @@ -0,0 +1,44 @@ +#include +#include +#include +#include + +#include "rclcpp/rclcpp.hpp" +#include "std_msgs/msg/string.hpp" + +using namespace std::chrono_literals; + +/* This example creates a subclass of Node and uses std::bind() to register a +* member function as a callback from the timer. */ + +class MinimalPublisher : public rclcpp::Node +{ + public: + MinimalPublisher() + : Node("minimal_publisher"), count_(0) + { + publisher_ = this->create_publisher("topic", 10); + timer_ = this->create_wall_timer( + 500ms, std::bind(&MinimalPublisher::timer_callback, this)); + } + + private: + void timer_callback() + { + auto message = std_msgs::msg::String(); + message.data = "Hello, world! " + std::to_string(count_++); + RCLCPP_INFO(this->get_logger(), "Publishing: '%s'", message.data.c_str()); + publisher_->publish(message); + } + rclcpp::TimerBase::SharedPtr timer_; + rclcpp::Publisher::SharedPtr publisher_; + size_t count_; +}; + +int main(int argc, char * argv[]) +{ + rclcpp::init(argc, argv); + rclcpp::spin(std::make_shared()); + rclcpp::shutdown(); + return 0; +} \ No newline at end of file diff --git a/ros2_ws/src/cpp_pubsub/src/subscriber_member_function.cpp b/ros2_ws/src/cpp_pubsub/src/subscriber_member_function.cpp new file mode 100644 index 0000000..83345b4 --- /dev/null +++ b/ros2_ws/src/cpp_pubsub/src/subscriber_member_function.cpp @@ -0,0 +1,31 @@ +#include + +#include "rclcpp/rclcpp.hpp" +#include "std_msgs/msg/string.hpp" +using std::placeholders::_1; + +class MinimalSubscriber : public rclcpp::Node +{ + public: + MinimalSubscriber() + : Node("minimal_subscriber") + { + subscription_ = this->create_subscription( + "topic", 10, std::bind(&MinimalSubscriber::topic_callback, this, _1)); + } + + private: + void topic_callback(const std_msgs::msg::String::SharedPtr msg) const + { + RCLCPP_INFO(this->get_logger(), "I heard: '%s'", msg->data.c_str()); + } + rclcpp::Subscription::SharedPtr subscription_; +}; + +int main(int argc, char * argv[]) +{ + rclcpp::init(argc, argv); + rclcpp::spin(std::make_shared()); + rclcpp::shutdown(); + return 0; +} \ No newline at end of file diff --git a/ros2_ws/src/cpp_srvcli/CMakeLists.txt b/ros2_ws/src/cpp_srvcli/CMakeLists.txt index 5a4bea2..a02395a 100644 --- a/ros2_ws/src/cpp_srvcli/CMakeLists.txt +++ b/ros2_ws/src/cpp_srvcli/CMakeLists.txt @@ -1,25 +1,21 @@ cmake_minimum_required(VERSION 3.8) project(cpp_srvcli) -if(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_CXX_COMPILER_ID MATCHES "Clang") - add_compile_options(-Wall -Wextra -Wpedantic) -endif() - -# find dependencies +# Find dependencies find_package(ament_cmake REQUIRED) find_package(rclcpp REQUIRED) find_package(example_interfaces 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() +add_executable(add_two_ints_server src/add_two_ints_server.cpp) +add_executable(add_two_ints_client src/add_two_ints_client.cpp) + +ament_target_dependencies(add_two_ints_server rclcpp example_interfaces) +ament_target_dependencies(add_two_ints_client rclcpp example_interfaces) + +install(TARGETS + add_two_ints_server + add_two_ints_client + DESTINATION lib/${PROJECT_NAME} +) -ament_package() +ament_package() \ No newline at end of file diff --git a/ros2_ws/src/cpp_srvcli/package.xml b/ros2_ws/src/cpp_srvcli/package.xml index a95ce80..cb95a05 100644 --- a/ros2_ws/src/cpp_srvcli/package.xml +++ b/ros2_ws/src/cpp_srvcli/package.xml @@ -1,21 +1,18 @@ - - cpp_srvcli - 0.0.0 - TODO: Package description - user - TODO: License declaration + cpp_srvcli + 0.0.0 + C++ service/client example + Your Name + Apache License 2.0 - ament_cmake + ament_cmake + rclcpp + example_interfaces + rclcpp + example_interfaces - rclcpp - example_interfaces - - ament_lint_auto - ament_lint_common - - - ament_cmake - - + + ament_cmake + + \ No newline at end of file diff --git a/ros2_ws/src/cpp_srvcli/src/add_two_ints_client.cpp b/ros2_ws/src/cpp_srvcli/src/add_two_ints_client.cpp new file mode 100644 index 0000000..65ab945 --- /dev/null +++ b/ros2_ws/src/cpp_srvcli/src/add_two_ints_client.cpp @@ -0,0 +1,32 @@ +#include "rclcpp/rclcpp.hpp" +#include "example_interfaces/srv/add_two_ints.hpp" + +using AddTwoInts = example_interfaces::srv::AddTwoInts; + +int main(int argc, char **argv) { + rclcpp::init(argc, argv); + // 創一個名叫 "add_two_ints_client" 的 Node + auto node = rclcpp::Node::make_shared("add_two_ints_client"); + // 創建一個 "add_two_ints" 這個 Service 的 Client 端 + auto client = node->create_client("add_two_ints"); + + while (!client->wait_for_service(std::chrono::seconds(1))) { + RCLCPP_INFO(node->get_logger(), "Waiting for service..."); + } + + auto request = std::make_shared(); + request->a = 3; + request->b = 7; + + // 發送請求給 Server 端 + auto result = client->async_send_request(request); + // 等待 Server 端回傳資料 + if (rclcpp::spin_until_future_complete(node, result) == rclcpp::FutureReturnCode::SUCCESS) { + RCLCPP_INFO(node->get_logger(), "Result: %ld", result.get()->sum); + } + else { + RCLCPP_ERROR(rclcpp::get_logger("rclcpp"), "Failed to call service add_three_doubles"); // CHANGE + } + rclcpp::shutdown(); + return 0; +} \ No newline at end of file diff --git a/ros2_ws/src/cpp_srvcli/src/add_two_ints_server.cpp b/ros2_ws/src/cpp_srvcli/src/add_two_ints_server.cpp new file mode 100644 index 0000000..826e75e --- /dev/null +++ b/ros2_ws/src/cpp_srvcli/src/add_two_ints_server.cpp @@ -0,0 +1,35 @@ +#include "rclcpp/rclcpp.hpp" +#include "example_interfaces/srv/add_two_ints.hpp" + +using AddTwoInts = example_interfaces::srv::AddTwoInts; +using std::placeholders::_1; +using std::placeholders::_2; + +class AddTwoIntsServer : public rclcpp::Node { +public: + // 建構子, Node名稱叫做"add_two_ints_server" + AddTwoIntsServer() : Node("add_two_ints_server") { + // 建立 Service 通訊 + // 第一個參數是名稱(跟Topic一樣), 第二個是bind函式,裡面放要運行的函式,後面照著打就好 + service_ = create_service( + "add_two_ints", std::bind(&AddTwoIntsServer::handle_add, this, _1, _2)); + } + +private: + // ()裡面就是固定格式 + // <>裡面放 Service 用的 interface,也就是定義通訊雙方的資料格式,可以看下方補充 + void handle_add(const std::shared_ptr request, + std::shared_ptr response) { + response->sum = request->a + request->b; + RCLCPP_INFO(this->get_logger(), "Received: %ld + %ld", request->a, request->b); + } + + rclcpp::Service::SharedPtr service_; +}; + +int main(int argc, char **argv) { + rclcpp::init(argc, argv); + rclcpp::spin(std::make_shared()); + rclcpp::shutdown(); + return 0; +} \ No newline at end of file diff --git a/ros2_ws/src/example_interfaces/CMakeLists.txt b/ros2_ws/src/example_interfaces/CMakeLists.txt new file mode 100644 index 0000000..0ee243b --- /dev/null +++ b/ros2_ws/src/example_interfaces/CMakeLists.txt @@ -0,0 +1,22 @@ +cmake_minimum_required(VERSION 3.16) +project(example_interfaces) + +set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD_REQUIRED ON) + +find_package(ament_cmake REQUIRED) +find_package(rclcpp REQUIRED) +find_package(rclcpp_action REQUIRED) +find_package(std_msgs REQUIRED) +find_package(action_msgs REQUIRED) +find_package(geometry_msgs REQUIRED) +find_package(builtin_interfaces REQUIRED) +find_package(rosidl_default_generators REQUIRED) + +rosidl_generate_interfaces(example_interfaces + "action/Fibonacci.action" + DEPENDENCIES geometry_msgs action_msgs std_msgs builtin_interfaces +) + +ament_export_dependencies(rosidl_default_runtime) +ament_package() diff --git a/ros2_ws/src/example_interfaces/action/Fibonacci.action b/ros2_ws/src/example_interfaces/action/Fibonacci.action new file mode 100644 index 0000000..32b18f2 --- /dev/null +++ b/ros2_ws/src/example_interfaces/action/Fibonacci.action @@ -0,0 +1,5 @@ +int32 order +--- +int32[] sequence +--- +int32[] partial_sequence \ No newline at end of file diff --git a/ros2_ws/src/example_interfaces/package.xml b/ros2_ws/src/example_interfaces/package.xml new file mode 100644 index 0000000..a41edca --- /dev/null +++ b/ros2_ws/src/example_interfaces/package.xml @@ -0,0 +1,30 @@ + + example_interfaces + 0.2.0 + TODO: Package description + + Davide Faconti + MIT + Davide Faconti + + + ament_cmake + rosidl_default_generators + + rosidl_default_runtime + + rclcpp + rclcpp_action + rclcpp_components + action_msgs + std_msgs + geometry_msgs + builtin_interfaces + + rosidl_interface_packages + + + ament_cmake + + + diff --git a/ros2_ws/src/example_interfaces/srv/AddTwoInts.srv b/ros2_ws/src/example_interfaces/srv/AddTwoInts.srv new file mode 100644 index 0000000..e717870 --- /dev/null +++ b/ros2_ws/src/example_interfaces/srv/AddTwoInts.srv @@ -0,0 +1,4 @@ +int64 a # request要送的資料 +int64 b # request要送的資料 +--- +int64 sum # response會回傳的資料 \ No newline at end of file diff --git a/ros2_ws/src/py_pubsub/package.xml b/ros2_ws/src/py_pubsub/package.xml new file mode 100644 index 0000000..e8eb33b --- /dev/null +++ b/ros2_ws/src/py_pubsub/package.xml @@ -0,0 +1,15 @@ + + + py_pubsub + 0.0.0 + Python pub/sub example + ros + Apache-2.0 + + rclpy + std_msgs + + + ament_python + + \ No newline at end of file diff --git a/ros2_ws/src/py_pubsub/py_pubsub/__init__.py b/ros2_ws/src/py_pubsub/py_pubsub/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/ros2_ws/src/py_pubsub/py_pubsub/publisher_member_function.py b/ros2_ws/src/py_pubsub/py_pubsub/publisher_member_function.py new file mode 100644 index 0000000..fff0ae1 --- /dev/null +++ b/ros2_ws/src/py_pubsub/py_pubsub/publisher_member_function.py @@ -0,0 +1,39 @@ +import rclpy +from rclpy.node import Node + +from std_msgs.msg import String + + +class MinimalPublisher(Node): + + def __init__(self): + super().__init__('minimal_publisher') + self.publisher_ = self.create_publisher(String, 'topic', 10) + timer_period = 0.5 # seconds + self.timer = self.create_timer(timer_period, self.timer_callback) + self.i = 0 + + def timer_callback(self): + msg = String() + msg.data = 'Hello World: %d' % self.i + self.publisher_.publish(msg) + self.get_logger().info('Publishing: "%s"' % msg.data) + self.i += 1 + + +def main(args=None): + rclpy.init(args=args) + + minimal_publisher = MinimalPublisher() + + rclpy.spin(minimal_publisher) + + # Destroy the node explicitly + # (optional - otherwise it will be done automatically + # when the garbage collector destroys the node object) + minimal_publisher.destroy_node() + rclpy.shutdown() + + +if __name__ == '__main__': + main() \ No newline at end of file diff --git a/ros2_ws/src/py_pubsub/py_pubsub/subscriber_member_function.py b/ros2_ws/src/py_pubsub/py_pubsub/subscriber_member_function.py new file mode 100644 index 0000000..90b6807 --- /dev/null +++ b/ros2_ws/src/py_pubsub/py_pubsub/subscriber_member_function.py @@ -0,0 +1,37 @@ +import rclpy +from rclpy.node import Node + +from std_msgs.msg import String + + +class MinimalSubscriber(Node): + + def __init__(self): + super().__init__('minimal_subscriber') + self.subscription = self.create_subscription( + String, + 'topic', + self.listener_callback, + 10) + self.subscription # prevent unused variable warning + + def listener_callback(self, msg): + self.get_logger().info('I heard: "%s"' % msg.data) + + +def main(args=None): + rclpy.init(args=args) + + minimal_subscriber = MinimalSubscriber() + + rclpy.spin(minimal_subscriber) + + # Destroy the node explicitly + # (optional - otherwise it will be done automatically + # when the garbage collector destroys the node object) + minimal_subscriber.destroy_node() + rclpy.shutdown() + + +if __name__ == '__main__': + main() \ No newline at end of file diff --git a/ros2_ws/src/py_pubsub/resource/py_pubsub b/ros2_ws/src/py_pubsub/resource/py_pubsub new file mode 100644 index 0000000..e69de29 diff --git a/ros2_ws/src/py_pubsub/setup.cfg b/ros2_ws/src/py_pubsub/setup.cfg new file mode 100644 index 0000000..3f886b9 --- /dev/null +++ b/ros2_ws/src/py_pubsub/setup.cfg @@ -0,0 +1,4 @@ +[develop] +script_dir=$base/lib/py_pubsub +[install] +install_scripts=$base/lib/py_pubsub diff --git a/ros2_ws/src/py_pubsub/setup.py b/ros2_ws/src/py_pubsub/setup.py new file mode 100644 index 0000000..3c6b2d0 --- /dev/null +++ b/ros2_ws/src/py_pubsub/setup.py @@ -0,0 +1,25 @@ +from setuptools import find_packages, setup + +package_name = 'py_pubsub' + +setup( + name=package_name, + version='0.0.0', + packages=find_packages(exclude=['test']), + data_files=[ + ('share/ament_index/resource_index/packages', + ['resource/' + package_name]), + ('share/' + package_name, ['package.xml']), + ], + install_requires=['setuptools'], + maintainer='ros', + maintainer_email='ros@todo.todo', + description='Python pub/sub example', + license='Apache-2.0', + entry_points={ + 'console_scripts': [ + 'talker = py_pubsub.publisher_member_function:main', + 'listener = py_pubsub.subscriber_member_function:main', + ], + }, +) diff --git a/ros2_ws/src/py_pubsub/test/test_copyright.py b/ros2_ws/src/py_pubsub/test/test_copyright.py new file mode 100644 index 0000000..97a3919 --- /dev/null +++ b/ros2_ws/src/py_pubsub/test/test_copyright.py @@ -0,0 +1,25 @@ +# Copyright 2015 Open Source Robotics Foundation, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from ament_copyright.main import main +import pytest + + +# Remove the `skip` decorator once the source file(s) have a copyright header +@pytest.mark.skip(reason='No copyright header has been placed in the generated source file.') +@pytest.mark.copyright +@pytest.mark.linter +def test_copyright(): + rc = main(argv=['.', 'test']) + assert rc == 0, 'Found errors' diff --git a/ros2_ws/src/py_pubsub/test/test_flake8.py b/ros2_ws/src/py_pubsub/test/test_flake8.py new file mode 100644 index 0000000..27ee107 --- /dev/null +++ b/ros2_ws/src/py_pubsub/test/test_flake8.py @@ -0,0 +1,25 @@ +# Copyright 2017 Open Source Robotics Foundation, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from ament_flake8.main import main_with_errors +import pytest + + +@pytest.mark.flake8 +@pytest.mark.linter +def test_flake8(): + rc, errors = main_with_errors(argv=[]) + assert rc == 0, \ + 'Found %d code style errors / warnings:\n' % len(errors) + \ + '\n'.join(errors) diff --git a/ros2_ws/src/py_pubsub/test/test_pep257.py b/ros2_ws/src/py_pubsub/test/test_pep257.py new file mode 100644 index 0000000..b234a38 --- /dev/null +++ b/ros2_ws/src/py_pubsub/test/test_pep257.py @@ -0,0 +1,23 @@ +# Copyright 2015 Open Source Robotics Foundation, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from ament_pep257.main import main +import pytest + + +@pytest.mark.linter +@pytest.mark.pep257 +def test_pep257(): + rc = main(argv=['.', 'test']) + assert rc == 0, 'Found code style errors / warnings' diff --git a/ros2_ws/src/py_srvcli/package.xml b/ros2_ws/src/py_srvcli/package.xml new file mode 100644 index 0000000..fcb6512 --- /dev/null +++ b/ros2_ws/src/py_srvcli/package.xml @@ -0,0 +1,15 @@ + + + py_srvcli + 0.0.0 + Python service example + ros + Apache-2.0 + + rclpy + std_msgs + + + ament_python + + \ No newline at end of file diff --git a/ros2_ws/src/py_srvcli/py_srvcli/__init__.py b/ros2_ws/src/py_srvcli/py_srvcli/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/ros2_ws/src/py_srvcli/py_srvcli/add_two_ints_client.py b/ros2_ws/src/py_srvcli/py_srvcli/add_two_ints_client.py new file mode 100644 index 0000000..d816a09 --- /dev/null +++ b/ros2_ws/src/py_srvcli/py_srvcli/add_two_ints_client.py @@ -0,0 +1,32 @@ +import rclpy +from rclpy.node import Node +from example_interfaces.srv import AddTwoInts + +class AddTwoIntsClient(Node): + def __init__(self): + super().__init__('add_two_ints_client') + # 建立 Client 端,參數分別為: + # (使用的interface(資料格式), Service名稱) + self.client = self.create_client(AddTwoInts, 'add_two_ints') + while not self.client.wait_for_service(timeout_sec=1.0): + self.get_logger().info('Waiting for service...') + self.send_request() + + def send_request(self): + req = AddTwoInts.Request() + req.a = 3 + req.b = 5 + # 發送 request + future = self.client.call_async(req) + # 等待 Server 端回傳資料 + rclpy.spin_until_future_complete(self, future) + if future.result(): + self.get_logger().info(f'Result: {future.result().sum}') + +def main(args=None): + rclpy.init(args=args) + node = AddTwoIntsClient() + rclpy.shutdown() + +if __name__ == '__main__': + main() \ No newline at end of file diff --git a/ros2_ws/src/py_srvcli/py_srvcli/add_two_ints_server.py b/ros2_ws/src/py_srvcli/py_srvcli/add_two_ints_server.py new file mode 100644 index 0000000..4957045 --- /dev/null +++ b/ros2_ws/src/py_srvcli/py_srvcli/add_two_ints_server.py @@ -0,0 +1,26 @@ +import rclpy +from rclpy.node import Node +from example_interfaces.srv import AddTwoInts + +class AddTwoIntsServer(Node): + # 初始化 + def __init__(self): + # 'add_two_ints_server' 為 Node名稱,其他照著打 + super().__init__('add_two_ints_server') + # 建立 Service 端,參數分別為: + # (使用的interface(資料格式), Service名稱, 要運行的函式),可以看一下C++版本對照一下 + self.srv = self.create_service(AddTwoInts, 'add_two_ints', self.handle_add) + + def handle_add(self, request, response): + self.get_logger().info(f"Received request: {request.a} + {request.b}") + response.sum = request.a + request.b + return response + +def main(args=None): + rclpy.init(args=args) + node = AddTwoIntsServer() + rclpy.spin(node) + rclpy.shutdown() + +if __name__ == '__main__': + main() \ No newline at end of file diff --git a/ros2_ws/src/py_srvcli/resource/py_srvcli b/ros2_ws/src/py_srvcli/resource/py_srvcli new file mode 100644 index 0000000..e69de29 diff --git a/ros2_ws/src/py_srvcli/setup.cfg b/ros2_ws/src/py_srvcli/setup.cfg new file mode 100644 index 0000000..3683684 --- /dev/null +++ b/ros2_ws/src/py_srvcli/setup.cfg @@ -0,0 +1,4 @@ +[develop] +script_dir=$base/lib/py_srvcli +[install] +install_scripts=$base/lib/py_srvcli diff --git a/ros2_ws/src/py_srvcli/setup.py b/ros2_ws/src/py_srvcli/setup.py new file mode 100644 index 0000000..d911a70 --- /dev/null +++ b/ros2_ws/src/py_srvcli/setup.py @@ -0,0 +1,18 @@ +from setuptools import setup +package_name = 'py_srvcli' + +setup( + name=package_name, + version='0.0.0', + packages=[package_name], + entry_points={ + 'console_scripts': [ + 'add_two_ints_server = py_srvcli.add_two_ints_server:main', + 'add_two_ints_client = py_srvcli.add_two_ints_client:main', + ], + }, + install_requires=['setuptools'], + zip_safe=True, + maintainer='your_name', + description='Example Service/Client in Python', +) \ No newline at end of file diff --git a/ros2_ws/src/py_srvcli/test/test_copyright.py b/ros2_ws/src/py_srvcli/test/test_copyright.py new file mode 100644 index 0000000..97a3919 --- /dev/null +++ b/ros2_ws/src/py_srvcli/test/test_copyright.py @@ -0,0 +1,25 @@ +# Copyright 2015 Open Source Robotics Foundation, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from ament_copyright.main import main +import pytest + + +# Remove the `skip` decorator once the source file(s) have a copyright header +@pytest.mark.skip(reason='No copyright header has been placed in the generated source file.') +@pytest.mark.copyright +@pytest.mark.linter +def test_copyright(): + rc = main(argv=['.', 'test']) + assert rc == 0, 'Found errors' diff --git a/ros2_ws/src/py_srvcli/test/test_flake8.py b/ros2_ws/src/py_srvcli/test/test_flake8.py new file mode 100644 index 0000000..27ee107 --- /dev/null +++ b/ros2_ws/src/py_srvcli/test/test_flake8.py @@ -0,0 +1,25 @@ +# Copyright 2017 Open Source Robotics Foundation, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from ament_flake8.main import main_with_errors +import pytest + + +@pytest.mark.flake8 +@pytest.mark.linter +def test_flake8(): + rc, errors = main_with_errors(argv=[]) + assert rc == 0, \ + 'Found %d code style errors / warnings:\n' % len(errors) + \ + '\n'.join(errors) diff --git a/ros2_ws/src/py_srvcli/test/test_pep257.py b/ros2_ws/src/py_srvcli/test/test_pep257.py new file mode 100644 index 0000000..b234a38 --- /dev/null +++ b/ros2_ws/src/py_srvcli/test/test_pep257.py @@ -0,0 +1,23 @@ +# Copyright 2015 Open Source Robotics Foundation, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from ament_pep257.main import main +import pytest + + +@pytest.mark.linter +@pytest.mark.pep257 +def test_pep257(): + rc = main(argv=['.', 'test']) + assert rc == 0, 'Found code style errors / warnings' diff --git a/ros2_ws/src/python_launch_examples/config/pub_params.yaml b/ros2_ws/src/python_launch_examples/config/pub_params.yaml new file mode 100644 index 0000000..285036a --- /dev/null +++ b/ros2_ws/src/python_launch_examples/config/pub_params.yaml @@ -0,0 +1,9 @@ +cpp_pubsub: +ros__parameters: + message_prefix: "Hello from CPP" # string + publish_frequency: 2.0 # floating + +py_pubsub: +ros__parameters: + message_prefix: "Hello from Python" # string + publish_frequency: 1.0 # floating \ No newline at end of file diff --git a/ros2_ws/src/python_launch_examples/launch/launch_arg_examples.py b/ros2_ws/src/python_launch_examples/launch/launch_arg_examples.py new file mode 100644 index 0000000..a692da8 --- /dev/null +++ b/ros2_ws/src/python_launch_examples/launch/launch_arg_examples.py @@ -0,0 +1,41 @@ +from launch import LaunchDescription +from launch.actions import DeclareLaunchArgument, LogInfo +from launch.substitutions import LaunchConfiguration +from launch_ros.actions import Node + +def generate_launch_description(): + # 宣告兩個 launch arguments: + # use_py_talker: bool,決定是否啟動 py_talker + # prefix: 傳給 py_talker 的 message_prefix 參數 + use_py_talker_arg = DeclareLaunchArgument( + 'use_py_talker', + default_value='true', + description='Whether to launch py_talker node' + ) + prefix_arg = DeclareLaunchArgument( + 'prefix', + default_value='Hello from launch args!', + description='Message prefix parameter for py_talker' + ) + + use_py_talker = LaunchConfiguration('use_py_talker') + prefix = LaunchConfiguration('prefix') + + # 定義節點,但包在條件中,只有 use_py_talker 是 true 時才啟動 + py_talker_node = Node( + package='py_pubsub', + executable='talker', + name='py_talker_arg', + output='screen', + parameters=[{'message_prefix': prefix}] + ) + + return LaunchDescription([ + use_py_talker_arg, + prefix_arg, + LogInfo(msg=['use_py_talker = ', use_py_talker]), + LogInfo(msg=['prefix = ', prefix]), + # 使用 Python 條件判斷來控制是否加入節點 + # LaunchDescription 不支援 if else 直接加入節點,所以用 Condition 或 Python API 動態控制 + py_talker_node if use_py_talker.perform({}) == 'true' else None + ]) \ No newline at end of file diff --git a/ros2_ws/src/python_launch_examples/launch/python_examples_launch.py b/ros2_ws/src/python_launch_examples/launch/python_examples_launch.py new file mode 100644 index 0000000..d1f850c --- /dev/null +++ b/ros2_ws/src/python_launch_examples/launch/python_examples_launch.py @@ -0,0 +1,21 @@ +from launch import LaunchDescription +from launch_ros.actions import Node + +def generate_launch_description(): + return LaunchDescription([ + # C++ Pub-Sub + Node(package='cpp_pubsub', executable='talker', name='cpp_talker', output='screen'), + Node(package='cpp_pubsub', executable='listener', name='cpp_listener', output='screen'), + + # Python Pub-Sub + Node(package='py_pubsub', executable='talker', name='py_talker', output='screen'), + Node(package='py_pubsub', executable='listener', name='py_listener', output='screen'), + + # C++ Service + Node(package='cpp_srvcli', executable='add_two_ints_server', name='cpp_srv_server', output='screen'), + Node(package='cpp_srvcli', executable='add_two_ints_client', name='cpp_srv_client', output='screen'), + + # Python Service + Node(package='py_srvcli', executable='add_two_ints_server', name='py_srv_server', output='screen'), + Node(package='py_srvcli', executable='add_two_ints_client', name='py_srv_client', output='screen'), + ]) \ No newline at end of file diff --git a/ros2_ws/src/python_launch_examples/launch/talker_with_params.py b/ros2_ws/src/python_launch_examples/launch/talker_with_params.py new file mode 100644 index 0000000..5a6614e --- /dev/null +++ b/ros2_ws/src/python_launch_examples/launch/talker_with_params.py @@ -0,0 +1,20 @@ +from launch import LaunchDescription +from launch_ros.actions import Node + +def generate_launch_description(): + return LaunchDescription([ + Node( + package='cpp_pubsub', + executable='talker', + name='cpp_talker_param', + parameters=['config/pub_params.yaml'], #參數採用config資料夾中的pub_params.yaml + output='screen' + ), + Node( + package='py_pubsub', + executable='talker', + name='py_talker_param', + parameters=['config/pub_params.yaml'], #參數採用config資料夾中的pub_params.yaml + output='screen' + ) + ]) \ No newline at end of file diff --git a/ros2_ws/src/python_launch_examples/setup.py b/ros2_ws/src/python_launch_examples/setup.py index 583e854..24ff591 100644 --- a/ros2_ws/src/python_launch_examples/setup.py +++ b/ros2_ws/src/python_launch_examples/setup.py @@ -1,25 +1,20 @@ -from setuptools import find_packages, setup +from setuptools import setup package_name = 'python_launch_examples' setup( name=package_name, version='0.0.0', - packages=find_packages(exclude=['test']), + packages=[package_name], data_files=[ - ('share/ament_index/resource_index/packages', - ['resource/' + package_name]), ('share/' + package_name, ['package.xml']), + ('share/' + package_name + '/launch', ['launch/python_examples_launch.py']), + ('share/ament_index/resource_index/packages', ['resource/' + package_name]), ], install_requires=['setuptools'], zip_safe=True, - maintainer='user', - maintainer_email='user@todo.todo', - description='TODO: Package description', - license='TODO: License declaration', - tests_require=['pytest'], - entry_points={ - 'console_scripts': [ - ], - }, -) + maintainer='your_name', + maintainer_email='your_email@example.com', + description='A launch package for pub-sub, service and action examples', + license='Apache License 2.0', +) \ No newline at end of file From f4d784de9d6a77f442b084f0f075a9e4d19922df Mon Sep 17 00:00:00 2001 From: TWang Date: Sun, 13 Jul 2025 22:57:26 +0800 Subject: [PATCH 05/20] modify: readme --- README.md | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 3df15b6..e3d46e5 100755 --- a/README.md +++ b/README.md @@ -1,11 +1,9 @@ # how to use ### build docker environment - `cd Winter-Tutorial/docker` -- `docker build -t dit/tutorial-ws-noetic:latest .` - `docker compose up` -- `docker start tutorial-ws-ros1` +- `docker start ros2_tutorial` ### open work space -- Attach Visual Studio Code -- `cd tutorial_ws` -- `source /opt/ros/noetic/setup.bash` -- `catkin_make` +- `docker exec -it ros2_tutorial` +- `cd ~/ros2_ws` +- `colcon build` From cc32610d1310e824739f296b1226f86b0ed83b37 Mon Sep 17 00:00:00 2001 From: TWang Date: Fri, 18 Jul 2025 14:49:36 +0800 Subject: [PATCH 06/20] modify: base image fix: example interface --- docker/Dockerfile | 2 +- ros2_ws/src/example_interfaces/CMakeLists.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docker/Dockerfile b/docker/Dockerfile index 5a094c1..b141f7f 100755 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -1,4 +1,4 @@ -FROM osrf/ros:humble-desktop-full +FROM osrf/ros:humble-desktop LABEL shell=bash diff --git a/ros2_ws/src/example_interfaces/CMakeLists.txt b/ros2_ws/src/example_interfaces/CMakeLists.txt index 0ee243b..db3f184 100644 --- a/ros2_ws/src/example_interfaces/CMakeLists.txt +++ b/ros2_ws/src/example_interfaces/CMakeLists.txt @@ -14,7 +14,7 @@ find_package(builtin_interfaces REQUIRED) find_package(rosidl_default_generators REQUIRED) rosidl_generate_interfaces(example_interfaces - "action/Fibonacci.action" + "srv/AddTwoInts.srv" DEPENDENCIES geometry_msgs action_msgs std_msgs builtin_interfaces ) From 55d3fe4d80ef139c7e250b45eaeea3c6adb2a67f Mon Sep 17 00:00:00 2001 From: robert Date: Fri, 26 Dec 2025 01:10:03 +0800 Subject: [PATCH 07/20] add learning_tf2_cpp package --- ros2_ws/src/learning_tf2_cpp/CMakeLists.txt | 62 ++++++ ros2_ws/src/learning_tf2_cpp/LICENSE | 202 ++++++++++++++++++ .../launch/turtle_tf2_demo.launch.py | 43 ++++ ros2_ws/src/learning_tf2_cpp/package.xml | 24 +++ .../src/turtle_tf2_broadcaster.cpp | 78 +++++++ .../src/turtle_tf2_listener.cpp | 135 ++++++++++++ 6 files changed, 544 insertions(+) create mode 100755 ros2_ws/src/learning_tf2_cpp/CMakeLists.txt create mode 100755 ros2_ws/src/learning_tf2_cpp/LICENSE create mode 100755 ros2_ws/src/learning_tf2_cpp/launch/turtle_tf2_demo.launch.py create mode 100755 ros2_ws/src/learning_tf2_cpp/package.xml create mode 100755 ros2_ws/src/learning_tf2_cpp/src/turtle_tf2_broadcaster.cpp create mode 100755 ros2_ws/src/learning_tf2_cpp/src/turtle_tf2_listener.cpp diff --git a/ros2_ws/src/learning_tf2_cpp/CMakeLists.txt b/ros2_ws/src/learning_tf2_cpp/CMakeLists.txt new file mode 100755 index 0000000..f31064d --- /dev/null +++ b/ros2_ws/src/learning_tf2_cpp/CMakeLists.txt @@ -0,0 +1,62 @@ +cmake_minimum_required(VERSION 3.8) +project(learning_tf2_cpp) + +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(geometry_msgs REQUIRED) +find_package(rclcpp REQUIRED) +find_package(tf2 REQUIRED) +find_package(tf2_ros REQUIRED) +find_package(turtlesim REQUIRED) + +add_executable(turtle_tf2_broadcaster src/turtle_tf2_broadcaster.cpp) +ament_target_dependencies( + turtle_tf2_broadcaster + geometry_msgs + rclcpp + tf2 + tf2_ros + turtlesim +) + +install(TARGETS + turtle_tf2_broadcaster + DESTINATION lib/${PROJECT_NAME}) + + +add_executable(turtle_tf2_listener src/turtle_tf2_listener.cpp) +ament_target_dependencies( + turtle_tf2_listener + geometry_msgs + rclcpp + tf2 + tf2_ros + turtlesim +) + +install(TARGETS + turtle_tf2_listener + DESTINATION lib/${PROJECT_NAME}) + +install( + DIRECTORY launch + DESTINATION share/${PROJECT_NAME} +) + +if(BUILD_TESTING) + find_package(ament_lint_auto REQUIRED) + # the following line skips the linter which checks for copyrights + # comment the line when a copyright and license is added to all source files + set(ament_cmake_copyright_FOUND TRUE) + # the following line skips cpplint (only works in a git repo) + # comment the line when this package is in a git repo and when + # a copyright and license is added to all source files + set(ament_cmake_cpplint_FOUND TRUE) + ament_lint_auto_find_test_dependencies() +endif() + +ament_package() diff --git a/ros2_ws/src/learning_tf2_cpp/LICENSE b/ros2_ws/src/learning_tf2_cpp/LICENSE new file mode 100755 index 0000000..d645695 --- /dev/null +++ b/ros2_ws/src/learning_tf2_cpp/LICENSE @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/ros2_ws/src/learning_tf2_cpp/launch/turtle_tf2_demo.launch.py b/ros2_ws/src/learning_tf2_cpp/launch/turtle_tf2_demo.launch.py new file mode 100755 index 0000000..f096f62 --- /dev/null +++ b/ros2_ws/src/learning_tf2_cpp/launch/turtle_tf2_demo.launch.py @@ -0,0 +1,43 @@ +from launch import LaunchDescription +from launch.actions import DeclareLaunchArgument +from launch.substitutions import LaunchConfiguration + +from launch_ros.actions import Node + + +def generate_launch_description(): + return LaunchDescription([ + Node( + package='turtlesim', + executable='turtlesim_node', + name='sim' + ), + Node( + package='learning_tf2_cpp', + executable='turtle_tf2_broadcaster', + name='broadcaster1', + parameters=[ + {'turtlename': 'turtle1'} + ] + ), + DeclareLaunchArgument( + 'target_frame', default_value='turtle1', + description='Target frame name.' + ), + Node( + package='learning_tf2_cpp', + executable='turtle_tf2_broadcaster', + name='broadcaster2', + parameters=[ + {'turtlename': 'turtle2'} + ] + ), + Node( + package='learning_tf2_cpp', + executable='turtle_tf2_listener', + name='listener', + parameters=[ + {'target_frame': LaunchConfiguration('target_frame')} + ] + ) + ]) \ No newline at end of file diff --git a/ros2_ws/src/learning_tf2_cpp/package.xml b/ros2_ws/src/learning_tf2_cpp/package.xml new file mode 100755 index 0000000..a78579d --- /dev/null +++ b/ros2_ws/src/learning_tf2_cpp/package.xml @@ -0,0 +1,24 @@ + + + + learning_tf2_cpp + 0.0.0 + TODO: Package description + user + Apache-2.0 + + ament_cmake + + geometry_msgs + rclcpp + tf2 + tf2_ros + turtlesim + + ament_lint_auto + ament_lint_common + + + ament_cmake + + diff --git a/ros2_ws/src/learning_tf2_cpp/src/turtle_tf2_broadcaster.cpp b/ros2_ws/src/learning_tf2_cpp/src/turtle_tf2_broadcaster.cpp new file mode 100755 index 0000000..b639f43 --- /dev/null +++ b/ros2_ws/src/learning_tf2_cpp/src/turtle_tf2_broadcaster.cpp @@ -0,0 +1,78 @@ +#include +#include +#include +#include + +#include "geometry_msgs/msg/transform_stamped.hpp" +#include "rclcpp/rclcpp.hpp" +#include "tf2/LinearMath/Quaternion.h" +#include "tf2_ros/transform_broadcaster.h" +#include "turtlesim/msg/pose.hpp" + +class FramePublisher : public rclcpp::Node +{ +public: + FramePublisher() + : Node("turtle_tf2_frame_publisher") + { + // Declare and acquire `turtlename` parameter + turtlename_ = this->declare_parameter("turtlename", "turtle"); + + // Initialize the transform broadcaster + tf_broadcaster_ = + std::make_unique(*this); + + // Subscribe to a turtle{1}{2}/pose topic and call handle_turtle_pose + // callback function on each message + std::ostringstream stream; + stream << "/" << turtlename_.c_str() << "/pose"; + std::string topic_name = stream.str(); + + subscription_ = this->create_subscription( + topic_name, 10, + std::bind(&FramePublisher::handle_turtle_pose, this, std::placeholders::_1)); + } + +private: + void handle_turtle_pose(const std::shared_ptr msg) + { + geometry_msgs::msg::TransformStamped t; + + // Read message content and assign it to + // corresponding tf variables + t.header.stamp = this->get_clock()->now(); + t.header.frame_id = "world"; + t.child_frame_id = turtlename_.c_str(); + + // Turtle only exists in 2D, thus we get x and y translation + // coordinates from the message and set the z coordinate to 0 + t.transform.translation.x = msg->x; + t.transform.translation.y = msg->y; + t.transform.translation.z = 0.0; + + // For the same reason, turtle can only rotate around one axis + // and this why we set rotation in x and y to 0 and obtain + // rotation in z axis from the message + tf2::Quaternion q; + q.setRPY(0, 0, msg->theta); + t.transform.rotation.x = q.x(); + t.transform.rotation.y = q.y(); + t.transform.rotation.z = q.z(); + t.transform.rotation.w = q.w(); + + // Send the transformation + tf_broadcaster_->sendTransform(t); + } + + rclcpp::Subscription::SharedPtr subscription_; + std::unique_ptr tf_broadcaster_; + std::string turtlename_; +}; + +int main(int argc, char * argv[]) +{ + rclcpp::init(argc, argv); + rclcpp::spin(std::make_shared()); + rclcpp::shutdown(); + return 0; +} \ No newline at end of file diff --git a/ros2_ws/src/learning_tf2_cpp/src/turtle_tf2_listener.cpp b/ros2_ws/src/learning_tf2_cpp/src/turtle_tf2_listener.cpp new file mode 100755 index 0000000..54693cb --- /dev/null +++ b/ros2_ws/src/learning_tf2_cpp/src/turtle_tf2_listener.cpp @@ -0,0 +1,135 @@ +#include +#include +#include +#include + +#include "geometry_msgs/msg/transform_stamped.hpp" +#include "geometry_msgs/msg/twist.hpp" +#include "rclcpp/rclcpp.hpp" +#include "tf2/exceptions.h" +#include "tf2_ros/transform_listener.h" +#include "tf2_ros/buffer.h" +#include "turtlesim/srv/spawn.hpp" + +using namespace std::chrono_literals; + +class FrameListener : public rclcpp::Node +{ +public: + FrameListener() + : Node("turtle_tf2_frame_listener"), + turtle_spawning_service_ready_(false), + turtle_spawned_(false) + { + // Declare and acquire `target_frame` parameter + target_frame_ = this->declare_parameter("target_frame", "turtle1"); + + tf_buffer_ = + std::make_unique(this->get_clock()); + tf_listener_ = + std::make_shared(*tf_buffer_); + + // Create a client to spawn a turtle + spawner_ = + this->create_client("spawn"); + + // Create turtle2 velocity publisher + publisher_ = + this->create_publisher("turtle2/cmd_vel", 1); + + // Call on_timer function every second + timer_ = this->create_wall_timer( + 1s, std::bind(&FrameListener::on_timer, this)); + } + +private: + void on_timer() + { + // Store frame names in variables that will be used to + // compute transformations + std::string fromFrameRel = target_frame_.c_str(); + std::string toFrameRel = "turtle2"; + + if (turtle_spawning_service_ready_) { + if (turtle_spawned_) { + geometry_msgs::msg::TransformStamped t; + + // Look up for the transformation between target_frame and turtle2 frames + // and send velocity commands for turtle2 to reach target_frame + try { + t = tf_buffer_->lookupTransform( + toFrameRel, fromFrameRel, + tf2::TimePointZero); + } catch (const tf2::TransformException & ex) { + RCLCPP_INFO( + this->get_logger(), "Could not transform %s to %s: %s", + toFrameRel.c_str(), fromFrameRel.c_str(), ex.what()); + return; + } + + geometry_msgs::msg::Twist msg; + + static const double scaleRotationRate = 1.0; + msg.angular.z = scaleRotationRate * atan2( + t.transform.translation.y, + t.transform.translation.x); + + static const double scaleForwardSpeed = 0.5; + msg.linear.x = scaleForwardSpeed * sqrt( + pow(t.transform.translation.x, 2) + + pow(t.transform.translation.y, 2)); + + publisher_->publish(msg); + } else { + RCLCPP_INFO(this->get_logger(), "Successfully spawned"); + turtle_spawned_ = true; + } + } else { + // Check if the service is ready + if (spawner_->service_is_ready()) { + // Initialize request with turtle name and coordinates + // Note that x, y and theta are defined as floats in turtlesim/srv/Spawn + auto request = std::make_shared(); + request->x = 4.0; + request->y = 2.0; + request->theta = 0.0; + request->name = "turtle2"; + + // Call request + using ServiceResponseFuture = + rclcpp::Client::SharedFuture; + auto response_received_callback = [this](ServiceResponseFuture future) { + auto result = future.get(); + if (strcmp(result->name.c_str(), "turtle2") == 0) { + turtle_spawning_service_ready_ = true; + } else { + RCLCPP_ERROR(this->get_logger(), "Service callback result mismatch"); + } + }; + auto result = spawner_->async_send_request(request, response_received_callback); + } else { + RCLCPP_INFO(this->get_logger(), "Service is not ready"); + } + } + } + + // Boolean values to store the information + // if the service for spawning turtle is available + bool turtle_spawning_service_ready_; + // if the turtle was successfully spawned + bool turtle_spawned_; + rclcpp::Client::SharedPtr spawner_{nullptr}; + rclcpp::TimerBase::SharedPtr timer_{nullptr}; + rclcpp::Publisher::SharedPtr publisher_{nullptr}; + std::shared_ptr tf_listener_{nullptr}; + std::unique_ptr tf_buffer_; + std::string target_frame_; +}; + +int main(int argc, char * argv[]) +{ + rclcpp::init(argc, argv); + rclcpp::spin(std::make_shared()); + rclcpp::shutdown(); + return 0; +} \ No newline at end of file From e7d1cdc7c77d32c719f8615dfbade5ac2808e052 Mon Sep 17 00:00:00 2001 From: robert Date: Fri, 26 Dec 2025 11:01:31 +0800 Subject: [PATCH 08/20] Modify: parent frame to map --- ros2_ws/src/learning_tf2_cpp/src/turtle_tf2_broadcaster.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ros2_ws/src/learning_tf2_cpp/src/turtle_tf2_broadcaster.cpp b/ros2_ws/src/learning_tf2_cpp/src/turtle_tf2_broadcaster.cpp index b639f43..1571098 100755 --- a/ros2_ws/src/learning_tf2_cpp/src/turtle_tf2_broadcaster.cpp +++ b/ros2_ws/src/learning_tf2_cpp/src/turtle_tf2_broadcaster.cpp @@ -41,7 +41,7 @@ class FramePublisher : public rclcpp::Node // Read message content and assign it to // corresponding tf variables t.header.stamp = this->get_clock()->now(); - t.header.frame_id = "world"; + t.header.frame_id = "map"; t.child_frame_id = turtlename_.c_str(); // Turtle only exists in 2D, thus we get x and y translation From 49c796095fbc5286e7efc947f662278aa51c2eba Mon Sep 17 00:00:00 2001 From: chuangtsh Date: Sun, 28 Dec 2025 16:23:18 +0800 Subject: [PATCH 09/20] finish draft for teaching materials --- cpp_oop/Makefile | 38 +++++++++++++ cpp_oop/README.md | 110 +++++++++++++++++++++++++++++++++++++ cpp_oop/include/Airplane.h | 23 ++++++++ cpp_oop/include/Car.h | 24 ++++++++ cpp_oop/include/Vehicle.h | 60 ++++++++++++++++++++ cpp_oop/src/Airplane.cpp | 23 ++++++++ cpp_oop/src/Car.cpp | 24 ++++++++ cpp_oop/src/Vehicle.cpp | 65 ++++++++++++++++++++++ cpp_oop/src/main.cpp | 54 ++++++++++++++++++ 9 files changed, 421 insertions(+) create mode 100644 cpp_oop/Makefile create mode 100644 cpp_oop/README.md create mode 100644 cpp_oop/include/Airplane.h create mode 100644 cpp_oop/include/Car.h create mode 100644 cpp_oop/include/Vehicle.h create mode 100644 cpp_oop/src/Airplane.cpp create mode 100644 cpp_oop/src/Car.cpp create mode 100644 cpp_oop/src/Vehicle.cpp create mode 100644 cpp_oop/src/main.cpp diff --git a/cpp_oop/Makefile b/cpp_oop/Makefile new file mode 100644 index 0000000..fe0fdda --- /dev/null +++ b/cpp_oop/Makefile @@ -0,0 +1,38 @@ +# Makefile for C++ OOP Tutorial +# Demonstrates header files, source files, enum, and extern + +CXX = g++ +CXXFLAGS = -Wall -Wextra -std=c++14 -I./include +TARGET = vehicle_demo + +# Source files +SRCS = src/Vehicle.cpp src/Car.cpp src/Airplane.cpp src/main.cpp + +# Object files +OBJS = $(SRCS:.cpp=.o) + +# Default target +all: $(TARGET) + +# Link object files to create executable +$(TARGET): $(OBJS) + $(CXX) $(CXXFLAGS) -o $(TARGET) $(OBJS) + @echo "Build complete! Run with: ./$(TARGET)" + +# Compile source files to object files +%.o: %.cpp + $(CXX) $(CXXFLAGS) -c $< -o $@ + +# Run the program +run: $(TARGET) + ./$(TARGET) + +# Clean build files +clean: + rm -f $(OBJS) $(TARGET) + @echo "Clean complete!" + +# Rebuild everything +rebuild: clean all + +.PHONY: all run clean rebuild diff --git a/cpp_oop/README.md b/cpp_oop/README.md new file mode 100644 index 0000000..7516351 --- /dev/null +++ b/cpp_oop/README.md @@ -0,0 +1,110 @@ +# C++ OOP Tutorial + +2025 Winter Turtorial Supplementary C++, OOP的範例程式 + +## Topics Covered + +### 1. OOP (Object-Oriented Programming) +- **Encapsulation**: Private members in `Car` and `Airplane` classes +- **Inheritance**: `Car` and `Airplane` inherit from `Vehicle` +- **Polymorphism**: Virtual function `move()` with different implementations + +### 2. Header File (.h) & Source File (.cpp) + +#### Header File (`.h`) +- 放 declarations(宣告)、include、extern +- 別人看 header file 就可以知道有什麼功能 +- 統一放在資料夾`include` + +例如: `Vehicle.h`, `Car.h`, `Airplane.h` + +#### Source File (`.cpp`) +- 實際地把 header file 中的東西 implement 出來 +- 放在資料夾`src` +- `main.cpp`是主程式,因為只有`main()`所以我沒寫`main.h` + +例如: `Vehicle.cpp`, `Car.cpp`, `Airplane.cpp` + + +### 3. enum (Enumeration) + +enum 是一個 user-defined type,代表一組有限的命名整數。 + + +**用途:** +- 劃分不同的類別或狀態 +- 讓程式碼更容易閱讀和維護 +- 範例 1: Color +- 範例 2: VehicleStatus +- 都在`Vehicle.h` + + +### 4. Global Variable & extern + +**重要規則:** +- extern 只要宣告一次 +- 接下來都用 include header file 的方式 +- **初始化只要一次**(多個程式嘗試初始化會造成問題!) + +**本專案範例:** +- `Vehicle.h`: 宣告 `extern int global_vehicle_count;` +- `Vehicle.cpp`: 初始化 `int global_vehicle_count = 0;` +- `main.cpp`: 透過 include `Car.h` (which includes `Vehicle.h`) 使用這個變數 + +## Project Structure + +``` +cpp_oop/ +├── include/ +│ ├── Vehicle.h # base class + enum + extern 宣告(declaration) +│ ├── Car.h # Car class declaration +│ └── Airplane.h # Airplane declaration +├── src/ +│ ├── Vehicle.cpp # Vehicle 實作(implementation) + global 變數初始化 +│ ├── Car.cpp # Car implementation +│ ├── Airplane.cpp # Airplane implementation +│ └── main.cpp # 主程式 +├── Makefile # 編譯腳本 +└── README.md # 本檔案 +``` + +## How to Build and Run + +- 由於有多個檔案需要編譯,我叫AI寫了`Makefile` +- `Makefile`可以包含編譯的指令,給`make`工具使用 +- 學ROS時會有`CMakeLists.txt`,功能比Makefile更多,編譯過程中會自動生成`Makefile` +- 不需要打開`Makefile` +- 想要自己用`g++`之類的編譯請問AI + +### Entering the directory in docker container +```bash +# 以免有人本地端無法編譯C++,這邊的指令是在container裡編譯 +# 編譯管道與ROS無關,只是單純的 make工具 與 g++ compiler + +### Build docker environement(Same as the homepage READE.md) +- `cd docker` +- docker compose up -d +- *Container ros2_tutorial Running* # expected o/p, dont type +- docker exec -it ros2_tutorial bash + +### Enter cpp_oop directory +cd cpp_oop +``` + +### Using Makefile +```bash +# type the command in terminal + +# Build the project +make + +# Build and run +make run + +# Clean build files +make clean + +# Rebuild from scratch +make rebuild +``` + diff --git a/cpp_oop/include/Airplane.h b/cpp_oop/include/Airplane.h new file mode 100644 index 0000000..ff1326f --- /dev/null +++ b/cpp_oop/include/Airplane.h @@ -0,0 +1,23 @@ +// to avoid multiple include (include guard) +#ifndef AIRPLANE_H +#define AIRPLANE_H + +#include "Vehicle.h" + +// Airplane is-a Vehicle, so Airplane inherits from Vehicle just like Car +class Airplane : public Vehicle { +private: + int altitude; + +public: + // Constructor of Airplane + Airplane(int fuel, int max_speed, int altitude, Color color = BLUE); + + // 除了inherited from Vehicle的public getter/setter function,Airplane還有自己的函式 + void climb(int meters); + int getAltitude() const; + + std::string move() const override; // declare with override to enable polymorphism +}; + +#endif diff --git a/cpp_oop/include/Car.h b/cpp_oop/include/Car.h new file mode 100644 index 0000000..5a200e0 --- /dev/null +++ b/cpp_oop/include/Car.h @@ -0,0 +1,24 @@ +// to avoid multiple include (include guard) +#ifndef CAR_H +#define CAR_H + +#include "Vehicle.h" // to use class declaration of Vehicle + + +// Car is-a Vehicle, so Car inherits from Vehicle +class Car : public Vehicle { +private: + int seat_count; // encapsulated + +public: + // Constructor with color parameter + Car(int fuel, int max_speed, int seat_count, Color color = RED); + + // 除了inherited from Vehicle的public getter/setter function,Car還有自己的函式 + void drive(); + int getSeatCount() const; + + std::string move() const override; // declare with override to enable polymorphism +}; + +#endif diff --git a/cpp_oop/include/Vehicle.h b/cpp_oop/include/Vehicle.h new file mode 100644 index 0000000..16c9ac3 --- /dev/null +++ b/cpp_oop/include/Vehicle.h @@ -0,0 +1,60 @@ +// to avoid multiple include (include guard) +#ifndef VEHICLE_H +#define VEHICLE_H + +#include + +// enum: a user-defined type that represents a finite set of named integer +enum Color { + RED, // 預設 RED==0 + GREEN, // GREEN==1 + BLUE, // BLUE==2 + YELLOW, + BLACK, + WHITE +}; + +// 用來表示交通工具的狀態 +enum VehicleStatus { + IDLE = 0, + MOVING = 1, + REFUELING = 2, + MAINTENANCE = 3 +}; + +// 在.h宣告變數extern但不初始化 +extern int global_vehicle_count; + +// 交通工具(抽象概念) +class Vehicle { +protected: + int fuel; // 燃料 + int max_speed; // 最大速度 + Color color; // 顏色 (使用 enum) + VehicleStatus status; // 狀態 (使用 enum) + +public: + Vehicle(int fuel, int max_speed, Color color = WHITE); + virtual ~Vehicle(); // destructor + + void refuel(int amount); + + // getter function (get the values of these variables but not directly accessing them) + int getFuel() const; // const -> this function should not modify the variable in the class + int getMaxSpeed() const; + Color getColor() const; + VehicleStatus getStatus() const; + + // setter function (set the values of specified varaible) + void setStatus(VehicleStatus new_status); + + // demo how to use enum for if-statement + std::string getColorName() const; + + // Polymorphism interface, declared virtual + // this is pure virtual function(so the child of Vehicle must declare own move function) + // pure virtual function does not need function definition here + virtual std::string move() const = 0; +}; + +#endif diff --git a/cpp_oop/src/Airplane.cpp b/cpp_oop/src/Airplane.cpp new file mode 100644 index 0000000..e738aa9 --- /dev/null +++ b/cpp_oop/src/Airplane.cpp @@ -0,0 +1,23 @@ +#include "Airplane.h" + +Airplane::Airplane(int fuel, int max_speed, int altitude, Color color) + : Vehicle(fuel, max_speed, color), altitude(altitude) {} + +void Airplane::climb(int meters) { + if (fuel > 0) { + status = MOVING; // 使用 enum 設定狀態 + altitude += meters; + fuel--; + } + else { + status = IDLE; + } +} + +int Airplane::getAltitude() const { + return altitude; +} + +std::string Airplane::move() const { + return "Airplane flies in the sky"; +} diff --git a/cpp_oop/src/Car.cpp b/cpp_oop/src/Car.cpp new file mode 100644 index 0000000..fb61fd5 --- /dev/null +++ b/cpp_oop/src/Car.cpp @@ -0,0 +1,24 @@ +#include "Car.h" +// no need to include Vehicle.h, since Car.h has included Vehicle.h + +Car::Car(int fuel, int max_speed, int seat_count, Color color) + : Vehicle(fuel, max_speed, color), seat_count(seat_count) {} + // Car's constructor must call the constructor of the parent(Vehicle) + +void Car::drive() { + if (fuel > 0) { + status = MOVING; // 使用 enum 設定狀態 + fuel--; // protected member + } + else { + status = IDLE; + } +} + +int Car::getSeatCount() const { + return seat_count; +} + +std::string Car::move() const { + return "Car drives on the road"; +} diff --git a/cpp_oop/src/Vehicle.cpp b/cpp_oop/src/Vehicle.cpp new file mode 100644 index 0000000..9dba07b --- /dev/null +++ b/cpp_oop/src/Vehicle.cpp @@ -0,0 +1,65 @@ +#include "Vehicle.h" +// Vehicle.cpp hava the function definition +// and variable initialization +// corresponding to Vehicle.h + +// 初始化extern過的variable +int global_vehicle_count = 0; + +Vehicle::Vehicle(int fuel, int max_speed, Color color) + : fuel(fuel), max_speed(max_speed), color(color), status(IDLE) { + global_vehicle_count++; // 每創建一個交通工具,總數+1 +} + +Vehicle::~Vehicle() { + global_vehicle_count--; // 交通工具被刪除時,總數-1 +} + +void Vehicle::refuel(int amount) { + status = REFUELING; + fuel += amount; + status = IDLE; +} + +int Vehicle::getFuel() const { + return fuel; +} + +int Vehicle::getMaxSpeed() const { + return max_speed; +} + +Color Vehicle::getColor() const { + return color; +} + +VehicleStatus Vehicle::getStatus() const { + return status; +} + +void Vehicle::setStatus(VehicleStatus new_status) { + status = new_status; +} + +// ===== 使用 enum 來判斷 ===== +std::string Vehicle::getColorName() const { + if (color == Color::RED) { + return "Red"; + } + else if (color == Color::GREEN) { + return "Green"; + } + else if (color == Color::BLUE) { + return "Blue"; + } + else if (color == Color::YELLOW) { + return "Yellow"; + } + else if (color == Color::BLACK) { + return "Black"; + } + else if (color == Color::WHITE) { + return "White"; + } + return "Unknown"; +} diff --git a/cpp_oop/src/main.cpp b/cpp_oop/src/main.cpp new file mode 100644 index 0000000..ff1d439 --- /dev/null +++ b/cpp_oop/src/main.cpp @@ -0,0 +1,54 @@ +#include +#include +#include "Car.h" +#include "Airplane.h" + +using namespace std; + +int main() { + cout << "===== C++ OOP Tutorial =====" << endl; + cout << "Topics: Header/Source files, enum, extern, OOP" << endl << endl; + + // ===== Topic 4: extern 全域變數 ===== + cout << "Global vehicle count: " << global_vehicle_count << endl; + + // ===== Topic 3: enum 使用 ===== + Color my_color = Color::RED; + if (my_color == Color::RED) { + cout << "Color is RED!" << endl; + } + + // ===== OOP: Polymorphism ===== + Car* v1 = new Car(100, 180, 5, Color::RED); + Airplane* v2 = new Airplane(300, 900, 1000, Color::BLUE); + // 也可以用smart pointer + // unique_ptr v1 = make_unique(100, 180, 5, Color::RED); + // unique_ptr v2 = make_unique(300, 900, 1000, Color::BLUE); + + cout << v1->move() << endl; + cout << v2->move() << endl; + + cout << "Car color: " << v1->getColorName() << endl; + cout << "Airplane color: " << v2->getColorName() << endl; + + cout << "Global count: " << global_vehicle_count << endl; + + // this method is inherited from Vehicle + cout << "Car Fuel: " << v1->getFuel() << endl; + cout << "Airplane maxSpeed: " << v2->getMaxSpeed() << endl; + + // these methods are additional function + cout << "Car SeatCount: " << v1->getSeatCount() << endl; + cout << "Airplane Altitude: " << v2->getAltitude() << endl; + + + // try on your own to call Car, Airplane's other function + + + // if use smart pointer, no need to delete, + // since system automatically release memory + delete v1; + delete v2; + + return 0; +} From 5a98ef61516c4311411518fa735839452fe9ebc2 Mon Sep 17 00:00:00 2001 From: chuangtsh Date: Sun, 28 Dec 2025 16:28:45 +0800 Subject: [PATCH 10/20] fix some comments --- cpp_oop/src/main.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/cpp_oop/src/main.cpp b/cpp_oop/src/main.cpp index ff1d439..bd9bcbb 100644 --- a/cpp_oop/src/main.cpp +++ b/cpp_oop/src/main.cpp @@ -9,16 +9,16 @@ int main() { cout << "===== C++ OOP Tutorial =====" << endl; cout << "Topics: Header/Source files, enum, extern, OOP" << endl << endl; - // ===== Topic 4: extern 全域變數 ===== + // to see the initialization of extern global variable cout << "Global vehicle count: " << global_vehicle_count << endl; - // ===== Topic 3: enum 使用 ===== + // enum simple usage Color my_color = Color::RED; if (my_color == Color::RED) { - cout << "Color is RED!" << endl; + cout << "my Color is RED!" << endl; } - // ===== OOP: Polymorphism ===== + // OOP demo Car* v1 = new Car(100, 180, 5, Color::RED); Airplane* v2 = new Airplane(300, 900, 1000, Color::BLUE); // 也可以用smart pointer From 9214a3f56896d5c83d5e5471280c5f884b9f51cc Mon Sep 17 00:00:00 2001 From: robert Date: Wed, 31 Dec 2025 22:08:15 +0800 Subject: [PATCH 11/20] Add: URDF file for gazebo teaching --- .../src/rviz_gazebo_demo/urdf/diff_robot.urdf | 124 ++++++++++++++++++ 1 file changed, 124 insertions(+) create mode 100755 ros2_ws/src/rviz_gazebo_demo/urdf/diff_robot.urdf diff --git a/ros2_ws/src/rviz_gazebo_demo/urdf/diff_robot.urdf b/ros2_ws/src/rviz_gazebo_demo/urdf/diff_robot.urdf new file mode 100755 index 0000000..b0f1518 --- /dev/null +++ b/ros2_ws/src/rviz_gazebo_demo/urdf/diff_robot.urdf @@ -0,0 +1,124 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + left_wheel_joint + right_wheel_joint + + + 0.30 + 0.10 + + + true + true false + + + odom + base_link + + + cmd_vel + odom + + + + + From d4e1889b474fbf3b13028a8a14bfd15b2696b40d Mon Sep 17 00:00:00 2001 From: TWang Date: Wed, 11 Mar 2026 01:33:51 +0800 Subject: [PATCH 12/20] modify: env variable for display --- docker/compose.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker/compose.yaml b/docker/compose.yaml index 9b4a9ad..1a2cd49 100644 --- a/docker/compose.yaml +++ b/docker/compose.yaml @@ -15,7 +15,7 @@ services: network_mode: host working_dir: /home/user/ros_tutorial environment: - - DISPLAY=$DISPLAY + - DISPLAY=host.docker.internal:0.0 - XAUTHORITY=/root/.Xauthority - ROS_WS=/home/user/ros_tutorial/ros2_ws volumes: From 81c012d94ee6ccabc39963c3f766ab602443e9b5 Mon Sep 17 00:00:00 2001 From: TWang Date: Wed, 11 Mar 2026 01:34:27 +0800 Subject: [PATCH 13/20] add: example action interface --- ros2_ws/src/example_interfaces/CMakeLists.txt | 2 ++ ros2_ws/src/example_interfaces/action/Counter.action | 5 +++++ 2 files changed, 7 insertions(+) create mode 100644 ros2_ws/src/example_interfaces/action/Counter.action diff --git a/ros2_ws/src/example_interfaces/CMakeLists.txt b/ros2_ws/src/example_interfaces/CMakeLists.txt index db3f184..7c6a3fc 100644 --- a/ros2_ws/src/example_interfaces/CMakeLists.txt +++ b/ros2_ws/src/example_interfaces/CMakeLists.txt @@ -15,6 +15,8 @@ find_package(rosidl_default_generators REQUIRED) rosidl_generate_interfaces(example_interfaces "srv/AddTwoInts.srv" + "action/Fibonacci.action" + "action/Counter.action" DEPENDENCIES geometry_msgs action_msgs std_msgs builtin_interfaces ) diff --git a/ros2_ws/src/example_interfaces/action/Counter.action b/ros2_ws/src/example_interfaces/action/Counter.action new file mode 100644 index 0000000..751d1d6 --- /dev/null +++ b/ros2_ws/src/example_interfaces/action/Counter.action @@ -0,0 +1,5 @@ +int32 target_number +--- +float64 time_spent +--- +int32 current_number \ No newline at end of file From 4419469c1f96b7537ba63544f0235e670e24110f Mon Sep 17 00:00:00 2001 From: TWang Date: Wed, 11 Mar 2026 01:34:58 +0800 Subject: [PATCH 14/20] add: an example pkg for tf2 --- ros2_ws/src/cpp_tf2/CMakeLists.txt | 70 +++++++++++++++++++ ros2_ws/src/cpp_tf2/package.xml | 27 +++++++ ros2_ws/src/cpp_tf2/src/frame_broadcaster.cpp | 43 ++++++++++++ ros2_ws/src/cpp_tf2/src/frame_listener.cpp | 38 ++++++++++ ros2_ws/src/cpp_tf2/src/point_transformer.cpp | 46 ++++++++++++ 5 files changed, 224 insertions(+) create mode 100644 ros2_ws/src/cpp_tf2/CMakeLists.txt create mode 100644 ros2_ws/src/cpp_tf2/package.xml create mode 100644 ros2_ws/src/cpp_tf2/src/frame_broadcaster.cpp create mode 100644 ros2_ws/src/cpp_tf2/src/frame_listener.cpp create mode 100644 ros2_ws/src/cpp_tf2/src/point_transformer.cpp diff --git a/ros2_ws/src/cpp_tf2/CMakeLists.txt b/ros2_ws/src/cpp_tf2/CMakeLists.txt new file mode 100644 index 0000000..6175c3d --- /dev/null +++ b/ros2_ws/src/cpp_tf2/CMakeLists.txt @@ -0,0 +1,70 @@ +cmake_minimum_required(VERSION 3.5) +project(cpp_tf2) + +if(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_CXX_COMPILER_ID MATCHES "Clang") + add_compile_options(-Wall -Wextra -Wpedantic) +endif() + +# Include directories +include_directories( + include +) + +# find dependencies +find_package(ament_cmake REQUIRED) +find_package(rclcpp REQUIRED) +find_package(std_msgs REQUIRED) +find_package(rclcpp_action REQUIRED) +find_package(rclcpp_components REQUIRED) +find_package(geometry_msgs REQUIRED) +find_package(tf2_ros REQUIRED) +find_package(tf2_geometry_msgs REQUIRED) + +add_executable(frame_broadcaster src/frame_broadcaster.cpp) +ament_target_dependencies(frame_broadcaster + rclcpp + std_msgs + geometry_msgs + tf2_ros +) +add_executable(frame_listener src/frame_listener.cpp) +ament_target_dependencies(frame_listener + rclcpp + std_msgs + geometry_msgs + tf2_ros +) +add_executable(point_transformer src/point_transformer.cpp) +ament_target_dependencies(point_transformer + rclcpp + std_msgs + geometry_msgs + tf2_ros + tf2_geometry_msgs +) + +install(TARGETS + frame_broadcaster + frame_listener + point_transformer + DESTINATION lib/${PROJECT_NAME} +) + +install(DIRECTORY + launch/ + # rviz/ + DESTINATION share/${PROJECT_NAME}/launch +) + +if(BUILD_TESTING) + find_package(ament_lint_auto REQUIRED) + # the following line skips the linter which checks for copyrights + # uncomment the line when a copyright and license is not present in all source files + #set(ament_cmake_copyright_FOUND TRUE) + # the following line skips cpplint (only works in a git repo) + # uncomment the line when this package is not in a git repo + #set(ament_cmake_cpplint_FOUND TRUE) + ament_lint_auto_find_test_dependencies() +endif() + +ament_package() diff --git a/ros2_ws/src/cpp_tf2/package.xml b/ros2_ws/src/cpp_tf2/package.xml new file mode 100644 index 0000000..a7db28f --- /dev/null +++ b/ros2_ws/src/cpp_tf2/package.xml @@ -0,0 +1,27 @@ + + + + cpp_tf2 + 0.0.0 + TODO: Package description + root + TODO: License declaration + + ament_cmake + + rclcpp + std_msgs + rclcpp_action + rclcpp_components + geometry_msgs + tf2_ros + tf2_geometry_msgs + communicate_test + + ament_lint_auto + ament_lint_common + + + ament_cmake + + diff --git a/ros2_ws/src/cpp_tf2/src/frame_broadcaster.cpp b/ros2_ws/src/cpp_tf2/src/frame_broadcaster.cpp new file mode 100644 index 0000000..6a58508 --- /dev/null +++ b/ros2_ws/src/cpp_tf2/src/frame_broadcaster.cpp @@ -0,0 +1,43 @@ +#include +#include +#include + +class FrameBroadcaster : public rclcpp::Node { +public: + FrameBroadcaster() : Node("frame_broadcaster") { + broadcaster_ = std::make_shared(this); + + timer_ = this->create_wall_timer( + std::chrono::milliseconds(100), + std::bind(&FrameBroadcaster::broadcastTransform, this) + ); + } + +private: + void broadcastTransform() { + geometry_msgs::msg::TransformStamped transform; + transform.header.stamp = this->get_clock()->now(); + transform.header.frame_id = "world"; + transform.child_frame_id = "robot"; + + transform.transform.translation.x = 1.0; + transform.transform.translation.y = 2.0; + transform.transform.translation.z = 0.0; + transform.transform.rotation.x = 0.0; + transform.transform.rotation.y = 0.0; + transform.transform.rotation.z = 0.7071; + transform.transform.rotation.w = 0.7071; + + broadcaster_->sendTransform(transform); + } + + std::shared_ptr broadcaster_; + rclcpp::TimerBase::SharedPtr timer_; +}; + +int main(int argc, char **argv) { + rclcpp::init(argc, argv); + rclcpp::spin(std::make_shared()); + rclcpp::shutdown(); + return 0; +} diff --git a/ros2_ws/src/cpp_tf2/src/frame_listener.cpp b/ros2_ws/src/cpp_tf2/src/frame_listener.cpp new file mode 100644 index 0000000..cc87d0f --- /dev/null +++ b/ros2_ws/src/cpp_tf2/src/frame_listener.cpp @@ -0,0 +1,38 @@ +#include +#include +#include +#include + +class FrameListener : public rclcpp::Node { +public: + FrameListener() : Node("frame_listener"), buffer_(this->get_clock()), listener_(buffer_) { + timer_ = this->create_wall_timer( + std::chrono::seconds(1), + std::bind(&FrameListener::lookupTransform, this) + ); + } + +private: + void lookupTransform() { + try { + auto transform = buffer_.lookupTransform("world", "robot", tf2::TimePointZero); + RCLCPP_INFO(this->get_logger(), "Transform: x=%f, y=%f, z=%f", + transform.transform.translation.x, + transform.transform.translation.y, + transform.transform.translation.z); + } catch (const tf2::TransformException &ex) { + RCLCPP_WARN(this->get_logger(), "Failed to lookup transform: %s", ex.what()); + } + } + + tf2_ros::Buffer buffer_; + tf2_ros::TransformListener listener_; + rclcpp::TimerBase::SharedPtr timer_; +}; + +int main(int argc, char **argv) { + rclcpp::init(argc, argv); + rclcpp::spin(std::make_shared()); + rclcpp::shutdown(); + return 0; +} diff --git a/ros2_ws/src/cpp_tf2/src/point_transformer.cpp b/ros2_ws/src/cpp_tf2/src/point_transformer.cpp new file mode 100644 index 0000000..6aeeb0a --- /dev/null +++ b/ros2_ws/src/cpp_tf2/src/point_transformer.cpp @@ -0,0 +1,46 @@ +#include +#include +#include +#include +#include + +class PointTransformer : public rclcpp::Node { +public: + PointTransformer() : Node("point_transformer"), buffer_(this->get_clock()), listener_(buffer_) { + timer_ = this->create_wall_timer( + std::chrono::seconds(1), + std::bind(&PointTransformer::transformPoint, this) + ); + } + +private: + void transformPoint() { + geometry_msgs::msg::PointStamped point; + point.header.frame_id = "robot"; + point.header.stamp = this->get_clock()->now(); + point.point.x = 1.0; + point.point.y = 2.0; + point.point.z = 3.0; + + try { + auto transformed_point = buffer_.transform(point, "world"); + RCLCPP_INFO(this->get_logger(), "Transformed Point: x=%f, y=%f, z=%f", + transformed_point.point.x, + transformed_point.point.y, + transformed_point.point.z); + } catch (const tf2::TransformException &ex) { + RCLCPP_WARN(this->get_logger(), "Failed to transform point: %s", ex.what()); + } + } + + tf2_ros::Buffer buffer_; + tf2_ros::TransformListener listener_; + rclcpp::TimerBase::SharedPtr timer_; +}; + +int main(int argc, char **argv) { + rclcpp::init(argc, argv); + rclcpp::spin(std::make_shared()); + rclcpp::shutdown(); + return 0; +} From 4b79ee27e1f485c371af90710ace65c637728630 Mon Sep 17 00:00:00 2001 From: TWang Date: Wed, 11 Mar 2026 01:36:37 +0800 Subject: [PATCH 15/20] add: an example pkg for action communication - action client - action server - publish target - subscribe and do a CPU cost task - use multi-threaded executor and component composition --- ros2_ws/src/cpp_action/CMakeLists.txt | 59 ++++++++++++++ ros2_ws/src/cpp_action/README.md | 5 ++ .../include/cpp_action/counter_client.hpp | 39 +++++++++ .../include/cpp_action/counter_server.hpp | 60 ++++++++++++++ ros2_ws/src/cpp_action/package.xml | 23 ++++++ ros2_ws/src/cpp_action/src/counter_client.cpp | 60 ++++++++++++++ ros2_ws/src/cpp_action/src/counter_server.cpp | 68 ++++++++++++++++ ros2_ws/src/cpp_action/src/main.cpp | 30 +++++++ ros2_ws/src/cpp_action/src/thread_example.cpp | 81 +++++++++++++++++++ 9 files changed, 425 insertions(+) create mode 100644 ros2_ws/src/cpp_action/CMakeLists.txt create mode 100644 ros2_ws/src/cpp_action/README.md create mode 100644 ros2_ws/src/cpp_action/include/cpp_action/counter_client.hpp create mode 100644 ros2_ws/src/cpp_action/include/cpp_action/counter_server.hpp create mode 100644 ros2_ws/src/cpp_action/package.xml create mode 100644 ros2_ws/src/cpp_action/src/counter_client.cpp create mode 100644 ros2_ws/src/cpp_action/src/counter_server.cpp create mode 100644 ros2_ws/src/cpp_action/src/main.cpp create mode 100644 ros2_ws/src/cpp_action/src/thread_example.cpp diff --git a/ros2_ws/src/cpp_action/CMakeLists.txt b/ros2_ws/src/cpp_action/CMakeLists.txt new file mode 100644 index 0000000..72a4890 --- /dev/null +++ b/ros2_ws/src/cpp_action/CMakeLists.txt @@ -0,0 +1,59 @@ +cmake_minimum_required(VERSION 3.8) +project(cpp_action) + +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(rclcpp REQUIRED) +find_package(rclcpp_action REQUIRED) +find_package(rclcpp_components REQUIRED) +find_package(example_interfaces REQUIRED) +find_package(std_msgs REQUIRED) + +set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD_REQUIRED ON) + +add_executable(integrated_node + src/counter_client.cpp + src/counter_server.cpp + src/main.cpp +) + +target_include_directories(integrated_node PUBLIC + $ + $ +) + +ament_target_dependencies(integrated_node + rclcpp + rclcpp_action + example_interfaces + std_msgs +) + +add_executable(thread_example src/thread_example.cpp) +find_package(Threads REQUIRED) +target_link_libraries(thread_example Threads::Threads) + +install(TARGETS + integrated_node + thread_example + DESTINATION lib/${PROJECT_NAME} +) + +if(BUILD_TESTING) + find_package(ament_lint_auto REQUIRED) + # the following line skips the linter which checks for copyrights + # comment the line when a copyright and license is added to all source files + set(ament_cmake_copyright_FOUND TRUE) + # the following line skips cpplint (only works in a git repo) + # comment the line when this package is in a git repo and when + # a copyright and license is added to all source files + set(ament_cmake_cpplint_FOUND TRUE) + ament_lint_auto_find_test_dependencies() +endif() + +ament_package() diff --git a/ros2_ws/src/cpp_action/README.md b/ros2_ws/src/cpp_action/README.md new file mode 100644 index 0000000..b627d6d --- /dev/null +++ b/ros2_ws/src/cpp_action/README.md @@ -0,0 +1,5 @@ +1. execute an action client ask for server to count to a specific target number +2. the action server feedbask the time spent and return the result as the target number +3. execute another node that count the numbers of prime number under the target number +4. wrap the counter node and prime number finding node in a same multi-threaded executor +5. all these three nodes added into same component \ No newline at end of file diff --git a/ros2_ws/src/cpp_action/include/cpp_action/counter_client.hpp b/ros2_ws/src/cpp_action/include/cpp_action/counter_client.hpp new file mode 100644 index 0000000..3f659a7 --- /dev/null +++ b/ros2_ws/src/cpp_action/include/cpp_action/counter_client.hpp @@ -0,0 +1,39 @@ +#ifndef COUNTER_SERVER_HPP_ +#define COUNTER_SERVER_HPP_ + +#include "rclcpp/rclcpp.hpp" +#include "rclcpp_action/rclcpp_action.hpp" +#include "example_interfaces/action/counter.hpp" +#include "std_msgs/msg/int32.hpp" + +class CounterActionClient : public rclcpp::Node +{ +public: + explicit CounterActionClient(const rclcpp::NodeOptions & options) + : Node("example_action_client", options) { + this->action_client_ = rclcpp_action::create_client( + this, "count_to"); + + this->timer_ = this->create_wall_timer( + std::chrono::milliseconds(500), + std::bind(&CounterActionClient::send_goal, this)); + } + + void send_goal(); + +private: + rclcpp_action::Client::SharedPtr action_client_; + rclcpp::TimerBase::SharedPtr timer_; + + void goal_response_callback(std::shared_future::SharedPtr> future); + + void feedback_callback( + rclcpp_action::ClientGoalHandle::SharedPtr, + const std::shared_ptr feedback); + + void result_callback(const rclcpp_action::ClientGoalHandle + ::WrappedResult & result); +}; + +#endif \ No newline at end of file diff --git a/ros2_ws/src/cpp_action/include/cpp_action/counter_server.hpp b/ros2_ws/src/cpp_action/include/cpp_action/counter_server.hpp new file mode 100644 index 0000000..500b260 --- /dev/null +++ b/ros2_ws/src/cpp_action/include/cpp_action/counter_server.hpp @@ -0,0 +1,60 @@ +#ifndef COUNTER_CLIENT_HPP_ +#define COUNTER_CLIENT_HPP_ + +#include +#include +#include "rcl_action/action_server.h" +#include "example_interfaces/action/counter.hpp" +#include "std_msgs/msg/int32.hpp" + +class CounterActionServer : public rclcpp::Node +{ +public: + CounterActionServer(const rclcpp::NodeOptions & options) + : Node("counter_server", options) { + // use Reentrant and create a callback group + callback_group_ = this->create_callback_group(rclcpp::CallbackGroupType::Reentrant); + this->action_server_ = rclcpp_action::create_server( + this, + "count_to", + std::bind(&CounterActionServer::handle_goal, this, std::placeholders::_1, std::placeholders::_2), + std::bind(&CounterActionServer::handle_cancel, this, std::placeholders::_1), + std::bind(&CounterActionServer::handle_accepted, this, std::placeholders::_1), + rcl_action_server_get_default_options(), + callback_group_ + ); + } + +private: + double start_time, current_time; + int current_number; + rclcpp_action::Server::SharedPtr action_server_; + rclcpp::CallbackGroup::SharedPtr callback_group_; + + rclcpp_action::GoalResponse handle_goal( + const rclcpp_action::GoalUUID & uuid, + std::shared_ptr goal); + rclcpp_action::CancelResponse handle_cancel( + const std::shared_ptr> goal_handle); + void handle_accepted(const std::shared_ptr> goal_handle); +}; + +class PrimeFinderSubscriber : public rclcpp::Node +{ +public: + PrimeFinderSubscriber(const rclcpp::NodeOptions & options) : Node("prime_finder", options) { + auto cb_group = this->create_callback_group(rclcpp::CallbackGroupType::Reentrant); + auto sub_options = rclcpp::SubscriptionOptions(); + sub_options.callback_group = cb_group; + + subscription_ = this->create_subscription( + "target_num", 10, std::bind(&PrimeFinderSubscriber::calculate_primes, this, std::placeholders::_1)); + } + +private: + rclcpp::Subscription::SharedPtr subscription_; + + void calculate_primes(const std_msgs::msg::Int32::SharedPtr msg); +}; + +#endif \ No newline at end of file diff --git a/ros2_ws/src/cpp_action/package.xml b/ros2_ws/src/cpp_action/package.xml new file mode 100644 index 0000000..9b14137 --- /dev/null +++ b/ros2_ws/src/cpp_action/package.xml @@ -0,0 +1,23 @@ + + + + cpp_action + 0.0.0 + TODO: Package description + user + TODO: License declaration + + ament_cmake + + example_interfaces + rclcpp + rclcpp_action + rclcpp_components + + ament_lint_auto + ament_lint_common + + + ament_cmake + + diff --git a/ros2_ws/src/cpp_action/src/counter_client.cpp b/ros2_ws/src/cpp_action/src/counter_client.cpp new file mode 100644 index 0000000..b7fb265 --- /dev/null +++ b/ros2_ws/src/cpp_action/src/counter_client.cpp @@ -0,0 +1,60 @@ +#include "cpp_action/counter_client.hpp" + +void CounterActionClient::send_goal() +{ + this->timer_->cancel(); + + if (!this->action_client_->wait_for_action_server()) { + RCLCPP_ERROR(this->get_logger(), "Action server not available after waiting"); + rclcpp::shutdown(); + } + + auto goal_msg = example_interfaces::action::Counter::Goal(); + goal_msg.target_number = 100000; + + RCLCPP_INFO(this->get_logger(), "Sending goal"); + + auto send_goal_options = rclcpp_action::Client::SendGoalOptions(); + // send_goal_options.goal_response_callback = + // std::bind(&CounterActionClient::goal_response_callback, this, std::placeholders::_1); + send_goal_options.feedback_callback = + std::bind(&CounterActionClient::feedback_callback, this, std::placeholders::_1, std::placeholders::_2); + send_goal_options.result_callback = + std::bind(&CounterActionClient::result_callback, this, std::placeholders::_1); + this->action_client_->async_send_goal(goal_msg, send_goal_options); +} + +void CounterActionClient::goal_response_callback(std::shared_future::SharedPtr> future) { + auto goal_handle = future.get(); + if (!goal_handle) { + RCLCPP_ERROR(this->get_logger(), "Goal was rejected by server"); + } else { + RCLCPP_INFO(this->get_logger(), "Goal accepted by server, waiting for result"); + } +} + +void CounterActionClient::feedback_callback( + rclcpp_action::ClientGoalHandle::SharedPtr, + const std::shared_ptr feedback) { + RCLCPP_INFO_STREAM(this->get_logger(), "Feedback get! current number is" << feedback->current_number); +} + +void CounterActionClient::result_callback(const rclcpp_action::ClientGoalHandle + ::WrappedResult & result) { + switch (result.code) { + case rclcpp_action::ResultCode::SUCCEEDED: + break; + case rclcpp_action::ResultCode::ABORTED: + RCLCPP_ERROR(this->get_logger(), "Goal was aborted"); + return; + case rclcpp_action::ResultCode::CANCELED: + RCLCPP_ERROR(this->get_logger(), "Goal was canceled"); + return; + default: + RCLCPP_ERROR(this->get_logger(), "Unknown result code"); + return; + } + RCLCPP_INFO_STREAM(this->get_logger(), "Result get! Spend" << result.result->time_spent << " second."); + rclcpp::shutdown(); +} \ No newline at end of file diff --git a/ros2_ws/src/cpp_action/src/counter_server.cpp b/ros2_ws/src/cpp_action/src/counter_server.cpp new file mode 100644 index 0000000..19fbdf5 --- /dev/null +++ b/ros2_ws/src/cpp_action/src/counter_server.cpp @@ -0,0 +1,68 @@ +#include "cpp_action/counter_server.hpp" + +rclcpp_action::GoalResponse CounterActionServer::handle_goal( + const rclcpp_action::GoalUUID & uuid, + std::shared_ptr goal) { + RCLCPP_INFO(this->get_logger(), "Received goal request with target number %d", goal->target_number); + (void)uuid; + return rclcpp_action::GoalResponse::ACCEPT_AND_EXECUTE; +} + +rclcpp_action::CancelResponse CounterActionServer::handle_cancel( + const std::shared_ptr> goal_handle) { + RCLCPP_INFO(this->get_logger(), "Received request to cancel goal"); + (void)goal_handle; + return rclcpp_action::CancelResponse::ACCEPT; +} + +void CounterActionServer::handle_accepted(const std::shared_ptr + > goal_handle) { + using namespace std::placeholders; + rclcpp::Rate loop_rate(1); + const auto goal = goal_handle->get_goal(); + auto feedback = std::make_shared(); + auto result = std::make_shared(); + int i; + this->start_time = this->get_clock()->now().seconds(); + + for (i = 0; i < goal->target_number; ++i) { + this->current_time = this->get_clock()->now().seconds(); + // Check if there is a cancel request + if (goal_handle->is_canceling()) { + result->time_spent = this->current_time - this->start_time; + goal_handle->canceled(result); + RCLCPP_INFO(this->get_logger(), "Goal canceled"); + return; + } + // Update feedback + feedback->current_number = i; + goal_handle->publish_feedback(feedback); + RCLCPP_INFO(this->get_logger(), "Publish feedback"); + loop_rate.sleep(); + } + // Check if goal is done + if (rclcpp::ok()) { + result->time_spent = this->current_time - this->start_time; + goal_handle->succeed(result); + RCLCPP_INFO(this->get_logger(), "Goal succeeded"); + } +} + +void PrimeFinderSubscriber::calculate_primes(const std_msgs::msg::Int32::SharedPtr msg) { + int target_number = msg->data; + RCLCPP_INFO(this->get_logger(), "start counting the number of the prime number under %d", target_number); + int count = 0; + for (int i = 2; i <= target_number; i++) { + bool is_prime = true; + for (int j = 2; j * j <= i; j++) { + if (i % j == 0) { + is_prime = false; + break; + } + } + if (is_prime) + count++; + } + RCLCPP_INFO(this->get_logger(), "finish! has %d prime numbers", count); +} \ No newline at end of file diff --git a/ros2_ws/src/cpp_action/src/main.cpp b/ros2_ws/src/cpp_action/src/main.cpp new file mode 100644 index 0000000..74addbb --- /dev/null +++ b/ros2_ws/src/cpp_action/src/main.cpp @@ -0,0 +1,30 @@ +#include +#include +#include "example_interfaces/action/Counter.hpp" + +#include "cpp_action/counter_client.hpp" +#include "cpp_action/counter_server.hpp" + +int main(int argc, char * argv[]) { + rclcpp::init(argc, argv); + + // Intra-process Communication + rclcpp::NodeOptions options; + options.use_intra_process_comms(true); + + auto action_client = std::make_shared(options); + auto action_server = std::make_shared(options); + auto prime_finder = std::make_shared(options); + + // create multi-threaded executor + rclcpp::executors::MultiThreadedExecutor executor(rclcpp::ExecutorOptions(), 4); + + // add all nodes into one executor + executor.add_node(action_client); + executor.add_node(action_server); + executor.add_node(prime_finder); + + executor.spin(); + rclcpp::shutdown(); + return 0; +} \ No newline at end of file diff --git a/ros2_ws/src/cpp_action/src/thread_example.cpp b/ros2_ws/src/cpp_action/src/thread_example.cpp new file mode 100644 index 0000000..9869ff5 --- /dev/null +++ b/ros2_ws/src/cpp_action/src/thread_example.cpp @@ -0,0 +1,81 @@ +#include +#include +#include +#include +#include +#include + +using namespace std::chrono_literals; + +class MultiThreadedTimer { +public: + MultiThreadedTimer() : running_(true) { + start_time_ = std::chrono::steady_clock::now(); + } + + // 啟動所有執行緒 + void run() { + // 分別以 0, 10, 20, 30 ms 的延遲啟動 4 個執行緒 + for (int i = 0; i < 4; ++i) { + workers_.emplace_back(&MultiThreadedTimer::worker_task, this, i * 10ms); + } + } + + // 解構時確保資源正確釋放 (RAII) + ~MultiThreadedTimer() { + stop(); + } + + void stop() { + running_ = false; // 原子操作,確保所有執行緒同步收到停止指令 + for (auto& t : workers_) { + if (t.joinable()) { + t.join(); // 等待執行緒結束,避免程式崩潰 + } + } + } + +private: + // 模擬 EventQueue 的定時執行邏輯 + void worker_task(std::chrono::milliseconds initial_offset) { + std::this_thread::sleep_for(initial_offset); // 初始交錯延遲 + + while (running_) { + auto now = std::chrono::steady_clock::now(); + auto elapsed = std::chrono::duration_cast(now - start_time_); + + // 設定停止條件:運行超過 10,000 ms (10秒) + if (elapsed.count() > 10000) break; + + safe_print(elapsed.count()); + + // 模擬 call_every(40ms) 的行為 + std::this_thread::sleep_for(40ms); + } + } + + // 解決 Race Condition:使用 Mutex 保護標準輸出流 + void safe_print(long long ms) { + std::lock_guard lock(print_mtx_); + std::cout << "[Thread " << std::this_thread::get_id() << "] Elapsed: " + << ms << " ms" << std::endl; + } + + std::atomic running_; // 執行緒安全的開關 + std::chrono::steady_clock::time_point start_time_; + std::mutex print_mtx_; // 防止輸出字元交錯的鎖 + std::vector workers_; // 管理複數個執行緒 +}; + +int main() { + std::cout << "系統啟動:開始多執行緒並行任務..." << std::endl; + + MultiThreadedTimer timer_app; + timer_app.run(); + + // 主執行緒可以執行其他任務,或簡單等待 + std::this_thread::sleep_for(11s); + + std::cout << "所有任務已完成。" << std::endl; + return 0; +} \ No newline at end of file From c1246eac47c2d5fcb131ca549a5174cb88fe3b56 Mon Sep 17 00:00:00 2001 From: TWang Date: Wed, 11 Mar 2026 17:43:04 +0800 Subject: [PATCH 16/20] add: tools --- docker/Dockerfile | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/docker/Dockerfile b/docker/Dockerfile index b141f7f..67db515 100755 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -22,10 +22,12 @@ RUN apt-get update && apt-get install -y \ git \ tree \ net-tools \ + usbutils \ tmux \ vim \ wget \ python3-pip \ + ros-humble-rqt-tf-tree \ bash-completion RUN echo "source /opt/ros/humble/setup.bash" >> /etc/bash.bashrc \ @@ -36,7 +38,7 @@ RUN apt-get update && apt-get dist-upgrade -y \ && apt-get autoclean -y \ && rm -rf /var/lib/apt/lists/* -ENV SHELL /bin/bash +ENV SHELL=/bin/bash ENV TERM=xterm-256color USER $USERNAME From a19be696e2395f8afc4d2c9f198705b8db14d817 Mon Sep 17 00:00:00 2001 From: TWang Date: Wed, 11 Mar 2026 20:39:04 +0800 Subject: [PATCH 17/20] add: enable launch --- ros2_ws/src/cpp_launch_examples/CMakeLists.txt | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/ros2_ws/src/cpp_launch_examples/CMakeLists.txt b/ros2_ws/src/cpp_launch_examples/CMakeLists.txt index abbe594..538ffdb 100644 --- a/ros2_ws/src/cpp_launch_examples/CMakeLists.txt +++ b/ros2_ws/src/cpp_launch_examples/CMakeLists.txt @@ -22,4 +22,9 @@ if(BUILD_TESTING) ament_lint_auto_find_test_dependencies() endif() +install(DIRECTORY + launch + DESTINATION share/${PROJECT_NAME}/ +) + ament_package() From 8f67978a2d59b4de803f40118273e662fe2b1458 Mon Sep 17 00:00:00 2001 From: TWang Date: Wed, 11 Mar 2026 20:40:55 +0800 Subject: [PATCH 18/20] modify: shorter sleep duration in thread add: example of single-thread --- ros2_ws/src/cpp_action/src/thread_example.cpp | 35 +++------- .../src/cpp_action/src/thread_example_1.cpp | 67 +++++++++++++++++++ 2 files changed, 78 insertions(+), 24 deletions(-) create mode 100644 ros2_ws/src/cpp_action/src/thread_example_1.cpp diff --git a/ros2_ws/src/cpp_action/src/thread_example.cpp b/ros2_ws/src/cpp_action/src/thread_example.cpp index 9869ff5..2b421c0 100644 --- a/ros2_ws/src/cpp_action/src/thread_example.cpp +++ b/ros2_ws/src/cpp_action/src/thread_example.cpp @@ -13,69 +13,56 @@ class MultiThreadedTimer { start_time_ = std::chrono::steady_clock::now(); } - // 啟動所有執行緒 void run() { - // 分別以 0, 10, 20, 30 ms 的延遲啟動 4 個執行緒 for (int i = 0; i < 4; ++i) { - workers_.emplace_back(&MultiThreadedTimer::worker_task, this, i * 10ms); + workers_.emplace_back(&MultiThreadedTimer::worker_task, this, i * 1ms); } } - - // 解構時確保資源正確釋放 (RAII) ~MultiThreadedTimer() { stop(); } void stop() { - running_ = false; // 原子操作,確保所有執行緒同步收到停止指令 + running_ = false; for (auto& t : workers_) { if (t.joinable()) { - t.join(); // 等待執行緒結束,避免程式崩潰 + t.join(); } } } private: - // 模擬 EventQueue 的定時執行邏輯 + // EventQueue void worker_task(std::chrono::milliseconds initial_offset) { - std::this_thread::sleep_for(initial_offset); // 初始交錯延遲 + std::this_thread::sleep_for(initial_offset); while (running_) { auto now = std::chrono::steady_clock::now(); auto elapsed = std::chrono::duration_cast(now - start_time_); - // 設定停止條件:運行超過 10,000 ms (10秒) if (elapsed.count() > 10000) break; - safe_print(elapsed.count()); - - // 模擬 call_every(40ms) 的行為 - std::this_thread::sleep_for(40ms); + std::this_thread::sleep_for(1ms); } } - // 解決 Race Condition:使用 Mutex 保護標準輸出流 + // tackle Race Condition void safe_print(long long ms) { - std::lock_guard lock(print_mtx_); + // std::lock_guard lock(print_mtx_); std::cout << "[Thread " << std::this_thread::get_id() << "] Elapsed: " << ms << " ms" << std::endl; } - std::atomic running_; // 執行緒安全的開關 + std::atomic running_; std::chrono::steady_clock::time_point start_time_; - std::mutex print_mtx_; // 防止輸出字元交錯的鎖 - std::vector workers_; // 管理複數個執行緒 + std::mutex print_mtx_; + std::vector workers_; }; int main() { - std::cout << "系統啟動:開始多執行緒並行任務..." << std::endl; - MultiThreadedTimer timer_app; timer_app.run(); - // 主執行緒可以執行其他任務,或簡單等待 std::this_thread::sleep_for(11s); - - std::cout << "所有任務已完成。" << std::endl; return 0; } \ No newline at end of file diff --git a/ros2_ws/src/cpp_action/src/thread_example_1.cpp b/ros2_ws/src/cpp_action/src/thread_example_1.cpp new file mode 100644 index 0000000..343c132 --- /dev/null +++ b/ros2_ws/src/cpp_action/src/thread_example_1.cpp @@ -0,0 +1,67 @@ +#include +#include +#include +#include +#include +#include + +using namespace std::chrono_literals; + +class MultiThreadedTimer { +public: + MultiThreadedTimer() : running_(true) { + start_time_ = std::chrono::steady_clock::now(); + } + + void run() { + workers_.emplace_back(&MultiThreadedTimer::worker_task, this, 0ms); + } + + ~MultiThreadedTimer() { + stop(); + } + + void stop() { + running_ = false; + for (auto& t : workers_) { + if (t.joinable()) { + t.join(); + } + } + } + +private: + // EventQueue + void worker_task(std::chrono::milliseconds initial_offset) { + std::this_thread::sleep_for(initial_offset); + + while (running_) { + auto now = std::chrono::steady_clock::now(); + auto elapsed = std::chrono::duration_cast(now - start_time_); + + if (elapsed.count() > 10000) break; + safe_print(elapsed.count()); + std::this_thread::sleep_for(1ms); + } + } + + // tackle Race Condition + void safe_print(long long ms) { + std::lock_guard lock(print_mtx_); + std::cout << "[Thread " << std::this_thread::get_id() << "] Elapsed: " + << ms << " ms" << std::endl; + } + + std::atomic running_; + std::chrono::steady_clock::time_point start_time_; + std::mutex print_mtx_; + std::vector workers_; +}; + +int main() { + MultiThreadedTimer timer_app; + timer_app.run(); + + std::this_thread::sleep_for(11s); + return 0; +} \ No newline at end of file From b1a950a1d6c7b23201abd68fc561bc3a526b881d Mon Sep 17 00:00:00 2001 From: TWang Date: Wed, 11 Mar 2026 20:42:17 +0800 Subject: [PATCH 19/20] modify: shorter sleep duration for feedback msg modify: publish to counter_server add: example of single-thread --- ros2_ws/src/cpp_action/CMakeLists.txt | 24 +++++++++++++++ .../include/cpp_action/counter_client.hpp | 2 ++ ros2_ws/src/cpp_action/src/counter_client.cpp | 5 ++++ ros2_ws/src/cpp_action/src/counter_server.cpp | 2 +- ros2_ws/src/cpp_action/src/main1.cpp | 30 +++++++++++++++++++ 5 files changed, 62 insertions(+), 1 deletion(-) create mode 100644 ros2_ws/src/cpp_action/src/main1.cpp diff --git a/ros2_ws/src/cpp_action/CMakeLists.txt b/ros2_ws/src/cpp_action/CMakeLists.txt index 72a4890..af1766e 100644 --- a/ros2_ws/src/cpp_action/CMakeLists.txt +++ b/ros2_ws/src/cpp_action/CMakeLists.txt @@ -22,11 +22,22 @@ add_executable(integrated_node src/main.cpp ) +add_executable(integrated_node_1 + src/counter_client.cpp + src/counter_server.cpp + src/main1.cpp +) + target_include_directories(integrated_node PUBLIC $ $ ) +target_include_directories(integrated_node_1 PUBLIC + $ + $ +) + ament_target_dependencies(integrated_node rclcpp rclcpp_action @@ -34,13 +45,26 @@ ament_target_dependencies(integrated_node std_msgs ) +ament_target_dependencies(integrated_node_1 + rclcpp + rclcpp_action + example_interfaces + std_msgs +) + add_executable(thread_example src/thread_example.cpp) find_package(Threads REQUIRED) target_link_libraries(thread_example Threads::Threads) +add_executable(thread_example_1 src/thread_example_1.cpp) +find_package(Threads REQUIRED) +target_link_libraries(thread_example_1 Threads::Threads) + install(TARGETS integrated_node thread_example + integrated_node_1 + thread_example_1 DESTINATION lib/${PROJECT_NAME} ) diff --git a/ros2_ws/src/cpp_action/include/cpp_action/counter_client.hpp b/ros2_ws/src/cpp_action/include/cpp_action/counter_client.hpp index 3f659a7..7fb1c3d 100644 --- a/ros2_ws/src/cpp_action/include/cpp_action/counter_client.hpp +++ b/ros2_ws/src/cpp_action/include/cpp_action/counter_client.hpp @@ -13,6 +13,7 @@ class CounterActionClient : public rclcpp::Node : Node("example_action_client", options) { this->action_client_ = rclcpp_action::create_client( this, "count_to"); + publisher_ = this->create_publisher("target_num", 10); this->timer_ = this->create_wall_timer( std::chrono::milliseconds(500), @@ -22,6 +23,7 @@ class CounterActionClient : public rclcpp::Node void send_goal(); private: + rclcpp::Publisher::SharedPtr publisher_; rclcpp_action::Client::SharedPtr action_client_; rclcpp::TimerBase::SharedPtr timer_; diff --git a/ros2_ws/src/cpp_action/src/counter_client.cpp b/ros2_ws/src/cpp_action/src/counter_client.cpp index b7fb265..e301b13 100644 --- a/ros2_ws/src/cpp_action/src/counter_client.cpp +++ b/ros2_ws/src/cpp_action/src/counter_client.cpp @@ -4,6 +4,11 @@ void CounterActionClient::send_goal() { this->timer_->cancel(); + auto message = std_msgs::msg::Int32(); + message.data = 100000; + RCLCPP_INFO(this->get_logger(), "Publishing: %d", message.data); + publisher_->publish(message); + if (!this->action_client_->wait_for_action_server()) { RCLCPP_ERROR(this->get_logger(), "Action server not available after waiting"); rclcpp::shutdown(); diff --git a/ros2_ws/src/cpp_action/src/counter_server.cpp b/ros2_ws/src/cpp_action/src/counter_server.cpp index 19fbdf5..4904d73 100644 --- a/ros2_ws/src/cpp_action/src/counter_server.cpp +++ b/ros2_ws/src/cpp_action/src/counter_server.cpp @@ -19,7 +19,7 @@ void CounterActionServer::handle_accepted(const std::shared_ptr > goal_handle) { using namespace std::placeholders; - rclcpp::Rate loop_rate(1); + rclcpp::Rate loop_rate(100); const auto goal = goal_handle->get_goal(); auto feedback = std::make_shared(); auto result = std::make_shared(); diff --git a/ros2_ws/src/cpp_action/src/main1.cpp b/ros2_ws/src/cpp_action/src/main1.cpp new file mode 100644 index 0000000..790d9b8 --- /dev/null +++ b/ros2_ws/src/cpp_action/src/main1.cpp @@ -0,0 +1,30 @@ +#include +#include +#include "example_interfaces/action/Counter.hpp" + +#include "cpp_action/counter_client.hpp" +#include "cpp_action/counter_server.hpp" + +int main(int argc, char * argv[]) { + rclcpp::init(argc, argv); + + // Intra-process Communication + rclcpp::NodeOptions options; + options.use_intra_process_comms(true); + + auto action_client = std::make_shared(options); + auto action_server = std::make_shared(options); + auto prime_finder = std::make_shared(options); + + // create single-thread executor + rclcpp::executors::SingleThreadedExecutor executor; + + // add all nodes into one executor + executor.add_node(action_client); + executor.add_node(action_server); + executor.add_node(prime_finder); + + executor.spin(); + rclcpp::shutdown(); + return 0; +} \ No newline at end of file From 2687239cea1b424a4203afed02a3db6a4731a0f6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?112033225=20=E5=BE=90=E5=AF=AC=E5=AE=87?= Date: Wed, 25 Mar 2026 12:19:03 +0800 Subject: [PATCH 20/20] =?UTF-8?q?feat:=E5=AE=8C=E6=88=90=E5=B0=8F=E4=BD=9C?= =?UTF-8?q?=E6=A5=AD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 7 +- ros2_ws/src/cpp_action/src/main.cpp | 4 +- ros2_ws/src/cpp_action/src/main1.cpp | 4 +- .../launch/turtle_launch.py | 68 ++++ ros2_ws/src/turtle_square_control/package.xml | 22 ++ .../resource/turtle_square_control | 0 .../metadata.yaml | 32 ++ .../turtle1_status_bag_20260325_121031_0.db3 | Bin 0 -> 122880 bytes ros2_ws/src/turtle_square_control/setup.cfg | 4 + ros2_ws/src/turtle_square_control/setup.py | 33 ++ .../test/test_copyright.py | 25 ++ .../turtle_square_control/test/test_flake8.py | 25 ++ .../turtle_square_control/test/test_pep257.py | 23 ++ .../turtle_square_control/__init__.py | 0 .../turtle_controller.py | 353 ++++++++++++++++++ 15 files changed, 593 insertions(+), 7 deletions(-) create mode 100644 ros2_ws/src/turtle_square_control/launch/turtle_launch.py create mode 100644 ros2_ws/src/turtle_square_control/package.xml create mode 100644 ros2_ws/src/turtle_square_control/resource/turtle_square_control create mode 100644 ros2_ws/src/turtle_square_control/rosbag/turtle1_status_bag_20260325_121031/metadata.yaml create mode 100644 ros2_ws/src/turtle_square_control/rosbag/turtle1_status_bag_20260325_121031/turtle1_status_bag_20260325_121031_0.db3 create mode 100644 ros2_ws/src/turtle_square_control/setup.cfg create mode 100644 ros2_ws/src/turtle_square_control/setup.py create mode 100644 ros2_ws/src/turtle_square_control/test/test_copyright.py create mode 100644 ros2_ws/src/turtle_square_control/test/test_flake8.py create mode 100644 ros2_ws/src/turtle_square_control/test/test_pep257.py create mode 100644 ros2_ws/src/turtle_square_control/turtle_square_control/__init__.py create mode 100644 ros2_ws/src/turtle_square_control/turtle_square_control/turtle_controller.py diff --git a/README.md b/README.md index e3d46e5..afe9fc9 100755 --- a/README.md +++ b/README.md @@ -1,9 +1,10 @@ # how to use ### build docker environment - `cd Winter-Tutorial/docker` -- `docker compose up` -- `docker start ros2_tutorial` +- `docker compose up -d` ### open work space -- `docker exec -it ros2_tutorial` +- `docker exec -it ros2_tutorial bash` - `cd ~/ros2_ws` - `colcon build` +### launch +- `ros2 launch turtle_square_control turtle_launch.py` diff --git a/ros2_ws/src/cpp_action/src/main.cpp b/ros2_ws/src/cpp_action/src/main.cpp index 74addbb..61d945a 100644 --- a/ros2_ws/src/cpp_action/src/main.cpp +++ b/ros2_ws/src/cpp_action/src/main.cpp @@ -1,6 +1,6 @@ #include #include -#include "example_interfaces/action/Counter.hpp" +#include "example_interfaces/action/counter.hpp" #include "cpp_action/counter_client.hpp" #include "cpp_action/counter_server.hpp" @@ -27,4 +27,4 @@ int main(int argc, char * argv[]) { executor.spin(); rclcpp::shutdown(); return 0; -} \ No newline at end of file +} diff --git a/ros2_ws/src/cpp_action/src/main1.cpp b/ros2_ws/src/cpp_action/src/main1.cpp index 790d9b8..99e49f3 100644 --- a/ros2_ws/src/cpp_action/src/main1.cpp +++ b/ros2_ws/src/cpp_action/src/main1.cpp @@ -1,6 +1,6 @@ #include #include -#include "example_interfaces/action/Counter.hpp" +#include "example_interfaces/action/counter.hpp" #include "cpp_action/counter_client.hpp" #include "cpp_action/counter_server.hpp" @@ -27,4 +27,4 @@ int main(int argc, char * argv[]) { executor.spin(); rclcpp::shutdown(); return 0; -} \ No newline at end of file +} diff --git a/ros2_ws/src/turtle_square_control/launch/turtle_launch.py b/ros2_ws/src/turtle_square_control/launch/turtle_launch.py new file mode 100644 index 0000000..80dd5c5 --- /dev/null +++ b/ros2_ws/src/turtle_square_control/launch/turtle_launch.py @@ -0,0 +1,68 @@ +import os +import datetime +from launch import LaunchDescription +from launch_ros.actions import Node +from launch.actions import ExecuteProcess, RegisterEventHandler, EmitEvent +from launch.event_handlers import OnProcessExit +from launch.events import Shutdown + +def generate_launch_description(): + # 取得當下時間並格式化為字串 (例如: 20260322_153025) + current_time = datetime.datetime.now().strftime("%Y%m%d_%H%M%S") + + # 組合出帶有時間戳記的資料夾名稱 + bag_folder_name = f'turtle1_status_bag_{current_time}' + + # 使用 os.path.expanduser 取得家目錄,並組合成你想要的完整路徑 + bag_path = os.path.join( + os.path.expanduser('~'), + 'ros_tutorial', 'ros2_ws', 'src', 'turtle_square_control', 'rosbag' , bag_folder_name + ) + + # 定義各個節點與執行程序 + turtlesim_node = Node( + package='turtlesim', + executable='turtlesim_node', + name='sim' + ) + + controller_node = Node( + package='turtle_square_control', + executable='controller', + name='turtle_controller', + parameters=[{ + 'start_x': 5.0, + 'start_y': 5.0, + 'square_length': 2.5 + }], + output='screen' + ) + + rqt_plot_node = Node( + package='rqt_plot', + executable='rqt_plot', + name='rqt_plot', + arguments=['/turtle1/cmd_vel/linear/x', '/turtle1/cmd_vel/angular/z'], + output='screen' + ) + + rosbag_record = ExecuteProcess( + cmd=['ros2', 'bag', 'record', '-o', bag_path, '/turtle1/cmd_vel', '/turtle1/pose'], + output='screen' + ) + + # 註冊事件處理器:當 controller_node 結束時,觸發 Shutdown 事件關閉整個 launch + shutdown_handler = RegisterEventHandler( + event_handler=OnProcessExit( + target_action=controller_node, + on_exit=[EmitEvent(event=Shutdown())] + ) + ) + + return LaunchDescription([ + turtlesim_node, + controller_node, + rqt_plot_node, + rosbag_record, + shutdown_handler # 加入事件處理器 + ]) diff --git a/ros2_ws/src/turtle_square_control/package.xml b/ros2_ws/src/turtle_square_control/package.xml new file mode 100644 index 0000000..939f11b --- /dev/null +++ b/ros2_ws/src/turtle_square_control/package.xml @@ -0,0 +1,22 @@ + + + + turtle_square_control + 0.0.0 + TODO: Package description + user + TODO: License declaration + + rclpy + geometry_msgs + turtlesim + + ament_copyright + ament_flake8 + ament_pep257 + python3-pytest + + + ament_python + + diff --git a/ros2_ws/src/turtle_square_control/resource/turtle_square_control b/ros2_ws/src/turtle_square_control/resource/turtle_square_control new file mode 100644 index 0000000..e69de29 diff --git a/ros2_ws/src/turtle_square_control/rosbag/turtle1_status_bag_20260325_121031/metadata.yaml b/ros2_ws/src/turtle_square_control/rosbag/turtle1_status_bag_20260325_121031/metadata.yaml new file mode 100644 index 0000000..d8a18f5 --- /dev/null +++ b/ros2_ws/src/turtle_square_control/rosbag/turtle1_status_bag_20260325_121031/metadata.yaml @@ -0,0 +1,32 @@ +rosbag2_bagfile_information: + version: 5 + storage_identifier: sqlite3 + duration: + nanoseconds: 17609813087 + starting_time: + nanoseconds_since_epoch: 1774411831903992434 + message_count: 1443 + topics_with_message_count: + - topic_metadata: + name: /turtle1/pose + type: turtlesim/msg/Pose + serialization_format: cdr + offered_qos_profiles: "- history: 3\n depth: 0\n reliability: 1\n durability: 2\n deadline:\n sec: 9223372036\n nsec: 854775807\n lifespan:\n sec: 9223372036\n nsec: 854775807\n liveliness: 1\n liveliness_lease_duration:\n sec: 9223372036\n nsec: 854775807\n avoid_ros_namespace_conventions: false" + message_count: 1094 + - topic_metadata: + name: /turtle1/cmd_vel + type: geometry_msgs/msg/Twist + serialization_format: cdr + offered_qos_profiles: "- history: 3\n depth: 0\n reliability: 1\n durability: 2\n deadline:\n sec: 9223372036\n nsec: 854775807\n lifespan:\n sec: 9223372036\n nsec: 854775807\n liveliness: 1\n liveliness_lease_duration:\n sec: 9223372036\n nsec: 854775807\n avoid_ros_namespace_conventions: false" + message_count: 349 + compression_format: "" + compression_mode: "" + relative_file_paths: + - turtle1_status_bag_20260325_121031_0.db3 + files: + - path: turtle1_status_bag_20260325_121031_0.db3 + starting_time: + nanoseconds_since_epoch: 1774411831903992434 + duration: + nanoseconds: 17609813087 + message_count: 1443 \ No newline at end of file diff --git a/ros2_ws/src/turtle_square_control/rosbag/turtle1_status_bag_20260325_121031/turtle1_status_bag_20260325_121031_0.db3 b/ros2_ws/src/turtle_square_control/rosbag/turtle1_status_bag_20260325_121031/turtle1_status_bag_20260325_121031_0.db3 new file mode 100644 index 0000000000000000000000000000000000000000..fe6a6385b9be11c82c60880c809294dc9e7c0de9 GIT binary patch literal 122880 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 F{vSd;VR8Tf literal 0 HcmV?d00001 diff --git a/ros2_ws/src/turtle_square_control/setup.cfg b/ros2_ws/src/turtle_square_control/setup.cfg new file mode 100644 index 0000000..58b1590 --- /dev/null +++ b/ros2_ws/src/turtle_square_control/setup.cfg @@ -0,0 +1,4 @@ +[develop] +script_dir=$base/lib/turtle_square_control +[install] +install_scripts=$base/lib/turtle_square_control diff --git a/ros2_ws/src/turtle_square_control/setup.py b/ros2_ws/src/turtle_square_control/setup.py new file mode 100644 index 0000000..5beeed3 --- /dev/null +++ b/ros2_ws/src/turtle_square_control/setup.py @@ -0,0 +1,33 @@ +import os +from glob import glob +from setuptools import find_packages, setup + +package_name = 'turtle_square_control' + +setup( + name=package_name, + version='0.0.0', + packages=find_packages(exclude=['test']), + data_files=[ + ('share/ament_index/resource_index/packages', + ['resource/' + package_name]), + ('share/' + package_name, ['package.xml']), + (os.path.join('share', package_name, 'launch'), glob(os.path.join('launch', '*launch.[pxy][yma]*'))), + ], + install_requires=['setuptools'], + zip_safe=True, + maintainer='user', + maintainer_email='user@todo.todo', + description='TODO: Package description', + license='TODO: License declaration', + extras_require={ + 'test': [ + 'pytest', + ], + }, + entry_points={ + 'console_scripts': [ + 'controller = turtle_square_control.turtle_controller:main' + ], + }, +) diff --git a/ros2_ws/src/turtle_square_control/test/test_copyright.py b/ros2_ws/src/turtle_square_control/test/test_copyright.py new file mode 100644 index 0000000..97a3919 --- /dev/null +++ b/ros2_ws/src/turtle_square_control/test/test_copyright.py @@ -0,0 +1,25 @@ +# Copyright 2015 Open Source Robotics Foundation, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from ament_copyright.main import main +import pytest + + +# Remove the `skip` decorator once the source file(s) have a copyright header +@pytest.mark.skip(reason='No copyright header has been placed in the generated source file.') +@pytest.mark.copyright +@pytest.mark.linter +def test_copyright(): + rc = main(argv=['.', 'test']) + assert rc == 0, 'Found errors' diff --git a/ros2_ws/src/turtle_square_control/test/test_flake8.py b/ros2_ws/src/turtle_square_control/test/test_flake8.py new file mode 100644 index 0000000..27ee107 --- /dev/null +++ b/ros2_ws/src/turtle_square_control/test/test_flake8.py @@ -0,0 +1,25 @@ +# Copyright 2017 Open Source Robotics Foundation, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from ament_flake8.main import main_with_errors +import pytest + + +@pytest.mark.flake8 +@pytest.mark.linter +def test_flake8(): + rc, errors = main_with_errors(argv=[]) + assert rc == 0, \ + 'Found %d code style errors / warnings:\n' % len(errors) + \ + '\n'.join(errors) diff --git a/ros2_ws/src/turtle_square_control/test/test_pep257.py b/ros2_ws/src/turtle_square_control/test/test_pep257.py new file mode 100644 index 0000000..b234a38 --- /dev/null +++ b/ros2_ws/src/turtle_square_control/test/test_pep257.py @@ -0,0 +1,23 @@ +# Copyright 2015 Open Source Robotics Foundation, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from ament_pep257.main import main +import pytest + + +@pytest.mark.linter +@pytest.mark.pep257 +def test_pep257(): + rc = main(argv=['.', 'test']) + assert rc == 0, 'Found code style errors / warnings' diff --git a/ros2_ws/src/turtle_square_control/turtle_square_control/__init__.py b/ros2_ws/src/turtle_square_control/turtle_square_control/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/ros2_ws/src/turtle_square_control/turtle_square_control/turtle_controller.py b/ros2_ws/src/turtle_square_control/turtle_square_control/turtle_controller.py new file mode 100644 index 0000000..a7bfec0 --- /dev/null +++ b/ros2_ws/src/turtle_square_control/turtle_square_control/turtle_controller.py @@ -0,0 +1,353 @@ +import sys # 新增 sys 模組以便呼叫 sys.exit() +import math +import rclpy +from rclpy.node import Node +from geometry_msgs.msg import Twist +from turtlesim.msg import Pose +from turtlesim.srv import Spawn, TeleportAbsolute, SetPen + + +class TurtleController(Node): + def __init__(self): + super().__init__('turtle_controller') + + self.declare_parameter('start_x', 5.0) + self.declare_parameter('start_y', 5.0) + self.declare_parameter('square_length', 2.5) + + self.start_x = self.get_parameter('start_x').value + self.start_y = self.get_parameter('start_y').value + self.square_length = self.get_parameter('square_length').value + + self.cmd_vel_pub = self.create_publisher(Twist, '/turtle1/cmd_vel', 10) + self.pose_sub = self.create_subscription( + Pose, '/turtle1/pose', self.pose_callback, 10) + + self.spawn_client = self.create_client(Spawn, '/spawn') + self.teleport_client = self.create_client( + TeleportAbsolute, '/turtle1/teleport_absolute') + self.pen_client = self.create_client(SetPen, '/turtle1/set_pen') + + self.current_pose = None + self.state = 'INIT_PEN_OFF' + self.edges_completed = 0 + self.child_turtles = {} + self.wait_ticks = 0 + + self.waypoints = [ + (self.start_x + self.square_length, self.start_y), + (self.start_x + self.square_length, self.start_y - self.square_length), + (self.start_x, self.start_y - self.square_length), + (self.start_x, self.start_y) + ] + self.target_headings = [0.0, -math.pi / 2, math.pi, math.pi / 2] + + self.angular_stop_thresh = 0.005 + self.linear_ramp_dist = 0.3 + self.high_linear_vel = 2.0 + self.low_linear_vel = 0.1 + + self.timer = self.create_timer(0.05, self.control_loop) + self.get_logger().info('Controller started.') + + # ------------------------------------------------------------------ # + + def pose_callback(self, msg): + self.current_pose = msg + + def normalize_angle(self, angle): + return math.atan2(math.sin(angle), math.cos(angle)) + + # ------------------------------------------------------------------ # + # Child turtle setup # + # ------------------------------------------------------------------ # + + def setup_child_turtle(self, name, corner_x, corner_y): + # --- Pen off --- + pen_client = self.create_client(SetPen, f'/{name}/set_pen') + if pen_client.wait_for_service(timeout_sec=1.0): + pen_client.call_async(SetPen.Request(off=1)) + else: + self.get_logger().warn(f'set_pen service not ready for {name}') + + # --- Publisher / subscriber --- + pub = self.create_publisher(Twist, f'/{name}/cmd_vel', 10) + sub = self.create_subscription( + Pose, f'/{name}/pose', + lambda msg, n=name: self.child_pose_callback(msg, n), + 10 + ) + + # Register entry with movement LOCKED until teleport confirmed + self.child_turtles[name] = { + 'pub': pub, + 'sub': sub, + 'pose': None, + 'corner_x': float(corner_x), + 'corner_y': float(corner_y), + 'teleported': False, # ← LOCK + } + + # --- Teleport to exact corner --- + teleport_client = self.create_client( + TeleportAbsolute, f'/{name}/teleport_absolute') + if teleport_client.wait_for_service(timeout_sec=1.0): + req = TeleportAbsolute.Request( + x=float(corner_x), + y=float(corner_y), + theta=0.0 + ) + future = teleport_client.call_async(req) + # UNLOCK only after the service confirms teleport is done + future.add_done_callback( + lambda f, n=name: self._on_teleport_done(f, n) + ) + self.get_logger().info( + f'{name} teleporting to exact corner ' + f'({corner_x:.3f}, {corner_y:.3f}) — movement locked.') + else: + # Fallback: unlock immediately to avoid permanent deadlock + self.child_turtles[name]['teleported'] = True + self.get_logger().warn( + f'teleport_absolute not ready for {name}; ' + f'movement unlocked without positional correction.') + + def _on_teleport_done(self, future, name): + """Teleport service responded — safe to start navigating.""" + if name in self.child_turtles: + self.child_turtles[name]['teleported'] = True # ← UNLOCK + self.get_logger().info( + f'{name} teleport confirmed — navigation unlocked.') + + def child_pose_callback(self, msg, name): + if name in self.child_turtles: + self.child_turtles[name]['pose'] = msg + + # ------------------------------------------------------------------ # + # Child turtle navigation # + # ------------------------------------------------------------------ # + + def control_children(self): + for name, data in self.child_turtles.items(): + if name == 'turtle5': + continue + + # KEY GUARD: skip until teleport is confirmed. + if not data.get('teleported', False): + continue + + pose = data['pose'] + if pose is None: + continue + + dist = math.hypot(self.start_x - pose.x, self.start_y - pose.y) + desired_h = math.atan2( + self.start_y - pose.y, self.start_x - pose.x) + h_err = self.normalize_angle(desired_h - pose.theta) + + cmd = Twist() + if dist > 0.05: + cmd.angular.z = 2.0 * h_err + # Strict heading threshold (0.02 rad) keeps paths straight + if abs(h_err) > 0.02: + cmd.linear.x = 0.0 + else: + cmd.linear.x = min(1.5, dist * 1.5) + else: + cmd.linear.x = 0.0 + cmd.angular.z = 0.0 + + data['pub'].publish(cmd) + + # ------------------------------------------------------------------ # + # Main control loop # + # ------------------------------------------------------------------ # + + def control_loop(self): + if self.current_pose is None: + return + + self.control_children() + cmd = Twist() + + # --- Initialization Phase --- + if self.state == 'INIT_PEN_OFF': + if self.pen_client.wait_for_service(timeout_sec=0.1): + self.pen_client.call_async(SetPen.Request(off=1)) + self.state = 'INIT_TELEPORT' + + elif self.state == 'INIT_TELEPORT': + if self.teleport_client.wait_for_service(timeout_sec=0.1): + self.teleport_client.call_async( + TeleportAbsolute.Request( + x=self.start_x, y=self.start_y, theta=0.0)) + self.state = 'INIT_WAIT' + + elif self.state == 'INIT_WAIT': + if (abs(self.current_pose.x - self.start_x) < 0.05 and + abs(self.current_pose.y - self.start_y) < 0.05): + self.state = 'INIT_PEN_ON' + + elif self.state == 'INIT_PEN_ON': + self.pen_client.call_async( + SetPen.Request(off=0, r=255, g=255, b=255, width=3)) + self.edges_completed = 0 + self.state = 'FORWARD' + self.get_logger().info( + 'Teleported to start. Beginning square pattern.') + + # --- Linear Movement --- + elif self.state == 'FORWARD': + target_x, target_y = self.waypoints[self.edges_completed] + + if self.edges_completed == 0: + dist_remaining = target_x - self.current_pose.x + elif self.edges_completed == 1: + dist_remaining = self.current_pose.y - target_y + elif self.edges_completed == 2: + dist_remaining = self.current_pose.x - target_x + elif self.edges_completed == 3: + dist_remaining = target_y - self.current_pose.y + + if dist_remaining > 0: + dist_to_target = math.hypot( + target_x - self.current_pose.x, + target_y - self.current_pose.y) + if dist_to_target > 0.05: + desired_heading = math.atan2( + target_y - self.current_pose.y, + target_x - self.current_pose.x) + else: + desired_heading = self.target_headings[self.edges_completed] + + h_err = self.normalize_angle( + desired_heading - self.current_pose.theta) + cmd.angular.z = 2.0 * h_err + + if dist_remaining > self.linear_ramp_dist: + cmd.linear.x = self.high_linear_vel + else: + speed_ratio = dist_remaining / self.linear_ramp_dist + cmd.linear.x = max( + self.low_linear_vel + + (self.high_linear_vel - self.low_linear_vel) * speed_ratio, + self.low_linear_vel) + else: + cmd.linear.x = 0.0 + cmd.angular.z = 0.0 + self.wait_ticks = 0 + self.state = 'WAIT_STOP_LINEAR' + + # --- Linear Braking / Wait & Absolute Spawning --- + elif self.state == 'WAIT_STOP_LINEAR': + cmd.linear.x = 0.0 + cmd.angular.z = 0.0 + self.wait_ticks += 1 + if self.wait_ticks >= 5: + turtle_name = f'turtle{self.edges_completed + 2}' + target_x, target_y = self.waypoints[self.edges_completed] + + if self.spawn_client.wait_for_service(timeout_sec=1.0): + req = Spawn.Request( + x=float(target_x), + y=float(target_y), + theta=float(self.current_pose.theta), + name=turtle_name + ) + future = self.spawn_client.call_async(req) + future.add_done_callback( + lambda f, n=turtle_name, cx=target_x, cy=target_y: + self.spawn_done_callback(f, n, cx, cy) + ) + self.state = 'WAITING_FOR_SPAWN' + self.get_logger().info( + f'Stationary. Spawning {turtle_name} at ' + f'({target_x:.3f}, {target_y:.3f}).') + else: + self.get_logger().error('Failed to call /spawn service.') + + elif self.state == 'WAITING_FOR_SPAWN': + cmd.linear.x = 0.0 + cmd.angular.z = 0.0 + + # --- Turning Movement --- + elif self.state == 'TURN': + target_heading = self.target_headings[self.edges_completed] + h_err = self.normalize_angle( + target_heading - self.current_pose.theta) + + if abs(h_err) > self.angular_stop_thresh: + cmd.angular.z = 2.5 * h_err + if cmd.angular.z > 0: + cmd.angular.z = max(min(cmd.angular.z, 1.5), 0.1) + else: + cmd.angular.z = min(max(cmd.angular.z, -1.5), -0.1) + else: + cmd.angular.z = 0.0 + self.wait_ticks = 0 + self.state = 'WAIT_STOP_ANGULAR' + + # --- Angular Braking / Wait --- + elif self.state == 'WAIT_STOP_ANGULAR': + cmd.linear.x = 0.0 + cmd.angular.z = 0.0 + self.wait_ticks += 1 + if self.wait_ticks >= 5: + self.state = 'FORWARD' + self.get_logger().info( + 'Rotation stabilized. Proceeding to next edge.') + + # --- Task Completed --- + elif self.state == 'STOP': + cmd.linear.x = 0.0 + cmd.angular.z = 0.0 + + self.cmd_vel_pub.publish(cmd) + + # ------------------------------------------------------------------ # + # Spawn callback # + # ------------------------------------------------------------------ # + + def spawn_done_callback(self, future, name, corner_x, corner_y): + self.edges_completed += 1 + + if self.edges_completed >= 4: + self.state = 'STOP' + self.get_logger().info('Square closed. 5 turtles active. Auto-shutting down launch in 3 seconds...') + # 設定 3 秒後觸發自我結束 + self.create_timer(3.0, self.shutdown_node) + else: + self.setup_child_turtle(name, corner_x, corner_y) + self.state = 'TURN' + self.get_logger().info('Spawn successful. Rotating...') + + # ------------------------------------------------------------------ # + # Shutdown Function # + # ------------------------------------------------------------------ # + def shutdown_node(self): + """結束目前 Python 行程,以觸發 launch 的 OnProcessExit""" + self.get_logger().info('Mission complete. Exiting controller node...') + sys.exit(0) + + +# ---------------------------------------------------------------------- # + +def main(args=None): + rclpy.init(args=args) + node = TurtleController() + try: + rclpy.spin(node) + except KeyboardInterrupt: + pass + except SystemExit: + # 捕捉 sys.exit() 以確保優雅關閉 + pass + finally: + node.destroy_node() + # 檢查 rclpy 是否已經 shutdown,避免重複 shutdown 產生錯誤 + if rclpy.ok(): + rclpy.shutdown() + + +if __name__ == '__main__': + main()