ROS

ROS系列文整理

由於 Robot Operating System(ROS) 的中文資源還幾乎不存在(2012年),決定先撒下一點麵包屑,之後有寫出新的也會依序整理到這上面。

我覺得機器人在能源不出現問題之前,是必然崛起的一個領域。機器人的存在可以幫助人類變得更好,被解放的生產力能夠聚焦到更為重要的議題上 – 永續發展,公平正義,世界均富等等,而要在機器人產業做出能推動世界進步的成果,我認為 ROS 是一個很重要的工具。

現在(2015年)已經多了不少中文資源,大家可以參考 ROS網站中文版。另外,最近同實驗室的同學黃昭霖也開始寫一些 他自己的筆記、有一位朋友林信男最近也開始寫 ROS on Jetson的學習筆記、他甚至還幫忙整理了各種 ROS 中文資源列表 XD 如果你需要問問題,去 ROS Answers絕對是不二選擇(因為有很多ROS package的開發者會到上面回答跟自己package相關的問題),或者可以去ROS.Taipei這個中文社群 或 另一個中文社群 逛逛。

對學習 ROS 有興趣的朋友來說,能有多一些資源參考總是好的。希望之後有心做 ROS 的人學習愉快 : D

如果你/妳覺得這些文章很有幫助,很歡迎你/妳給我一點小小的鼓勵!任何一點點對我來說都是很棒的鼓勵,希望可以分享更多自己的所學給大家。請大家千萬不要有一點點勉強喔!我最不喜歡強迫別人了。 附上我的 Paypal.me 連結:https://www.paypal.me/pojenlai


ROS tutorials 系列 (Beginner Level)

我的 tutorial 不詳細 go through 整個 tutorial 原文,而是就我認為重要之處詳加說明,而且有些東西原文寫得很清楚就不必重複寫了。換句話說,我仍預期你看下面這系列文章時要搭配原文看,才會比較完整。

0. 什麼是 ROS ? 要怎麼使用 ROS ? (玩ROS前必看!我盡量寫得淺顯,不是工程師應該也看得懂XD)

1. 淺談 ROS file system

2. 新增 ROS package

3. 建立 ROS packages

4. 了解 ROS Node

5. 了解 ROS Topics

6. 了解 ROS Service 跟 Parameters

7. 使用 rqt_console 跟 roslaunch

8. 使用 rosed 來編輯檔案

9. 建立自己的 msg 檔或 srv 檔

10.撰寫一個 publisher 跟 subscriber (上手 ROS Topic )

11.撰寫一個 service 跟 client (上手 ROS Service )

12.使用 rosbag 記錄和播放資料

13.使用 roswtf 來幫忙 debug

14.探索 ROS wiki

15.下一步是什麼?

———————————————–

Object Recognition Kitchen 系列

使用 Object Recogniton Kitchen 的 Linemod 演算法辨識物體

Object Recognition Kitchen 透明物體辨識(演算法概念)

ecto 簡介 (1) – cell 與 plasm

ecto 簡介 (2) – tendrils 與 scheduler

———————————————–

LSD SLAM 系列

深入學習 LSD-SLAM – 1

深入學習 LSD-SLAM – 2

深入學習 LSD-SLAM – 3

深入學習 LSD-SLAM – 4

深入學習 LSD-SLAM 番外篇 – RDS X RTAB-Map

深入學習 LSD-SLAM – 5

———————————————–

ROS觀念文

用 DDS 開發 ROS 2.0

簡介CRAM(Cognitive Robot Abstract Machine)

簡介 Knowrob (機器人知識處理的工具)

比較 Topic, Service 跟 Actionlib

ROS Navigation stack 簡介

ROS SMACH 簡介

———————————————–

ROS實作細節文

如何使用 Google Cartographer SLAM 演算法來建地圖

Caffe & GoogLeNet,如何幫助機器人更好地辨識物體

使用 Gazebo 模擬器控制機器人建立 2D 地圖

如何用 ROS Topic 控制機器人移動

使用 ROS 與 Gazebo 模擬一個自動避障機器人

改 launch file 中的參數值

launch file 中的條件用法

安裝 household object database

收到彩色影像,發布灰階影像 topic 的方法

接收來自 ROS Topic 的影像並偵測畫面中的動作

———————————————–

ROS雜感

PR2 開箱文

ROS Kong 2014 照片集

