天天看點

Isaac Codelets

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釋出類似了。

相似歸相似,使用上還是有很大的不同的,剛剛接觸這個架構,很多還不了解。慢慢啃文檔學習吧。

tx2