# ROS 工具 ###### tags: `ROS` [TOC] ## 昨天回顧 [昨天的講義](https://hackmd.io/icK5Y4f7RSq1bwdqgZEymQ) [昨天練習的檔案](https://github.com/WangMahua/ros_tutorial.git) 1. 如果錯誤顯示找不到檔案,可以先試試source ``` $ cd ~/catkin_ws $ source devel/setup.bash ``` 或是直接開啟~/.bashrc將source 指令加入.bashrc檔 ``` $ gedit ~/.bashrc ``` 在最後一行加上```source ~/catkin_ws/devel/setup.bash``` 2. 如何運行節點 ``` $ rosrun <package_name> <node_name> ``` ## Rviz Rviz是用來將topic可視化的工具,像是點雲圖、相機圖片 1. 原始畫面 ![](https://i.imgur.com/coDdmUN.png) 2. 可以從add by topic將想看的topic內容加入rviz ![](https://i.imgur.com/2MvfrgZ.png) 3. 就可以看到想看到的內容了~ ![](https://i.imgur.com/RIhX9kh.png) #### 小嘗試:利用rviz看topic 1. 下載 usb_cam ``` $ cd ~/catkin_ws/src $ git clone https://github.com/ros-drivers/usb_cam.git $ cd ~/catkin_ws $ catkin_make ``` 2. rosrun 節點 ``` $ roscore $ rosrun usb_cam usb_cam_node ``` 3. 開啟rviz按下add,選add by topic就可以看到多出image的部份,點開後就可以透過rviz檢查camera畫面了 ## rqt_graph 用來查看topic的流向 ![](https://i.imgur.com/4vSgnAb.png) ## rqt_plot 右上角綠色+號可以新增plot ![](https://i.imgur.com/2luLiKI.png) ## rosbag [ros wiki說明](http://wiki.ros.org/rosbag/Commandline) rosbag用於紀錄被topic發布過的訊息,這樣的功能對於我們在debug也相當方便,可以透過回放檢查數據是否有哪邊出問題 - ```$ rosbag record -a```: 錄製所有topic資訊 - ```$ rosbag record <topic_name>``` : 錄製某個topic上的資訊 - ```$ rosbag info <topic_name>``` : 查看某個bag檔錄製了甚麼資訊 - ```rosbag play <bag_name>``` : 回放某個bag檔錄製的資訊 - ```rosbag play -r 0.5 <bag_name>``` : 以0.5倍速回放某個bag檔錄製的資訊,其中0.5這個參數可被替換為任意希望回放的倍速 - ```rosbag play -l <bag_name>``` : 循環播放bag內容 ## tf [ROS wiki](http://wiki.ros.org/navigation/Tutorials/RobotSetup/TF) ![](https://i.imgur.com/LAqKq9a.png) tf是用於座標轉換的工具,因為很多時候sensor讀取到的資訊並不能直接套用在world frame或機器人質心的frame上,所以必須使用更動座標系 要構成tf,就必須要有發布座標轉換的tf broadcaster,負責定義座標系間的轉換 ##### 修改CMakelists.txt ``` find_package(catkin REQUIRED COMPONENTS roscpp rospy std_msgs message_generation tf ) ``` ##### tf_broadcaster.cpp 主要是透過這行來定義座標系的轉換 ```cpp= broadcaster.sendTransform( tf::StampedTransform( tf::Transform(tf::Quaternion(0, 0, 0, 1), tf::Vector3(0.1, 0.0, 0.2)), ros::Time::now(),"base_link", "base_laser")); ``` 其中tf::Transform的第一個變數用於定義座標系旋轉、第二個用於座標系平移 ```cpp= #include <ros/ros.h> #include <tf/transform_broadcaster.h> int main(int argc, char** argv){ ros::init(argc, argv, "robot_tf_publisher"); ros::NodeHandle n; ros::Rate r(100); tf::TransformBroadcaster broadcaster; while(n.ok()){ broadcaster.sendTransform( tf::StampedTransform( tf::Transform(tf::Quaternion(0, 0, 0, 1), tf::Vector3(0.1, 0.0, 0.2)), ros::Time::now(),"base_link", "base_laser")); r.sleep(); } } ``` ##### tf_listener.cpp 最重要的是這兩行: ```cpp= listener.waitForTransform("/base_laser","/base_link", ros::Time(), ros::Duration(5.0)); listener.transformPoint("/base_link", laser_point, base_point); ``` 只要發布了tf轉換,用上面兩行就可以達成座標系轉換 ```cpp= #include <ros/ros.h> #include <geometry_msgs/PointStamped.h> #include <tf/transform_listener.h> int main(int argc, char** argv){ ros::init(argc, argv, "robot_tf_listener"); ros::NodeHandle n; tf::TransformListener listener(ros::Duration(10)); geometry_msgs::PointStamped laser_point; laser_point.header.frame_id = "/base_laser"; //we'll just use the most recent transform available for our simple example laser_point.header.stamp = ros::Time(); //just an arbitrary point in space laser_point.point.x = 1.0; laser_point.point.y = 0.2; laser_point.point.z = 0.0; try{ geometry_msgs::PointStamped base_point; listener.waitForTransform("/base_laser","/base_link", ros::Time(), ros::Duration(5.0)); listener.transformPoint("/base_link", laser_point, base_point); ROS_INFO("base_laser: (%.2f, %.2f. %.2f) -----> base_link: (%.2f, %.2f, %.2f) at time %.2f", laser_point.point.x, laser_point.point.y, laser_point.point.z, base_point.point.x, base_point.point.y, base_point.point.z, base_point.header.stamp.toSec()); }catch(tf::TransformException& ex){ ROS_ERROR("Received an exception trying to transform a point from \"base_laser\" to \"base_link\": %s", ex.what()); } } ``` #### tf debug 可以生成目前所有的座標系轉換 ``` $ rosrun tf view_frames $ evince frames.pdf ``` #### 20.04可能會遇到的bug https://github.com/ros/geometry/pull/193 ## roslaunch 我們在寫專案的時候往往都會需要使用多個檔案,如果每一個都用rosrun去跑就會變得相當麻煩,此時就可以使用launch檔來運行多個節點,其中launch檔會被放置於<package_name>/launch這個資料夾底下: ```bash= cd <package_name> mkdir launch cd launch gedit startup.launch roslaunch <pckage_name> startup.launch ``` 介紹一下基本launch檔內容,基本上launch node需要被包在<launch>與</launch>之間: ``` <launch> <rosparam command="load" file="$(find beginner_tutorials)/config/param.yaml" /> <node pkg="beginner_tutorials" type="uav_pub" name="uav_pub" output="screen" > </node> <node pkg="beginner_tutorials" type="uav_sub" name ="uav_sub" output="screen" > </node> <include file="$(find openni_launch_marvin)/launch/kinect_left.launch" /> </launch> ``` 1. node啟動 最常用的功能,有以下兩種表達方式: ``` <node pkg="beginner_tutorials" type="uav_pub" name="uav_pub" output="screen" > </node> ``` or ``` <node pkg="beginner_tutorials" type="uav_pub" name="uav_pub" output="screen" /> ``` 2. roslaunch 其他 launch file 可以用來launch其他package中的launch檔 ``` <include file="$(find openni_launch_marvin)/launch/kinect_left.launch" /> </launch> ``` 3. remap 節點映射用於更換topic name,就不用進去修改檔案 ``` <remap from="topic_1" to="topic_2" /> ``` 4. param 使用 如果param每次都用command line去給會過長,可以用下面的方法直接打在launch檔中,第一個方法是使用.yaml賦值: ``` <rosparam command="load" file="$(find beginner_tutorials)/config/param.yaml" /> ``` or ``` <param name="param_name" type="int" value="10" /> ``` 5. tf轉換 不用自己開一個broadcaster直接用指令賦值就可以產生 ```! <node pkg="tf" type="static_transform_publisher" name="base_link_to_laser" args="0.1 0.0 3.0 0.0 0.0 0.0 /base_link /base_laser 40" /> ``` 前三個數是xyz座標,後三個是yaw pitch roll 三個角度,兩個frame_id第一個是父座標系,後一個是子座標系 #### 小練習: 1. 試著launch不同package中的launch file,可以用昨天的檔案試試看 沒有下載過得直接git clone 下載過得請git pull把更新拉下來 https://github.com/WangMahua/ros_tutorial.git 2. 試著寫寫看.yaml檔或是利用launch檔直接修改ros param 3. 試著透過launch檔發布一個tf broadcater ## ROS 多台電腦連線 電腦需連至同個內網 ``` $ gedit ~/.bashrc ``` 在文件中加上 ``` export ROS_IP=<自己的IP> export ROS_MASTER_URI=http://<roscore機器的IP>:11311 ``` 查看自己IP的方法 ``` $ ifconfig ``` https://github.com/ros/geometry/pull/193 ## 練習 ### odometry 積分 1.先下載bag檔 https://reurl.cc/GoeeOx ``` 今天的練習為實作odometry sensor 的積分, 與昨天練習類似, 昨天是以速度向量來更新位置向量, 今天速度資訊從odometry sensor拿 ``` ![](https://i.imgur.com/89evKoX.png =80%x) ![](https://i.imgur.com/fVVeVWF.png) ``` 1.請寫一個subscriber訂閱sensor的資料 2.寫兩個publisher,一個發布從sensor拿到的位置資訊當ground_truth 另一個發布速度積分後的結果 3.將兩個publisher發布的topic用rqt_plot繪出做比較 ``` Output ![](https://i.imgur.com/Nzwqqze.png) ``` 虛線為groundtruth,實線為積分後的結果 ``` ``` 註 Initial Point P0為 (0.627782718948, 0.0194202284956, 1.32679280569) ``` 2.在CMakeLists.txt中添加eigen package ``` find_package(Eigen3) include_directories( ${catkin_INCLUDE_DIRS} ${EIGEN3_INCLUDE_DIR} ) ``` ```cpp= #include "ros/ros.h" #include "geometry_msgs/PoseStamped.h" #include "geometry_msgs/Vector3Stamped.h" #include <nav_msgs/Odometry.h> #include <eigen3/Eigen/Dense> #include <eigen3/Eigen/Core> #include <iostream> #include <cmath> double last_time = 0; double now_time; double dt = 0; // define publisher ros::Publisher pub_pose_ground_truth; ros::Publisher pub_pose; Eigen::Vector3d pos; //geometry_msgs::PoseStamped rpyGoal; geometry_msgs::Vector3Stamped pose_ground_truth; geometry_msgs::Vector3Stamped pose; geometry_msgs::PoseStamped pose_rviz; void Predict(const nav_msgs::Odometry::ConstPtr& msg) { //Define now_time, last_time and dt if(last_time != 0) { now_time = msg->header.stamp.toSec(); dt = now_time - ???; ??? = now_time; } else { last_time = msg->header.stamp.toSec(); } // position groundtruth Eigen::Vector3d pos_ground(msg->???, msg->pose.pose.position.y, msg->pose.pose.position.z); // velocity groundtruth Eigen::Vector3d vel_ground(msg->twist.twist.linear.x, msg->twist.twist.linear.y, msg->???); // Integral equation pos = ??? + ??? * dt; pose_ground_truth.header.stamp=ros::Time::now(); pose_ground_truth.vector.x = ???.x(); pose_ground_truth.vector.y = ???.y(); pose_ground_truth.vector.z = ???.z(); pose.header.stamp=ros::Time::now(); pose.vector.x = ???.x(); pose.vector.y = ???.y(); pose.vector.z = ???.z(); } int main(int argc, char** argv) { ros::init(argc, argv, "integral_odometry"); pos << ???; ros::NodeHandle nh; ros::Subscriber odometry_sub; odometry_sub = nh.subscribe("???", 100 ,Predict); ros::Rate r(100); while (ros::ok()) { ??? = nh.advertise<geometry_msgs::Vector3Stamped>("pose_ground_truth", 100); ??? = nh.advertise<geometry_msgs::Vector3Stamped>("position", 100); ???.publish(???); ???.publish(???); ros::spinOnce(); r.sleep(); } return 0; } ``` ## 解答 ```cpp= #include "ros/ros.h" #include "geometry_msgs/PoseStamped.h" #include "geometry_msgs/Vector3Stamped.h" #include <nav_msgs/Odometry.h> #include <Eigen/Dense> #include <Eigen/Core> #include <iostream> #include <cmath> double last_time = 0; double now_time; double dt = 0; ros::Publisher pub_pose_ground_truth; ros::Publisher pub_pose; Eigen::Vector3d pos; //geometry_msgs::PoseStamped rpyGoal; geometry_msgs::Vector3Stamped pose_ground_truth; geometry_msgs::Vector3Stamped pose; void Predict(const nav_msgs::Odometry::ConstPtr& msg) { //Time if(last_time != 0) { now_time = msg->header.stamp.toSec(); dt = now_time - last_time; last_time = now_time; } else { last_time = msg->header.stamp.toSec(); } Eigen::Vector3d pos_sensor(msg->pose.pose.position.x, msg->pose.pose.position.y, msg->pose.pose.position.z); Eigen::Vector3d vel(msg->twist.twist.linear.x, msg->twist.twist.linear.y, msg->twist.twist.linear.z); pos = pos + vel * dt; pose_ground_truth.header.stamp=ros::Time::now(); pose_ground_truth.vector.x = pos_sensor.x(); pose_ground_truth.vector.y = pos_sensor.y(); pose_ground_truth.vector.z = pos_sensor.z(); pose.header.stamp=ros::Time::now(); pose.vector.x = pos.x(); pose.vector.y = pos.y(); pose.vector.z = pos.z(); } int main(int argc, char** argv) { ros::init(argc, argv, "ekf"); pos << 0.627782718948, 0.0194202284956, 1.32679280569; ros::NodeHandle nh; ros::Subscriber odometry_sub; odometry_sub = nh.subscribe("/iris1/ground_truth/odometry", 100 ,Predict); ros::Rate r(100); while (ros::ok()) { pub_pose_ground_truth = nh.advertise<geometry_msgs::Vector3Stamped>("pose_ground_truth", 100); pub_pose = nh.advertise<geometry_msgs::Vector3Stamped>("position", 100); pub_pose_ground_truth.publish(pose_ground_truth); pub_pose.publish(pose); ros::spinOnce(); r.sleep(); } return 0; } ```