ROS in DARPA Robotics Challenge!

———————————————–

補些關鍵字

ROS(Robot operating system), 機器人作業系統, 教學文章, 範例, 說明

最後更新:2017/09/09

Advertisements
Practical

超讚的學科大地圖

今天 Youtube 突然推薦我一個看起來超讚的影片 – The Map of Mathematics:

裡面把各種數學的定位都給出來了,要是以前學數學就有這個大地圖該有多好,就可以在學習微積分、線性代數、微分方程、機率與統計、複變等這些科目時就有完整的概念,也更有機會把學習過的東西都串起來並真正應用在實際的問題上。

順便附上 Computer Science 的大地圖:

祝看到的各位都能運用數學來解決實際上遇到的問題,不斷體會到數學的美好!

AI/ML

XGBoost演算法理解

這次因緣際會地學習了一點 XGBoost 的演算法概念,在看了許多部落格文章和網路上的資源後,覺得還是原作者講解的比較清楚且完整,覺得可以在這邊點出來,幫助更多人學懂這個好用的算法。

建議可以先專注地聽到 37 分鐘的地方,你大概就可以了解整個 XGBoost 的演算法精神了!很值得的半小時!

Robotics, ROS

TB專欄 – Object Recognition Kitchen 透明物體辨識(演算法概念篇)

前言

這次的文章想要討論一個有趣的題目 – 透明物體辨識,這次的介紹先把題目限定在找出透明物體的位置,並把透明物體的輪廓找出來。

演算法功能簡介

我們的演算法目的是要找出影像中的透明物體,並把輪廓圈出來,就像下面這張圖一樣。

glasssegmentation

其實如果要更精確,應該把想要辨識哪些透明物體、在那些場景、辨識成功率希望有多高、如何定義辨識成功等都說清楚,不過這邊想先帶給大家一個初步的概念,就不討論得太過瑣碎。

演算法概念

為了達成這個功能,我們勢必要先收到彩色影像,所以肯定需要一台相機。不過下一個問題是,我們只需要用彩色影像資訊就能夠抓到透明物體的輪廓嗎? 還是我們需要其他的資訊 (也就是要不要考慮使用其他的 sensor) ?

要回答上面的問題,首先要來想想這個問題會什麼會困難。直覺上應該很容易發現,透明物體的顏色會隨著背景顏色不同而變,所以一般用顏色、紋理特徵來辨識的演算法都不太適用;此外,因為透明物體的邊界常常是模糊不清的,所以跟旁邊的物體很容易混在一起,造成輪廓抓錯。所以核心的問題就是,除了顏色、邊緣這些常用的特徵,我們還可以用什麼資訊來抓透明物體? 有一篇論文提出一個滿有趣的做法,他是使用 Kinect 的深度資訊,而且利用透明物體在 Kinect 的深度圖裡面會有破洞的特性,當成一個辨識的起點:

這個概念主要來自這篇論文 – Recognition and Pose Estimation of Rigid Transparent Objects with a Kinect Sensor,他提出的做法是,利用深度圖裡面有大塊破洞的位置,猜測這塊位置是透明物體,接著再根據這個線索找出透明物體的輪廓。

所以下一個問題就是,怎麼根據這個粗略的位置,把透明物體的輪廓切出來,論文中使用的方法是 Grabcut 演算法,這個演算法需要先有一個初始的前景跟背景線段,然後根據這個線段區分出前景跟背景,例如在下圖中,紅色線段是前景、藍色線段是背景,接著 Grabcut 演算法會根據顏色的近似程度還有距離關係,算出綠色框框內那些是前景、哪些是背景,然後把前景切出來。

1.PNG

切出來之後,目的就算是達成了!當然後續還有很多問題待討論(例如背景的複雜度、怎麼對深度影像中的破碎點做一些基本的處理等),不過今天先把範圍限縮在最上層的概念。

總結

演算法的應用真的是千奇百怪,但不管是簡單的演算法題目還是困難的演算法題目,解決的方法都是要看出問題的本質,然後再根據這個問題的特性去使用特定的步驟來解決。像這個範例就是巧妙地利用 Kinect 特性來處理透明物體的特徵很難抓的這個問題,畢竟會在 Kinect 的深度圖中固定呈現出破洞也算是一種特徵,至於更詳細的演算法流程或是程式碼的部分,就留待未來再介紹囉!

