ROS重名問題
ROS工作空間覆寫
所謂工作空間覆寫,是指不同工作空間中,存在重名的功能包的情形。
ROS 開發中,會自定義工作空間且自定義工作空間可以同時存在多個,可能會出現一種情況: 雖然特定工作空間内的功能包不能重名,但是自定義工作空間的功能包與内置的功能包可以重名或者不同的自定義的工作空間中也可以出現重名的功能包,那麼調用該名稱功能包時,會調用哪一個呢?比如:自定義工作空間A存在功能包 turtlesim,自定義工作空間B也存在功能包 turtlesim,當然系統内置空間也存在turtlesim,如果調用turtlesim包,會調用哪個工作空間中的呢?
實作
0.建立工作空間A與工作空間B,兩個工作空間中都建立功能包: turtlesim。
1.配置環境變量使得在任何終端下都能調用A和B,在 (主目錄下按Ctrl+H)~/.bashrc 檔案下追加目前工作空間的 bash 格式如下:
source /home/使用者/路徑/工作空間A/devel/setup.bash
source /home/使用者/路徑/工作空間B/devel/setup.bash
2.新開指令行:
source .bashrc
加載環境變量
3.檢視ROS環境環境變量
echo $ROS_PACKAGE_PATH
結果:自定義工作空間B:自定義空間A:系統内置空間
4.調用指令:
roscd turtlesim
會進入自定義工作空間B
原因
ROS 會解析 .bashrc 檔案,并生成 ROS_PACKAGE_PATH ROS包路徑,該變量中按照 .bashrc 中配置設定工作空間優先級,在設定時需要遵循一定的原則:ROS_PACKAGE_PATH 中的值,和 .bashrc 的配置順序相反—>後配置的優先級更高,如果更改自定義空間A與自定義空間B的source順序,那麼調用時,将進入工作空間A。
結論
功能包重名時,會按照 ROS_PACKAGE_PATH 查找,配置在前的會優先執行。
隐患
存在安全隐患,比如目前工作空間B優先級更高,意味着當程式調用 turtlesim 時,不會調用工作空間A也不會調用系統内置的 turtlesim,如果工作空間A在實作時有其他功能包依賴于自身的 turtlesim,而按照ROS工作空間覆寫的涉及原則,那麼實際執行時将會調用工作空間B的turtlesim,進而導緻執行異常,出現安全隐患。

