# ROS 基本介紹
###### tags: `ROS`
[TOC]
## Installation
[(VMware虛擬機安裝)](https://hackmd.io/@NL7lSHMfR5W9am2Vnpxa5Q/HJgLrnym_)
[官網安裝說明](http://wiki.ros.org/melodic/Installation/Ubuntu)
Ver: melodic
```bash=
sudo sh -c 'echo "deb http://packages.ros.org/ros/ubuntu $(lsb_release -sc) main" > /etc/apt/sources.list.d/ros-latest.list'
sudo apt-key adv --keyserver 'hkp://keyserver.ubuntu.com:80' --recv-key C1CF6E31E6BADE8868B172B4F42ED6FBAB17C654
sudo apt update
sudo apt install ros-melodic-desktop-full
apt search ros-melodic
echo "source /opt/ros/melodic/setup.bash" >> ~/.bashrc
source ~/.bashrc
sudo apt install python-rosdep python-rosinstall python-rosinstall-generator python-wstool build-essential
sudo apt install python-rosdep
sudo rosdep init
rosdep update
```
- 安裝成功與否測試 (跳出附圖才算成功)
```bash==
roscore
```
最後按`Ctrl+C`結束`roscore`
![](https://i.imgur.com/xTxRQ5k.png)
## Introduction
- ROS的全名是Robot Operating System,是在 Linux 中專門設計來控制機器人與自動化裝置的軟體架構,全世界的研究員都可以基於這個架構分享各種機器人相關研究的資源和演算法
- ROS將節點間的傳輸變得很容易,也讓須執行多功能的機器人各項任務都可以被模組化
- 只要符合ROS定義好的格式,便可以達成檔案(節點)間的資料連結。
- [ROS支援sensor](http://wiki.ros.org/Sensors)
- [ROS自學筆記](https://sychaichangkun.gitbooks.io/ros-tutorial-icourse163/content/)
## ROS 檔案存放的基本架構
```
-- workspace
-- build
-- devel
-- src
-- package1
-- package2
-- launch
-- CMakelists.txt
-- package.xml
-- src
-- node.cpp
-- ...
-- ...
-- ...
```
##### Workspace工作空間
如果沒有工作空間,那就不能使用 ROS,就假設這是個虛擬平台,連結著所有的節點。
##### Package包裹
包裹中可以存放所需的節點。通常一個包裹代表一個特定的任務,節點則是任務的拆解子任務。
##### Node節點
節點,就是執行檔。通常會包含輸入與輸出資料。編譯完後,就可以直接當一塊積木,鑲嵌在其他積木上。
---
**以下為新建一個基本的ROS工作環境步驟:**
### 1. 建立工作空間
[ROS wiki 說明](http://wiki.ros.org/catkin/Tutorials/create_a_workspace)
- 建立一個工作空間(範例)
```
$ source /opt/ros/melodic/setup.bash
$ mkdir -p ~/catkin_ws/src
$ cd ~/catkin_ws/
$ catkin_make
```
- 為了讓ros可以認得自己的工作空間,需要source自己工作空間下的```setup.bash```
- 如果上述步驟都照著範例做,可以直接下指令```$ source ~/catkin_ws/devel/setup.bash```或是直接將```source ~/catkin_ws/devel/setup.bash```加在```~/.bashrc```的最後一行,在每次開啟新終端機的時候自動執行。
### 2. ROS Package建立
[ROS wiki 說明](http://wiki.ros.org/ROS/Tutorials/CreatingPackage)
package 通常會被建立於workspace/src底下,如果想用別人的github中的package通常也是下載在這一層當中
```
$ catkin_create_pkg <package_name> [depend1] [depend2] [depend3]
```
範例:
```
$ cd ~/catkin_ws/src
$ catkin_create_pkg beginner_tutorials std_msgs rospy roscpp
$ git clone https://github.com/WangMahua/ros_tutorial.git
```
### 3. 編譯
##### catkin_make
在ROS的編譯是靠catkin_make這個指令來完成的,只要修改package中的CMakelist檔,就可以新增編譯的檔案
```
$ cd ~/catkin_ws/
$ catkin_make
```
## ROS通訊基本架構
![](https://i.imgur.com/p5nDCgf.png)
##### ROS Mater
相當於所有節點的管理中心,在運行任何ros節點前必須先啟動
##### Launch file
ROS 中通常在包裹中都會有一個 Launch 資料夾,裡面存放不同的 launch 文件,只要執行一個 launch 文件,便會一次啟動所有需要的節點。
##### Topic
成員:publisher & subscirber
topic就像一個定好發布格式的佈告欄,publisher node將資料貼上去,subscriber 就只能看資料不能對佈告欄做更動
##### Service
成員:client & server
service則是採用一來一往的模式,唯有當client呼叫service,server才會給出回饋
## ros 通訊格式
- msg (for topic)
範例:
[geometry_msgs/TwistStamped](http://docs.ros.org/en/noetic/api/geometry_msgs/html/msg/TwistStamped.html)
```
std_msgs/Header header
uint32 seq
time stamp
string frame_id
geometry_msgs/Twist twist
geometry_msgs/Vector3 linear
float64 x
float64 y
float64 z
geometry_msgs/Vector3 angular
float64 x
float64 y
float64 z
```
- srv (for service)
- 範例
[AddTwoInts](http://docs.ros.org/en/jade/api/rospy_tutorials/html/srv/AddTwoInts.html)
通常官方就已經定義好很多基本常用的msg,如果需要自訂義msg/srv可以參考[官方教程](http://wiki.ros.org/ROS/Tutorials/CreatingMsgAndSrv)
## node撰寫
基本上會被放置於<package_name>/src下
### 基本node架構
基本要件有3個:
- include ros.h
- node init
- handler define
```cpp=
#include <ros/ros.h> // 引用 ros.h 檔
int main(int argc, char** argv){
ros::init(argc, argv, "hello_cpp_node"); // 初始化 hello_cpp_node
ros::NodeHandle handler; // node 的 handler
ROS_INFO("Hello World!"); // 印出 Hello World
}
```
##### 編譯
如果要編譯上面這個新寫的node,必須將node加入CMakelists.txt,並加入下列兩行
```
add_executable(hi src/hello_cpp_node.cpp)
target_link_libraries(hi ${catkin_LIBRARIES})
```
##### 執行
如果要執行上面這個新寫的node
```
rosrun beginner_tutorials hi
```
## topic撰寫
[subscriber/publisher撰寫](http://wiki.ros.org/ROS/Tutorials/WritingPublisherSubscriber%28c%2B%2B%29)
[std_msgs/string](http://docs.ros.org/en/noetic/api/std_msgs/html/msg/String.html)
### publisher
必要條件如下:
- 前述node該有的東西
- include 需要的msg檔
- publisher define
```
ros::Publisher <自訂義pub名稱>;
<自訂義pub名稱> = <nodehandle name>.advertise<topic msg type>("topic name", 佇列大小);
```
- pub command
```
<自訂義pub名稱>.publish(要傳出去的內容)
```
- 官方教學(publisher.cpp)
```cpp=
#include "ros/ros.h" //使用ros必須要加此標頭檔
#include "std_msgs/String.h" //你要使用的msg檔案
#include <sstream>
int main(int argc, char **argv){
ros::init(argc, argv, "talker"); //node名稱定義
ros::NodeHandle n; //nodehandle名稱定義
ros::Publisher chatter_pub = n.advertise<std_msgs::String>("chatter", 1000); //pub定義
ros::Rate loop_rate(10);
int count = 0;
while (ros::ok()){
std_msgs::String msg;
std::stringstream ss;
ss << "hello world " << count;
msg.data = ss.str();
ROS_INFO("%s", msg.data.c_str());
chatter_pub.publish(msg);//此行將訊息發布,括號內格式須與ros::publisher定義的相同
ros::spinOnce(); //此行執行所有callback
loop_rate.sleep();
++count;
}
return 0;
}
```
### subscriber
必要條件如下:
- 前述node該有的東西
- include 需要的msg檔
- subscriber define
```
ros::Subscriber <自訂義sub名稱>;
<自訂義sub名稱> = <nodehandle name>.subscribe("topic name", 佇列大小,<callback function名稱>);
```
- callback function
```
void <callback function名稱>(const <topic msg type>::ConstPtr& msg)
```
- 官方教學(subscriber.cpp)
```cpp=
#include "ros/ros.h" //使用ros必須要加此標頭檔
#include "std_msgs/String.h" //你要使用的msg檔案
void chatterCallback(const std_msgs::String::ConstPtr& msg){ //callback function
ROS_INFO("I heard: [%s]", msg->data.c_str());
}
int main(int argc, char **argv){
ros::init(argc, argv, "listener"); //node名稱定義
ros::NodeHandle n; //nodehandle名稱定義
ros::Subscriber sub = n.subscribe("chatter", 1000, chatterCallback);//subscriber定義
ros::spin(); //將程式固定在此行,並重複執行所有callback
return 0;
}
```
#### 小練習:試著自己新增node進Cmakelist然後將兩個node分別用rosrun執行一下
結果應該的像這樣:
![](https://i.imgur.com/S1ZOkUJ.png)
## service撰寫
[sever/client撰寫](http://wiki.ros.org/ROS/Tutorials/WritingServiceClient%28c%2B%2B%29)
[AddTwoInts.srv](http://docs.ros.org/en/jade/api/rospy_tutorials/html/srv/AddTwoInts.html)
[ROS 創立srv](http://wiki.ros.org/ROS/Tutorials/CreatingMsgAndSrv)
### 創立srv
msg也是差不多的流程
```
$ cd ~/catkin_ws/src/beginner_tutorials
$ mkdir srv
$ gedit AddTwoInts.srv
```
package.xml請新增以下部份:
```
<build_depend>message_generation</build_depend>
<exec_depend>message_runtime</exec_depend>
```
CMakelists.txt請修改以下部份:
```
find_package(catkin REQUIRED COMPONENTS
roscpp
rospy
std_msgs
message_generation
)
```
```
catkin_package(
...
CATKIN_DEPENDS message_runtime ...
...)
```
```
generate_messages(
DEPENDENCIES
std_msgs
)
```
```
add_service_files(
FILES
AddTwoInts.srv
)
```
### server
必要條件如下:
- 前述node該有的東西
- include 需要的srv檔
- sever define
```
ros::ServiceServer <自訂義server名稱>;
<自訂義server名稱> = <nodehandle name>.advertiseService("service名稱", server_function);
```
- server function
```
void server_function(<srv_type>::Request &req,
<srv_type>::Response &res)
```
- 官方教學(sever.cpp)
```cpp=
#include "ros/ros.h" //使用ros必須要加此標頭檔
#include "beginner_tutorials/AddTwoInts.h" //你要使用的srv檔案
bool add(beginner_tutorials::AddTwoInts::Request &req,
beginner_tutorials::AddTwoInts::Response &res){//server function
res.sum = req.a + req.b;
ROS_INFO("request: x=%ld, y=%ld", (long int)req.a, (long int)req.b);
ROS_INFO("sending back response: [%ld]", (long int)res.sum);
return true;
}
int main(int argc, char **argv){
ros::init(argc, argv, "add_two_ints_server");
ros::NodeHandle n;
ros::ServiceServer service = n.advertiseService("add_two_ints", add);
ROS_INFO("Ready to add two ints.");
ros::spin();
return 0;
}
```
### client
必要條件如下:
- 前述node該有的東西
- include 需要的srv檔
- client define
```
ros::ServiceClient <自訂義client名稱>;
<自訂義client名稱> = <nodehandle name>.serviceClient("service名稱");
```
- call server
```
<自訂義client名稱>.call(srv)
```
- 官方教學(client.cpp)
```cpp=
#include "ros/ros.h"
#include "beginner_tutorials/AddTwoInts.h"
#include <cstdlib>
int main(int argc, char **argv)
{
ros::init(argc, argv, "add_two_ints_client");
if (argc != 3)
{
ROS_INFO("usage: add_two_ints_client X Y");
return 1;
}
ros::NodeHandle n;
ros::ServiceClient client = n.serviceClient<beginner_tutorials::AddTwoInts>("add_two_ints");
beginner_tutorials::AddTwoInts srv;
srv.request.a = atoll(argv[1]);
srv.request.b = atoll(argv[2]);
if (client.call(srv))
{
ROS_INFO("Sum: %ld", (long int)srv.response.sum);
}
else
{
ROS_ERROR("Failed to call service add_two_ints");
return 1;
}
return 0;
}
```
#### 小練習:試著自己新增node進Cmakelist然後將兩個node分別用rosrun執行一下
![](https://i.imgur.com/chcTmb4.png)
如果報錯把這行加進CMakelists.txt
```
...
add_dependencies( <your_client_name> ${catkin_EXPORTED_TARGETS})
...
add_dependencies( <your_server_name> ${catkin_EXPORTED_TARGETS})
```
## rostopic 指令
[ros wiki說明](http://wiki.ros.org/rostopic)
- ```$ rostopic list```: 列出現在ros上所有的topic
- ```$ rostopic echo <topic-name>``` : 顯示某個topic上的數值
- ```$ rostopic info <topic-name>``` : 顯示某個topic的資訊
- ```$ rostopic pub <topic-name> <topic-type> [data...]``` : 在某個topic上發布資料
- 範例
- 在其中一個terminal下指令```$ rostopic pub /test std_msgs/String hello```
- 在另一個terminal下指令```rostopic list```,可以看到多一個topic
- 在第二個terminal下```rostopic echo /test```,並從新執行第一個terminal的指令,可以看到傳到topic上的data
## rosparam
ros master底下內建了一個parameter server,是用來控管全域變數,當今天我們沒有要動到code順序,只是想要測試不同數值,parameter就變得十分好用,因為在cpp檔中做任何更動就需要catkin_make,如果專案過大常常需要編譯非常久,這時候使用ros param去修改參數就非常方便。
- ```$ rosparam list```: 看目前所有參數
- ```$ rosparam set <parameter_name> <value>``` : 參數設置
- ```$ rosparam get <parameter_name>``` : 查看目前這個參數的內容是什麼
```cpp=
ros::NodeHandle nh;
int param;
nh.getParam("/param_name", param);
```
## 練習
設定無人機從(0,0,0)出發,每隔固定頻率(初始值為1秒)以速度(1,-1,1)出發,請用publisher以該頻率發布位置訊息到/UAV_pos這個topic上,並製作一個subscriber接收此topic印出目前無人機位置,其中:
1. 頻率請用rosparam撰寫,讓人可以任意改變頻率。
2. 該topic格式請用[此格式](http://docs.ros.org/en/noetic/api/geometry_msgs/html/msg/Point.html)