延伸閱讀

  1. 演算法介紹投影片
  2. ORK 的 transparent object recognition page
Robotics, ROS

TB 專欄 – 如何用 ROS Topic 控制機器人移動

前言

筆者今年在 COSCUP 2016 開了一個工作坊,教大家 ROS 的基礎以及 Gazebo 的基本概念,因為是第一次辦工作坊,還在學習怎麼拿捏時間跟內容,自覺並不是講得很完整,所以希望可以再提供一個簡單的範例,讓大家對 topic 傳輸要怎麼應用在控制機器人上會更有概念。

在工作坊講的內容 – Node, Topic and Service

這份投影片裡面主要就是帶大家實際操作建立 package、寫 ROS Node、寫 ROS Topic 和 Service 傳輸資料的步驟,讓大家熟悉怎麼撰寫 ROS 程式,並帶到一點點使用 Gazebo 的基本概念,不過比較可惜的是沒有一個範例讓大家實際體會怎麼裡利用投影片裡教的機制來控制機器人,所以補充下面的章節,讓大家可以透過 Topic 簡單地控制機器人移動。

如何使用 Turtle_sim 模擬一隻可以接收 geometry_msgs/Twist 型態

接下來,來講一下怎麼透過 Topic 傳輸機制來控制 Turtlesim 裡面的機器人。首先讓我們啟動 turtlesim 的程式,使用下列指令:

roscore
rosrun turtlesim turtlesim_node

啟動 Turtlesim 後,就會看到一個 GUI 介面,裡面有一隻可愛的烏龜。

turtle_sim_1

在 ROS 的世界裡,大家通常都會透過傳送命令到 /cmd_vel 這個 topic來控制機器人的移動。 我們可以先用 rostopic list 確認是不是已經有 /cmd_vel 這個 topic 存在於系統裡,理論上得到的結果如下:

ros@ros-K401UB:~$ rostopic list
/rosout
/rosout_agg
/turtle1/cmd_vel
/turtle1/color_sensor
/turtle1/pose

接下來就讓我們開始寫程式吧,首先建立一個 package 並開始寫後續的程式:

cd ~/catkin_ws/src
catkin_create_pkg coscup_follow_up rospy geometry_msgs
cd coscup_follow_up
vim src/circle_turtle.py

circle_turtle.py 的程式碼也很簡單,只是單純地送出一個 geometry_msgs/Twist 的 command,所以我們只要定期送出一個 command 就可以讓這隻可愛的烏龜移動了。


#!/usr/bin/env python
import rospy
from geometry_msgs.msg import Twist

def circle_walker():
 pub = rospy.Publisher('turtle1/cmd_vel', Twist, queue_size=10)
 rospy.init_node('cmd_publisher_node')
 rate = rospy.Rate(10) # 10hz

cmd = Twist()
 count = 0;

while not rospy.is_shutdown():
 cmd.linear.x = 1
 cmd.angular.z = 0.5

pub.publish(cmd)
 rate.sleep()

if __name__ == '__main__':
 try:
 circle_walker()
 except rospy.ROSInterruptException:
 pass

最後只要把程式跑起來,就可以看到結果了。

chmod +x src/circle_turtle.py
cd ../..
catkin_make
source devel/setup.bash
rosrun coscup_follow_up circle_turtle.py

turtle_sim_2

這邊要介紹一個簡單的機器人模型,也就是一般的兩輪機器人模型。在這種模型底下,其實你只能夠控制機器人的前後移動以及左右旋轉,所以程式中就只有指定 linear.x 跟 angular.z 的值 (因為就算其他值不給 0,也不會有任何反應)。

總結

這篇文章算是將 COSCUP 工作坊的內容再稍微延伸一下,讓大家比較有見樹又見林的感覺,不會寫了一堆 node ,卻都還沒有碰到機器人,雖然內容對於已經熟悉 ROS 的讀者不難,但希望透過這篇文章可以幫助更多想上手的讀者降低學習的難度。

ROS

TB 專欄 – 使用 Gazebo 模擬器控制機器人建立 2D 地圖

前言