BUG 說明:
當在 .bashrc 檔案中 source 多個工作空間後,可能出現的情況,在 ROS PACKAGE PATH 中隻包含兩個工作空間,可以删除自定義工作空間的 build 與 devel 目錄,重新 catkin_make,然後重新載入 .bashrc 檔案,問題解決。
ROS節點名稱重名
場景:ROS 中建立的節點是有名稱的,C++初始化節點時通過API:
ros::init(argc,argv,"xxxx");
來定義節點名稱,在Python中初始化節點則通過
rospy.init_node("yyyy")
來定義節點名稱。在ROS的網絡拓撲中,是不可以出現重名的節點的,因為假設可以重名存在,那麼調用時會産生混淆,這也就意味着,不可以啟動重名節點或者同一個節點啟動多次,的确,在ROS中如果啟動重名節點的話,之前已經存在的節點會被直接關閉,但是如果有這種需求的話,怎麼優化呢?
在ROS中給出的解決政策是使用命名空間或名稱重映射。
命名空間就是為名稱添加字首,名稱重映射是為名稱起别名。這兩種政策都可以解決節點重名問題,兩種政策的實作途徑有多種:
- rosrun 指令
- launch 檔案
- 編碼實作
rosrun設定命名空間與重映射
rosrun設定命名空間
設定命名空間示範
文法: rosrun 包名 節點名 __ns:=新名稱
rosrun turtlesim turtlesim_node __ns:=/xxx
rosrun turtlesim turtlesim_node __ns:=/yyy
兩個節點都可以正常運作
運作結果
rosnode list
檢視節點資訊,顯示結果:
/xxx/turtlesim
/yyy/turtlesim
.rosrun名稱重映射
為節點起别名
文法: rosrun 包名 節點名 __name:=新名稱
rosrun turtlesim turtlesim_node __name:=t1 | rosrun turtlesim turtlesim_node /turtlesim:=t1(不适用于python)
rosrun turtlesim turtlesim_node __name:=t2 | rosrun turtlesim turtlesim_node /turtlesim:=t2(不适用于python)
兩個節點都可以運作
運作結果
rosnode list
檢視節點資訊,顯示結果:
/t1
/t2
rosrun命名空間與名稱重映射疊加
設定命名空間同時名稱重映射
文法: rosrun 包名 節點名 __ns:=新名稱 __name:=新名稱
rosrun turtlesim turtlesim_node __ns:=/xxx __name:=tn
運作結果
rosnode list
檢視節點資訊,顯示結果:
/xxx/tn
使用環境變量也可以設定命名空間,啟動節點前在終端鍵入如下指令:
export ROS_NAMESPACE=xxxx
launch檔案設定命名空間與重映射
介紹 launch 檔案的使用文法時,在 node 标簽中有兩個屬性: name 和 ns,二者分别是用于實作名稱重映射與命名空間設定的。使用launch檔案設定命名空間與名稱重映射也比較簡單。
1.launch檔案
<!-- 需要啟動多個烏龜GUI 節點 -->
<launch>
<node pkg="turtlesim" type="turtlesim_node" name="t1" />
<!-- 名稱重映射 -->
<node pkg="turtlesim" type="turtlesim_node" name="t2" />
<!-- 命名空間 -->
<node pkg="turtlesim" type="turtlesim_node" name="t1" ns="hello"/>
<!-- 命名空間+名稱重映射 -->
<node pkg="turtlesim" type="turtlesim_node" name="t3" ns="hello"/>
</launch>
在 node 标簽中,name 屬性是必須的,ns 可選。
2.運作
rosnode list
檢視節點資訊,顯示結果:
/hello/t1
/hello/t3
/rosout
/t1
/t2
編碼設定命名空間與重映射
如果自定義節點實作,那麼可以更靈活的設定命名空間與重映射實作。
C++ 實作:重映射
名稱别名設定
核心代碼:
ros::init(argc,argv,"zhangsan",ros::init_options::AnonymousName);
執行
會在名稱後面添加時間戳。
C++ 實作:命名空間
命名空間設定
核心代碼
std::map<std::string, std::string> map;
map["__ns"] = "xxxx";
ros::init(map,"wangqiang");
執行
節點名稱設定了命名空間。
Python 實作:重映射
名稱别名設定
核心代碼:
rospy.init_node("lisi",anonymous=True)
執行
會在節點名稱字尾時間戳。
ROS話題名稱設定
在ROS中節點名稱可能出現重名的情況,同理話題名稱也可能重名。
在ROS中節點名稱可能出現重名的情況,同理話題名稱也可能重名。
在 ROS 中節點終端,不同的節點之間通信都依賴于話題,話題名稱也可能出現重複的情況,這種情況下,系統雖然不會抛出異常,但是可能導緻訂閱的消息非預期的,進而導緻節點運作異常。這種情況下需要将兩個節點的話題名稱由相同修改為不同。
又或者,兩個節點是可以通信的,兩個節點之間使用了相同的消息類型,但是由于,話題名稱不同,導緻通信失敗。這種情況下需要将兩個節點的話題名稱由不同修改為相同。
在實際應用中,按照邏輯,有些時候可能需要将相同的話題名稱設定為不同,也有可能将不同的話題名設定為相同。在ROS中給出的解決政策與節點名稱重命類似,也是使用名稱重映射或為名稱添加字首。根據字首不同,有全局、相對、和私有三種類型之分。
- 全局(參數名稱直接參考ROS系統,與節點命名空間平級)
- 相對(參數名稱參考的是節點的命名空間,與節點名稱平級)
- 私有(參數名稱參考節點名稱,是節點名稱的子級)
名稱重映射是為名稱起别名,為名稱添加字首,該實作比節點重名更複雜些,不單是使用命名空間作為字首、還可以使用節點名稱最為字首。兩種政策的實作途徑有多種:
- rosrun 指令
- launch 檔案
- 編碼實作
本節将對三者的使用逐一示範,三者要實作的需求類似。
案例
在ROS中提供了一個比較好用的鍵盤控制功能包: ros-noetic-teleop-twist-keyboard,該功能包,可以控制機器人的運動,作用類似于烏龜的鍵盤控制節點,可以使用 sudo apt install ros-noetic-teleop-twist-keyboard 來安裝該功能包,然後執行: rosrun teleop_twist_keyboard teleop_twist_keyboard.py,在啟動烏龜顯示節點,不過此時前者不能控制烏龜運動,因為,二者使用的話題名稱不同,前者使用的是
cmd_vel
話題,後者使用的是
/turtle1/cmd_vel
話題。需要将話題名稱修改為一緻,才能使用,如何實作?
rosrun設定話題重映射
rosrun名稱重映射文法: rorun 包名 節點名 話題名:=新話題名稱
實作teleop_twist_keyboard與烏龜顯示節點通信方案由兩種:
方案1
将 teleop_twist_keyboard 節點的話題設定為
/turtle1/cmd_vel
啟動鍵盤控制節點:
rosrun teleop_twist_keyboard teleop_twist_keyboard.py /cmd_vel:=/turtle1/cmd_vel
啟動烏龜顯示節點:
rosrun turtlesim turtlesim_node
二者可以實作正常通信
方案2
将烏龜顯示節點的話題設定為
/cmd_vel
啟動鍵盤控制節點:
rosrun teleop_twist_keyboard teleop_twist_keyboard.py
啟動烏龜顯示節點:
rosrun turtlesim turtlesim_node /turtle1/cmd_vel:=/cmd_vel
二者可以實作正常通信
launch檔案設定話題重映射(remap)
launch 檔案設定話題重映射文法:
<node pkg="xxx" type="xxx" name="xxx">
<remap from="原話題" to="新話題" />
</node>
實作teleop_twist_keyboard與烏龜顯示節點通信方案由兩種:
方案1
将 teleop_twist_keyboard 節點的話題設定為
/turtle1/cmd_vel
<launch>
<node pkg="turtlesim" type="turtlesim_node" name="t1" />
<node pkg="teleop_twist_keyboard" type="teleop_twist_keyboard.py" name="key">
<remap from="/cmd_vel" to="/turtle1/cmd_vel" />
</node>
</launch>
方案2
将烏龜顯示節點的話題設定為
/cmd_vel
<launch>
<node pkg="turtlesim" type="turtlesim_node" name="t1">
<remap from="/turtle1/cmd_vel" to="/cmd_vel" />
</node>
<node pkg="teleop_twist_keyboard" type="teleop_twist_keyboard.py" name="key" />
</launch>
編碼設定話題名稱
話題的名稱與節點的命名空間、節點的名稱是有一定關系的,話題名稱大緻可以分為三種類型:
- 全局(話題參考ROS系統,與節點命名空間平級)
- 相對(話題參考的是節點的命名空間,與節點名稱平級)
- 私有(話題參考節點名稱,是節點名稱的子級)
結合編碼示範具體關系。
全局話題名稱
話題名稱需要以 / 開頭(也可以設定自己的命名空間),這種情況下和節點的命名空間以及名稱沒有關系.
實作:
int main(int argc, char *argv[])
{
ros::init(argc,argv,"hello_rename");
ros::NodeHandle nh;
//核心:設定不同類型的話題
//1.全局 ---- 話題名稱需要以 / 開頭(也可以設定自己的命名空間),這種情況下和節點的命名空間以及名稱沒有關系
ros::Publisher pub = nh.advertise<std_msgs::String>("/my/chatter",100);
/* code */
while (ros::ok())
{
/* code */
}
return 0;
}
執行:
相對話題名稱
相對 —沒有 / 開頭
int main(int argc, char *argv[])
{
ros::init(argc,argv,"hello_rename");
ros::NodeHandle nh;
//核心:設定不同類型的話題
//2.相對 ---沒有 / 開頭
ros::Publisher pub = nh.advertise<std_msgs::String>("my2/chatter",100);
/* code */
while (ros::ok())
{
/* code */
}
return 0;
}
執行:
私有話題名稱
私有 ----需要建立 NodeHandle nh(“~”)
注意如果私有的nh建立的話題以 / 開頭,那麼生成的話題是全局的而不是私有的
全局話題優先級更高
int main(int argc, char *argv[])
{
ros::init(argc,argv,"hello_rename");
ros::NodeHandle nh;
//核心:設定不同類型的話題
//3.私有 ----需要建立 NodeHandle nh("~")
ros::NodeHandle nh("~");
ros::Publisher pub = nh.advertise<std_msgs::String>("my2/chatter",100);
//注意如果私有的nh建立的話題以 / 開頭,那麼生成的話題是全局的而不是私有的
//全局話題優先級更高
while (ros::ok())
{
/* code */
}
return 0;
}
執行:
C++ 實作
示範準備:
1.初始化節點設定一個節點名稱
ros::init(argc,argv,"hello")
2.設定不同類型的話題
3.啟動節點時,傳遞一個 __ns:= xxx
4.節點啟動後,使用 rostopic 檢視話題資訊
全局名稱
**格式:**以
/
開頭的名稱,和節點名稱無關
比如:/xxx/yyy/zzz
示例1:
ros::Publisher pub = nh.advertise<std_msgs::String>("/chatter",1000);
結果1:
/chatter
示例2:
ros::Publisher pub = nh.advertise<std_msgs::String>("/chatter/money",1000);
結果2:
/chatter/money
相對名稱
**格式:**非
/
開頭的名稱,參考命名空間(與節點名稱平級)來确定話題名稱
示例1:
ros::Publisher pub = nh.advertise<std_msgs::String>("chatter",1000);
結果1:
xxx/chatter
示例2:
ros::Publisher pub = nh.advertise<std_msgs::String>("chatter/money",1000);
結果2:
xxx/chatter/money
私有名稱
**格式:**以
~
開頭的名稱
示例1:
ros::NodeHandle nh("~");
ros::Publisher pub = nh.advertise<std_msgs::String>("chatter",1000);
結果1:
/xxx/hello/chatter
示例2:
ros::NodeHandle nh("~");
ros::Publisher pub = nh.advertise<std_msgs::String>("chatter/money",1000);
結果2:
/xxx/hello/chatter/money
PS:當使用
~
,而話題名稱有時
/
開頭時,那麼話題名稱是絕對的
示例3:
ros::NodeHandle nh("~");
ros::Publisher pub = nh.advertise<std_msgs::String>("/chatter/money",1000);
結果3:
/chatter/money
Python 實作
示範準備:
1.初始化節點設定一個節點名稱
rospy.init_node("hello")
2.設定不同類型的話題
3.啟動節點時,傳遞一個 __ns:= xxx
4.節點啟動後,使用 rostopic 檢視話題資訊
全局名稱
**格式:**以
/
開頭的名稱,和節點名稱無關
示例1:
pub = rospy.Publisher("/chatter",String,queue_size=1000)
結果1:
/chatter
示例2:
pub = rospy.Publisher("/chatter/money",String,queue_size=1000)
結果2:
/chatter/money
相對名稱
**格式:**非
/
開頭的名稱,參考命名空間(與節點名稱平級)來确定話題名稱
示例1:
pub = rospy.Publisher("chatter",String,queue_size=1000)
結果1:
xxx/chatter
示例2:
pub = rospy.Publisher("chatter/money",String,queue_size=1000)
結果2:
xxx/chatter/money
私有名稱
**格式:**以
~
開頭的名稱
示例1:
pub = rospy.Publisher("~chatter",String,queue_size=1000)
結果1:
/xxx/hello/chatter
示例2:
pub = rospy.Publisher("~chatter/money",String,queue_size=1000)
結果2:
/xxx/hello/chatter/money
ROS參數名稱設定
在ROS中節點名稱話題名稱可能出現重名的情況,同理參數名稱也可能重名。
當參數名稱重名時,那麼就會産生覆寫,如何避免這種情況?
關于參數重名的處理,沒有重映射實作,為了盡量的避免參數重名,都是使用為參數名添加字首的方式,實作類似于話題名稱,有全局、相對、和私有三種類型之分。
- 全局(參數名稱直接參考ROS系統,與節點命名空間平級)
- 相對(參數名稱參考的是節點的命名空間,與節點名稱平級)
- 私有(參數名稱參考節點名稱,是節點名稱的子級)
設定參數的方式也有三種:
- rosrun 指令
- launch 檔案
- 編碼實作
三種設定方式前面都已經有所涉及,但是之前沒有涉及命名問題,本節将對三者命名的設定逐一示範。
案例
啟動節點時,為參數伺服器添加參數(需要注意參數名稱設定)。
rosrun設定參數
rosrun 在啟動節點時,也可以設定參數:
文法: rosrun 包名 節點名稱 _參數名:=參數值
設定參數
啟動烏龜顯示節點,并設定參數 A = 100
rosrun turtlesim turtlesim_node _A:=100
運作
rosparam list
檢視節點資訊,顯示結果:
/turtlesim/A
/turtlesim/background_b
/turtlesim/background_g
/turtlesim/background_r
結果顯示,參數A字首節點名稱,也就是說rosrun執行設定參數參數名使用的是私有模式
launch檔案設定參數
通過 launch 檔案設定參數的方式前面已經介紹過了,可以在 node 标簽外,或 node 标簽中通過 param 或 rosparam 來設定參數。在 node 标簽外設定的參數是全局性質的,參考的是 / ,在 node 标簽中設定的參數是私有性質的,參考的是 /命名空間/節點名稱。
設定參數
以 param 标簽為例,設定參數
<launch>
<!----全局------>
<param name="p1" value="100" />
<node pkg="turtlesim" type="turtlesim_node" name="t1">
<!----私有------>
<param name="p2" value="100" />
</node>
</launch>
運作
rosparam list
檢視節點資訊,顯示結果:
/p1
/t1/p1
運作結果與預期一緻。
編碼設定參數
編碼的方式可以更友善的設定:全局、相對與私有參數。
C++實作
在 C++ 中,可以使用 ros::param 或者 ros::NodeHandle 來設定參數。
ros::param設定參數
設定參數調用API是ros::param::set,該函數中,參數1傳入參數名稱,參數2是傳入參數值,參數1中參數名稱設定時,如果以 / 開頭,那麼就是全局參數,如果以 ~ 開頭,那麼就是私有參數,既不以 / 也不以 ~ 開頭,那麼就是相對參數。代碼示例:
ros::param::set("/set_A",100); //全局,和命名空間以及節點名稱無關
ros::param::set("set_B",100); //相對,參考命名空間
ros::param::set("~set_C",100); //私有,參考命名空間與節點名稱
運作時,假設設定的 namespace 為 xxx,節點名稱為 yyy,使用 rosparam list 檢視:
/set_A
/xxx/set_B
/xxx/yyy/set_C
ros::NodeHandle設定參數
設定參數時,首先需要建立 NodeHandle 對象,然後調用該對象的 setParam 函數,該函數參數1為參數名,參數2為要設定的參數值,如果參數名以 / 開頭,那麼就是全局參數,如果參數名不以 / 開頭,那麼,該參數是相對參數還是私有參數與NodeHandle 對象有關,如果NodeHandle 對象建立時如果是調用的預設的無參構造,那麼該參數是相對參數,如果NodeHandle 對象建立時是使用:
ros::NodeHandle nh(“~”),那麼該參數就是私有參數。代碼示例:
ros::NodeHandle nh;
nh.setParam("/nh_A",100); //全局,和命名空間以及節點名稱無關
nh.setParam("nh_B",100); //相對,參考命名空間
ros::NodeHandle nh_private("~");
nh_private.setParam("nh_C",100);//私有,參考命名空間與節點名稱
運作時,假設設定的 namespace 為 xxx,節點名稱為 yyy,使用 rosparam list 檢視:
/nh_A
/xxx/nh_B
/xxx/yyy/nh_C
python實作
python 中關于參數設定的文法實作比 C++ 簡潔一些,調用的API時 rospy.set_param,該函數中,參數1傳入參數名稱,參數2是傳入參數值,參數1中參數名稱設定時,如果以 / 開頭,那麼就是全局參數,如果以 ~ 開頭,那麼就是私有參數,既不以 / 也不以 ~ 開頭,那麼就是相對參數。代碼示例:
rospy.set_param("/py_A",100) #全局,和命名空間以及節點名稱無關
rospy.set_param("py_B",100) #相對,參考命名空間
rospy.set_param("~py_C",100) #私有,參考命名空間與節點名稱
運作時,假設設定的 namespace 為 xxx,節點名稱為 yyy,使用 rosparam list 檢視:
/py_A
/xxx/py_B
/xxx/yyy/py_C
0); //全局,和命名空間以及節點名稱無關
nh.setParam(“nh_B”,100); //相對,參考命名空間
ros::NodeHandle nh_private(“~”);
nh_private.setParam(“nh_C”,100);//私有,參考命名空間與節點名稱
運作時,假設設定的 namespace 為 xxx,節點名稱為 yyy,使用 rosparam list 檢視:
/nh_A
/xxx/nh_B
/xxx/yyy/nh_C
### python實作
python 中關于參數設定的文法實作比 C++ 簡潔一些,調用的API時 rospy.set_param,該函數中,參數1傳入參數名稱,參數2是傳入參數值,參數1中參數名稱設定時,如果以 / 開頭,那麼就是全局參數,如果以 ~ 開頭,那麼就是私有參數,既不以 / 也不以 ~ 開頭,那麼就是相對參數。代碼示例:
```python
rospy.set_param("/py_A",100) #全局,和命名空間以及節點名稱無關
rospy.set_param("py_B",100) #相對,參考命名空間
rospy.set_param("~py_C",100) #私有,參考命名空間與節點名稱
運作時,假設設定的 namespace 為 xxx,節點名稱為 yyy,使用 rosparam list 檢視:
/py_A
/xxx/py_B
/xxx/yyy/py_C