ISAAC Robot Engine就nvidia釋出的一個機器人平台,使用了nvidia硬體加速。
Codelets其實有點像ROS中的nodelet.
Codelets提供三個主要的重載函數:start, tick, stop。當一個node啟動的時候,所有attached codelet都會被調用。例如,start函數中進行一些資源的配置設定和初始化。tick可以配置成周期調用或者接收到消息時調用。我們很多的代碼實作都在tick中實作。
當一個node 停止的時候,stop函數被調用。應該釋放前面申請的資源。不要使用構造函數和析構函數。因為你沒有權限獲得Robot Engine 功能比如構造函數中的configuration.
每一個自定義的codelet需要注冊到Isaac Robot Engine.
ISAAC_ALICE_REGISTER_CODELET宏就是用于注冊的。
下面添加一個Ping的功能實作。建立一個Ping.cpp.
#include "Ping.hpp"
void Ping::start() {}
void Ping::tick() {}
void Ping::stop() {}
codelet可以有多種方式實作tick,這個例子使用周期調用。通過在start中調用tickPeriodically函數。
void Ping::start() {
tickPeriodically();
}
為了驗證真的調用了,我們在tick函數中列印一些資訊;
void Ping::tick() {
LOG_INFO("ping");
}
就像編寫makefile一樣,我們需要把頭檔案和cpp檔案寫進BUILD中。
isaac_app(
...
)
isaac_cc_module(
name = "ping_components",
srcs = ["Ping.cpp"],
hdrs = ["Ping.hpp"],
)
Isaac子產品定義的是一個共享庫,可以被不同的應用程式調用。為了使用Ping codelet,我們需要在json檔案中建立一個node結點。
{
"name": "ping",
"graph": {
"nodes": [
{
"name": "ping",
"components": []
}
],
"edges": []
}
}
每一個node可以包含多個元件,元件可以定義功能。通過在components清單中添加一個新的項來添加Ping codelet到node。
{
"name": "ping",
"graph": {
"nodes": [
{
"name": "ping",
"components": [
{
"name": "ping",
"type": "Ping"
}
]
}
],
"edges": []
}
}
應用程式通常有叫邊(edges)的東西,它連接配接不同的結點,這是基于圖理論的,結點是graph,連接配接的邊叫edges.邊決定了消息傳送的方向。由于這個程式沒有其它的結點,是以edges為空就可以了。
如果你試着去運作這個程式,會報一個錯誤Could not load component ‘Ping’,這是因為所有的components必須添加到modules list中,需要在BUILD和JSON中同時添加。
load("//engine/build:isaac.bzl", "isaac_app", "isaac_cc_module")
isaac_app(
name = "ping",
modules = ["//packages/ping:ping_components"]
)
{
"name": "ping",
"modules": [
"ping:ping_components"
],
"graph": {
...
}
}
這裡說明一下,ping:ping_components代表的是先前定義的子產品子產品//package/ping:ping_components。
如果你現在去運作程式,會報一個錯誤Parameter ‘ping/ping/tick_period’ not found or wrong type.。這是由于我們沒有設定tick的周期。我們需要添加一個配置參數到JSON檔案中。
添加配置
大多數程式都需要不同的參數來控制程式的行為。Issaac 架構中是通過配置檔案來修改的。
在Json檔案中添加配置
{
"name": "ping",
"modules": [
"ping:ping_components"
],
"graph": {
...
},
"config": {
"ping" : {
"ping" : {
"tick_period" : "1Hz"
}
}
}
}
每一個配置參數有三個元素:node name, component name 和 parameter name.上面的代碼中,我們設定ping結點的ping 元件的tick_period參數。
現在我們可以運作程式,每隔1秒會列印一條資訊。
[email protected]:~/isaac/packages/ping$ bazel run ping
2019-03-24 17:09:39.726 DEBUG engine/alice/backend/[email protected]: Starting codelet 'ping/ping' ...
2019-03-24 17:09:39.726 DEBUG engine/alice/backend/[email protected]: Starting codelet 'ping/ping' DONE
2019-03-24 17:09:39.726 DEBUG engine/alice/backend/[email protected]: Starting job for codelet 'ping/ping'
2019-03-24 17:09:39.726 INFO packages/ping/[email protected]: ping
2019-03-24 17:09:40.727 INFO packages/ping/[email protected]: ping
2019-03-24 17:09:41.726 INFO packages/ping/[email protected]: ping
tick_period參數是isaac自動為我們建立的。但是我們也可以建立自定義的參數。通過下面的代碼我們添加一個自定義參數。
class Ping : public isaac::alice::Codelet {
public:
void start() override;
void tick() override;
void stop() override;
ISAAC_PARAM(std::string, message, "Hello World!");
};
ISAAC_PARAM宏,第一個參數是類型。可以是double,int bool std::string,第二個參數是參數名,第三個參數是預設值。The ISAAC_PARAM宏建立一個get_message的方法。
我們在tick中把他列印出來
void tick() {
LOG_INFO(get_message().c_str());
}
下一步,我們添加一個config到JSON檔案中。
{
"name": "ping",
"modules": [
"ping:ping_components"
],
"graph": {
...
},
"config": {
"ping" : {
"ping" : {
"message": "My own hello world!",
"tick_period" : "1Hz"
}
}
}
}
通過bazel run ping就可以運作這個程式了。可以預見,會每1秒列印一次my own hello world!.
發送消息
我們很多時候需要發送和接收消息。
發送消息使用ISAAC_PROTO_TX宏。
#pragma once
#include "engine/alice/alice.hpp"
#include "messages/messages.hpp"
class Ping : public isaac::alice::Codelet {
public:
...
ISAAC_PARAM(std::string, message, "Hello World!");
ISAAC_PROTO_TX(PingProto, ping);
};
ISAAC_ALICE_REGISTER_CODELET(Ping);
ISAAC_PROTO_TX有兩個參數,第一個參數是要發送的消息類型,這裡我們使用PingProto Message.這個和ros中的topic名類似。注意添加messages.hpp. 第二個參數指定channel的名字。
接下來,我們通過tick函數中添加發送消息。
ProtoPing message有一個叫message的string.是以我們可以使用setMessage方法把text寫入到proto.,然後釋出。
最後,更新這個結點(JSON檔案)來支援消息傳遞。為了使能消息發送和接收,添加一個components叫MessageLedger.
{
"name": "ping",
"graph": {
"nodes": [
{
"name": "ping",
"components": [
{
"name": "message_ledger",
"type": "isaac::alice::MessageLedger"
},
{
"name": "ping",
"type": "Ping"
}
]
}
],
"edges": []
},
"config": {
...
}
現在運作這個程式,什麼也不列印,因為我們沒有接收資訊的node.
接收資訊
下面寫一個codelet來接收資訊。建立一個叫pong的codelet. 寫一個Pong.hpp檔案。
#pragma once
#include "engine/alice/alice.hpp"
#include "messages/messages.hpp"
class Pong : public isaac::alice::Codelet {
public:
void start() override;
void tick() override;
// An incoming message channel on which we receive pings.
ISAAC_PROTO_RX(PingProto, trigger);
// Specifies how many times we print 'PONG' when we are triggered
ISAAC_PARAM(int, count, 3);
};
ISAAC_ALICE_REGISTER_CODELET(Pong);
Pong Codelet需要添加ping_components,添加到BUILD檔案中。
isaac_cc_module(
name = "ping_components",
srcs = [
"Ping.cpp",
"Pong.cpp"
],
hdrs = [
"Ping.hpp",
"Pong.hpp"
],
)
JSON檔案中也需要添加,建立第二個結點,把pong 添加到這個結點,連接配接Ping 和Pong.通過前面說的邊(edges)
{
"name": "ping",
"modules": [
"ping:ping_components"
],
"graph": {
"nodes": [
{
"name": "ping",
"components": [
{
"name": "message_ledger",
"type": "isaac::alice::MessageLedger"
},
{
"name": "ping",
"type": "Ping"
}
]
},
{
"name": "pong",
"components": [
{
"name": "message_ledger",
"type": "isaac::alice::MessageLedger"
},
{
"name": "pong",
"type": "Pong"
}
]
}
],
"edges": [
{
"source": "ping/ping/ping",
"target": "pong/pong/trigger"
}
]
},
"config": {
"ping" : {
"ping" : {
"message": "My own hello world!",
"tick_period" : "1Hz"
}
}
}
}
Edges連接配接RX 通道到TX 通道。一個發送通道可以有多個接收,一個接收能完也可以從多個傳送器接收。但是不鼓勵這樣用。
類似于參數,channels也有三個元素,node name,components name, channel name.source是發送通道的全名,target是接收通道的全名。
最後一步就是實作Pong.cpp,start函數中添加tickOnMessage,我們添加功能列印“PONG",
#include "Pong.hpp"
#include <cstdio>
void Pong::start() {
tickOnMessage(rx_trigger());
}
void Pong::tick() {
// Parse the message we received
auto proto = rx_trigger().getProto();
const std::string message = proto.getMessage();
// Print the desired number of 'PONG!' to the console
const int num_beeps = get_count();
std::printf("%s:", message.c_str());
for (int i = 0; i < num_beeps; i++) {
std::printf(" PONG!");
}
if (num_beeps > 0) {
std::printf("\n");
}
}
使用tickOnMessage 代替tickPeriodically
運作該程式,能看到列印"Pong".
通過網絡發送Message
如果Ping和Pong運作在不同的裝置上,通過網絡連接配接,那麼就需要使用TcpPublisher和TcpSubscriber 結點了。
{
"name": "ping",
...
"graph": {
"nodes": [
...
{
"name": "pub",
"components": [
{
"name": "message_ledger",
"type": "isaac::alice::MessageLedger"
},
{
"name": "tcp_publisher",
"type": "isaac::alice::TcpPublisher"
}
]
}
],
"edges": [
{
"source": "ping/ping/ping",
"target": "pub/tcp_publisher/tunnel"
}
]
},
"config": {
...
"pub": {
"tcp_publisher": {
"port": 5005
}
}
}
}
port參數指定網絡端口号。接收端需要加一個TcpSubscriber。
{
"name": "pong",
...
"graph": {
"nodes": [
...
{
"name": "sub",
"components": [
{
"name": "message_ledger",
"type": "isaac::alice::MessageLedger"
},
{
"name": "tcp_receiver",
"type": "isaac::alice::TcpSubscriber"
}
]
}
],
"edges": [
{
"source": "sub/tcp_receiver/tunnel",
"target": "pong/pong/trigger"
}
]
},
"config": {
...
"sub": {
"tcp_receiver": {
"port": 5005,
"reconnect_interval": 0.5,
"host": "127.0.0.1"
}
}
}
}
host參數為監聽的IP位址,也就是Ping運作的位址。由于示例都是在同一個電腦上運作,是以IP為127.0.0.1.
以上基本上是官方ISAAC 教程的翻譯。搞過ROS的人應該有一些體會。和ROS有些類似,很像Nodelet。這個Codelet是代碼片段。代碼片段不能單獨運作,需要在JSON檔案中配置graph edges以及參數。類比于ROS package ,BUILD檔案相當于CMakelist.txt, JSON檔案相當于Package.xml檔案。JSON檔案功能更多一些。都寫成代碼段,統一由Robot Engine管理,在效率上會高一些,消息發送直接傳遞指針就可以了。通過網絡發送的TcpPublisher和TcpSubscriber和Ros的topic釋出類似了。
相似歸相似,使用上還是有很大的不同的,剛剛接觸這個架構,很多還不了解。慢慢啃文檔學習吧。