ROS 很吸引人的一個地方在於,他跟模擬器 Gazebo 很好地結合在一起,讓使用者可以使用一台筆記型電腦就開始撰寫機器人程式,而且可以在筆電上看到程式執行的結果。模擬器裡的機器人不會撞壞、不會沒電,所以就算在程式設計過程中有錯誤,也不會有任何硬體成本的損失 (時間當然是會損失),而且很容易大量測試演算法,因為可以在模擬器中設置各種場景,而不需要一個很大的空間和昂貴的機器人。這篇文章會簡單介紹怎麼在 Gazebo 裡面啟動 PR2,並遙控他來建立一個 2D 的地圖。

啟動 Gazebo 跟 PR2

我的測試環境是 Ubuntu 14.04 + ROS Indigo,不過使用的指令都很 general,即便你使用其他版本,應該也不會有什麼問題。

首先我們確定已經安裝所需要的套件:

sudo apt-get install ros-indigo-gazebo-*

接下來在終端機輸入 gazebo 應該就可以成功啟動 Gazebo 模擬器。然後我們可以用現成的 package 啟動一個有 PR2 的空白模擬環境。

roslaunch pr2_gazebo pr2_empty_world.launch

Gazebo 裡面已經有很多現成的模型,選擇左邊的 Insert 頁籤,裡面就可以找到一些現成的模型,在這邊我先加入了 Cube 跟 Dumpster 的模型,加入後的結果如下圖:

1

對於機器人來說,最重要的第一個環節就是感知功能,他必須要可以感知環境中的資訊,才能做出相對應的動作。所以我們會先關心的就是 Gazebo 裡面的 PR2 機器人可不可以接收到環境中的資訊。

答案當然是可以的,我們可以透過 ROS 提供的 visualizer Rviz 來觀看 PR2 接收到的資訊,如果是已經熟悉 ROS 的讀者,想必對 Rviz 不陌生,只要開一個新的終端機,輸入 rviz 就可以啟動了。啟動之後,可以 Add 一個 Image 的顯示框,然後選擇現在可以看到的 topic,就可以看到 Gazebo 裡面的 PR2 看到的畫面。

rviz

啟動 gmapping package 建地圖

環境都已經啟動了,接下來就要開始建地圖啦! 這邊我們使用的工具是 gmapping ,首先我們在隨便一個路徑新增一個 launch 檔 –  vim pr2_build_map.launch 。

然後輸入這一串文字,儲存檔案後離開。

<launch>

  <!-- dynamic map generation -->
  <node name="gmapping_node" pkg="gmapping" type="slam_gmapping" respawn="false" >
    <remap to="base_scan" from="scan"/>
    <param name="odom_frame" value="odom_combined" />
  </node>

</launch>
然後可以用 roslaunch pr2_build_map.launch啟動。

啟動 gmapping 之後,我們還是透過 Rviz 來觀看地圖建立的狀態,這邊需要加入 Map 的顯示,然後 topic 選擇 /map 就好。地圖中的黑色線就是障礙物,淺灰色區域表示是可以走的無障礙物區域,深灰色則是未知區域。

rviz-2

讓我們近看一下 map 的 topic 要怎麼選:

rviz-3

但如果機器人不能移動的話,就只能顯示眼前看到的這一塊地圖。所以我們先使用簡單的遙控程式來控制 Gazebo 裡面的 PR2 移動。

這邊只要啟動 teleop_keyborad 就可以用鍵盤控制機器人移動:

roslaunch pr2_teleop teleop_keyboard.launch

WASD 四個按鍵分別代表前左後右四個方向的平移、QE兩個按鍵是原地旋轉,這邊要注意必須選到啟動 teleop_keyboard的視窗,按按鍵才有用。使用 teleop 來控制 PR2 走一走之後,就可以看得出我們已經使用 gmapping 建立了一個看起來有模有樣的地圖了。

2

如果你想要把地圖存起來,提供未來要做 Navigation 使用,或只是單純想留個紀念,可以用一個指令把地圖存起來 – rosrun map_server map_saver 。

 

總結

這篇文章跟大家簡單介紹怎麼使用 Gazebo 模擬器來模擬機器人,並利用他實際操作了一個小小的 SLAM 功能,也建出了自己的一張地圖。我們能利用 Gazebo 來做的事情當然遠不止於此,不過時間有限,我們下次見。

延伸閱讀

  1. gmapping 演算法原理簡介
  2. SDF 網站中文版
Observation

馬祖東引島 – 魚多多小吃店

這次來馬祖玩,有很多好笑的事情,不過這一篇要說的故事沒有很好笑、反而算是有點慚愧。

故事發生在東引島 downtown 的魚多多小吃店,這家店不是很起眼,離我們住的安逸旅店也只有10公尺,看起來只是一間普通的小吃店(因為招牌就只寫小吃店啊),所以我們也只是想進去吃個粗飽而已。

IMG_3839.JPG

進去之後,自然就是先看一下菜單(可惡…忘記拍菜單),果然…魚還滿多的,但因為實在有太多種菜了,我們實在不太知道怎麼點,而且又想多吃一點馬祖當地的海產,所以我們就請老闆幫我們配 3000 的合菜。

於是老闆就開始配了,他一開始說有一道叫作”夢幻魚”的很好吃,但是會比較貴,問我們要不要,這時我感覺在場至少有 5/7 的人都覺得他應該是想推銷我們貴的魚,但想說既然都來了,所以就點了下去。接著他又幫我們配了幾道海鮮跟熱炒類的食物,但是配到約莫有10道之後,他說這樣可能會到4000多,又讓我們有點小小的懷疑他是不是故意要配貴的 (因為一開始就已經說希望幫我們配3000的菜了,而且4000多平均起來一道要400多也好像有點不對勁),這時候大家的語氣就不是很友善 (也不到沒禮貌喇我覺得,但就是有想要把一些菜取消、趕快點到3000出頭,結束點菜活動的感覺)。總之後來經過一番抉擇,還是點了一桌3800的。

IMG_3835

但是,隨著菜一道一道的上來(上面這些並不是全部喔,有好幾道已經被收走了),我們發現菜都超大盤(原本想說可能是跟我們一般吃的熱炒一樣小盤、所以才覺得平均一盤400很貴)、老闆推薦的夢幻魚也真的超好吃。我們才開始發現,原來…心靈被汙染的是我們啊…我們很直覺地用都市人的眼光來看待其他人,以為老闆是要推銷我們。

後來跟老闆聊了一下,發現原來是夢幻魚那一條就要1000左右,而且可以感覺到他的出發點比較像是想推薦我們好吃的料理,雖然缺點是會比較貴,但也會有比較深刻的印象。他還拿天下雜誌的報導給我們看,想不到魚多多居然這麼猛,只好趕忙多叫老闆幾聲大大。

最後,慚愧的我,還是跟老闆拍了一張照,並且立下承諾說回來要寫一篇推薦文,希望老闆,可以從看到這篇文章去吃的客人身上間接感受到我們的誠意 XD

IMG_3838

至於晚上去山猴叔叔的店,本來老闆娘想要關店、不太想理我們,但聽到我們是安逸旅店的客人,立馬叫我們坐下,讓老闆出來跟我們聊的故事,又是另一段佳話了XDD

(噢然後山猴叔叔的兒子居然是SBL球員宋宇軒!! 還好我偶爾還會看緯來體育新聞,因為山猴叔叔先問我有沒有打籃球,我說有之後他就立馬問我知不知道宋宇軒哈哈)

Robotics, ROS

ecto框架簡介(1) – cell 與 plasm

前言

這次想要跟大家介紹 ROS ORK (Object Recognition Kitchen) 這個函式庫實作物體辨識 pipeline 的機制,其背後運用到的一個重要的函式庫叫做 ecto ,接下來會介紹 ecto 的基本觀念和用法。但因為我想把內容講解得比較詳細,所以不會只花一篇文章的篇幅就介紹完這個工具,這一篇會講到最基本的 cell 與 plasm,讓大家先有初步的認識,更進階的用法甚至是實例會在之後的文章介紹。

ecto 是什麼 & 為什麼要使用 ecto

可以把 ecto 想成一個框架,這個框架可以讓你很方便地把程式用 DAG (Directed Acyclic Graph) 的方式來實作,這樣實作的兩大好處在於模組化跟彈性。

DAG的一個範例:

DAG

首先談到模組化,在 ecto 的世界裡,你首先可以用 C++ 或 Python 寫出一個個的 cell ,這個 cell 就是執行一個功能的單位 (所以命名為 cell,細胞的意思),這種設計的方式讓你在撰寫 cell 的時候,比較不會把好多個功能硬寫在一起,增加了程式的可讀性、也讓後續的擴充彈性變強。

模組化所衍生出的好處就是彈性,因為你只要抽換某個模組,就可以改變整個程式的行為。

以 ORK 為例,在撰寫物體辨識的 pipeline 時,假設有三個步驟: (這邊只是為了方便理解舉例,不是真實情況)

  1. 讀取 Kinect 影像
  2. 使用 Linemod 演算法進行 template matching
  3. 將辨識結果輸出

那用 ecto 實作就會寫成三個 cells,然後再建立一個 ecto 的 plasm,plasm 其實就只是 graph,把寫好的三個cell相連接。 所以,如果我想要實作另一個物體辨識的演算法,我只要改寫第二個 cell 就好,當然有個前提是兩個演算法的 input 和 output 要一致,才不會影響到第一個和第三個 cell。

基本中的基本 – Cells & Plasm 的簡單用法

為了讓大家有見樹又見林的感覺,我們先看一下 ecto 大致上要怎麼用。最簡單的用法大概就是建立兩個 cell ,再用一個 plasm 將這兩個 cell 串成 graph 。

現在我們只要先知道要寫一個 cell 需要在裡面定義四個函式:

static void
declare_params(tendrils&amp;)
static void
declare_io(const tendrils&amp;, tendrils&amp;, tendrils&amp;)
void
configure(const tendrils&amp;, const tendrils&amp;, const tendrils&amp;)
int
process(const tendrils&amp;, const tendrils&amp;)

這四個函式顧名思義就是要定義這個 cell 有哪些參數可以設定、輸入跟輸出是什麼、怎麼設定參數以及 cell 運作時的功能,很符合直覺上的需求。

那假設我們已經定義了兩個 cell – MyAwesomeCell1 跟 MyAwesomeCell2,ru, 剩下的就是建立一個 Plasm 來串接這兩個 cell 並執行,他的程式碼會像這樣:

#!/usr/bin/env python
import ecto
import my_awesome_cpp_ecto_module
import my_awesome_python_ecto_module

# create a plasm
plasm = ecto.Plasm()

# create some cells
cell1 = my_awesome_cpp_ecto_module.MyAwesomeCell1(param1=whatever1)
cell2 = my_awesome_python_ecto_module.MyAwesomeCell2(param2=whatever2)

# connect those cells in the plasm
plasm.connect(cell1['output'] &gt;&gt; cell2['input'])

# execute the graph
plasm.execute(niter=2)

重點其實只有 Plasm 的初始化、串接 cell 成 graph 與執行這三個部分:

# create a plasm
plasm = ecto.Plasm()

# connect those cells in the plasm
plasm.connect(cell1['output'] &gt;&gt; cell2['input'])

# execute the graph
plasm.execute(niter=2)

Cell 的機制詳解

從上面的例子,大家應該可以明顯的看出,plasm 因為只是要串連寫好的 cell ,所以設定相對單純(目前我們先不討論 scheduling 等複雜的狀況),但 cell 就不太一樣,上面只提到需要寫四個函式,卻沒有實例讓大家了解怎麼實作,接下來就會介紹比較實際的例子,讓大家了解 cell 要怎麼寫。

首先我們看個簡單的例子,這個 class 是一個 Printer ,我們在產生這個 Printer 的 instance 時,可以設定裡面的兩個 data member – prefix_ 和 suffix_ (或可以理解成參數)

struct Printer
{
  Printer(const std::string&amp; prefix, const std::string&amp; suffix)
      :
        prefix_(prefix),
        suffix_(suffix)
  {
  }
  void
  operator()(std::ostream&amp; out, const std::string&amp; message)
  {
    out &lt;&lt; prefix_ &lt;&lt; message &lt;&lt; suffix_;
  }
  std::string prefix_, suffix_;
};

假設要改寫成 ecto 的 cell,首先我們來宣告參數,透過 declare_params 這個函式可以做到,在這個例子裡,只是先單純宣告有兩個 params,提供這兩個參數的說明(這個說明可以用來自動生成文件,不過我們先忽略),以及預設值。

大家可能會疑惑的地方是,params.declare 這個用法怎麼突然就跑出來了。這是因為在 ecto 裡面,cell 之間的溝通是透過 tendrils 這個類別來處理,但這一篇先不提到 tendrils 的細節,所以才會有點混亂,不過如果去看 tendrils 的 API,就會清楚這中間是怎麼一回事。

static void declare_params(tendrils&amp; params)
{
  params.declare&lt;std::string&gt;(&quot;prefix&quot;, &quot;A string to prefix printing with.&quot;, &quot;start&gt;&gt; &quot;);
  params.declare&lt;std::string&gt;(&quot;suffix&quot;, &quot;A string to append printing with.&quot;, &quot; &lt;&lt;stop\n&quot;);
}

 

接著我們來定義 IO 的介面,透過 declare_io 來做,因為這個 cell 只需要接收需要印出的 message ,所以只需要宣告一個 input ,不需要宣告 output 。

static void
declare_io(const tendrils&amp; params, tendrils&amp; inputs, tendrils&amp; outputs)
{
  inputs.declare&lt;std::string&gt;(&quot;message&quot;, &quot;The message to print.&quot;);
}

目前我們已經指定了對外的兩個重點 – 有哪些參數以及 IO 介面。接著該考慮內部使用的設定了,所以第一步是將 declare_params 裡面宣告的參數 (此例中是 prefix 跟 suffix) 跟類別裡面的 data member (此例中是 prefix_ 跟 suffix_) 連接。

void
configure(const tendrils&amp; params, const tendrils&amp; inputs, const tendrils&amp; outputs)
{
  params[&quot;prefix&quot;] &gt;&gt; prefix_;
  params[&quot;suffix&quot;] &gt;&gt; suffix_;
}

最後的重點就是,實作這個 cell 的功能,我們要把實際做的事情寫在 process 這個函式裡面。

int
process(const tendrils&amp; inputs, const tendrils&amp; outputs)
    {
      std::cout &lt;&lt; prefix_ &lt;&lt; inputs.get&lt;std::string&gt;(&quot;message&quot;) &lt;&lt; suffix_;
      return ecto::OK;
    }

所以如果把四個函式合起來看,就會像這樣:

#include &lt;ecto/ecto.hpp&gt;
#include &lt;ecto/registry.hpp&gt;
#include &lt;iostream&gt;
#include &lt;string&gt;;
using ecto::tendrils;
namespace overview
{
  struct Printer01
  {
    static void
    declare_params(tendrils&amp;amp;amp;amp; params)
    {
      params.declare&lt;std::string&gt;(&quot;prefix&quot;, &quot;A string to prefix printing with.&quot;, &quot;start&gt;&gt; &quot;);
      params.declare&lt;std::string&gt;(&quot;suffix&quot;, &quot;A string to append printing with.&quot;, &quot; &lt;&lt;stop\n&quot;);
    }
    static void
    declare_io(const tendrils&amp; params, tendrils&amp; inputs, tendrils&amp; outputs)
    {
      inputs.declare&lt;std::string&gt;(&quot;message&quot;, &quot;The message to print.&quot;);
    }
    void
    configure(const tendrils&amp; params, const tendrils&amp; inputs, const tendrils&amp; outputs)
    {
      params[&quot;prefix&quot;] &gt;&gt; prefix_;
      params[&quot;suffix&quot;] &gt;&gt; suffix_;
    }
    int
    process(const tendrils&amp;amp;amp;amp; inputs, const tendrils&amp;amp;amp;amp; outputs)
    {
      std::cout &lt;&lt; prefix_ &lt;&lt; inputs.get&lt;std::string&gt;(&quot;message&quot;) &lt;&lt; suffix_;
      return ecto::OK;
    }
    std::string prefix_, suffix_;
  };
}
ECTO_CELL(ecto_overview, overview::Printer01, &quot;Printer01&quot;,
          &quot;A simple stdout printer with prefix and suffix parameters.&quot;);

雖然整個類別被寫成很長,但其實只要熟悉 cell 的基本用法,就不會被這一堆程式碼嚇到。最後想跟大加補充說明一下,上面提到的 cell 寫法有個不直覺的地方是,沒有明顯的繼承關係,所以會覺得不符合我們的 cell 應該要繼承一個 base 的 cell class 的直覺。其實這邊是被 ECTO_CELL 這個 MACRO 給處理掉了,所以才會看起來只有宣告幾個函式就寫完一個 cell 的感覺。

總結

這篇文章簡介了 ecto 的 cell 和 plasm,下一篇將會介紹 tendril 跟 scheduler 的機制,幫助大家更加理解 ecto ,並在未來能運用這個框架來建立自己的應用。

延伸閱讀

  1. When to use DAG (Directed Acyclic Graph) in programming?
  2. ecto 官方網頁的 plasm 介紹
  3. ecto 官方網頁的 cell 詳細介紹