Criando aplicações para o ROS baseadas em Nodelets

Nodelets

Olá pessoal, hoje trago até vocês um pouco mais a respeito dos recursos presentes no ROS, nesse texto falaremos sobre uma opção de desenvolvimento ao uso dos conhecidos ros_nodes, esse recurso, chamado de Nodelet, é uma escolha bem comum para uso em aplicação que demandam uso intensivo da CPU como processamento de vídeo ou processamento de dados vindos de outros sensores. Nesse texto apresentaremos um pouco do conceito dos Nodelets e como criar uma aplicação exemplo utilizando mais esse recurso do ROS.

 

O que são Nodelets? Qual a vantagem em relação a um ROS node?

 

É de se esperar que um sistema operacional focado para construção de robôs como o ROS ofereçam recursos para casos em que a latência de comunicação entre os ros_nodes seja minimizada, apesar dele usar uma implementação eficiente de um protocolo baseado em UDP, muitas syscalls podem ser geradas entre um dado publicado que chega a um determinado subscriber, se o que estamos falando sobre ros_nodes é novo para você siga a introdução ao ROS já existente neste artigo no site, ou através deste link que fala diretamente sobre os ros_nodes.  



É óbvio que quando estamos falando da natureza distribuída de um robô, pensamos em diversos computadores conectados em uma rede privada, assim o ROSUDP, protocolo de comunicação do ROS se faz perfeito para comunicar ros_nodes em dispositivos diferentes. Mas existem subsistemas de um robô como por exemplo o end-effector, que pode agrupar controles de junta além do controle de gripper (o gripper é utilizado como termo genérico para dispositivo que age como manipulador de algo no mundo real) que tem vários ros_nodes rodando um pedaço de cada controle, ai fica a pergunta:

 

“Vou ter que executar uma operação UDP para comunicar com um ros_node aqui do lado?”

 

É aí que entram os Nodelets, que basicamente consegue carregar as funcionalidades core de um ros_node, com uma vantagem especial, todos os nodelets rodam dentro de um mesmo processo chamado de nodelet_manager, dessa forma a comunicação entre vários Nodelets acaba sendo Zero Copy e com a segurança implicita de um shared_ptr. O uso do protocolo ROSUDP só será necessário se o ros_node subscriber to tópico estiver rodando fora do nodelet_manager, ou seja um ros_node localizado em outro hardware mas conectado através da rede do robô, lembrando que independente de ser Node ou Nodelet o sistema de publish e subscriber utilizado no ROS é sempre o mesmo.

 

Falando em termos de implementação, especificamente do roscpp, que é a client-library para desenvolvimento no ROS usando C++, um nodelet é implementado na sua forma mínima como exemplificado abaixo:

 

 

No arquivo header do seu nodelet, que é colocado dentro do namespace do seu ros_package, note que a implementação derivada da classe base nodelet::Nodelet é por sua conta, a função onInit() será chamada no momento em que seu Nodelet for carregado, além dela temos a função callback() invocada quando um dado é publicado em um tópico de interesse, a função onInit() tem por objetivo fazer as mesmas inicializações que um Node tipicamente faz.  Vejamos agora a implementação do Nodelet:

 

 

E temos a implementação do Nodelet mínimo onde implementamos a função onInit() que vai ser executada no momento que o Nodelet for carregado, observem que a implementação do Nodelet não contém uma função main(), isto ocorre porque o Nodelet é implementado em cima do módulo pluginlib que oferece um importante recurso para carregar classes em C++ em tempo de execução, dado isso basta que o usuário implemente os métodos que desejar ou detalhes específicos do seu Nodelet, é comum por exemplo disparar uma thread-daemon durante o onInit() que cuida do fluxo de dados que entra e sai do Nodelet.

 

Criando seu primeiro Nodelet

 

Agora que sabemos basicamente o que é um Nodelet e qual a sua vantagem, vamos criar um ros_package de forma muito similar feito neste artigo , inclusive sugiro a sua leitura antes de continuarmos para o leitor que ainda não está familiarizado com as workspaces e os ros_packages. A diferença em relação ao processo é que adicionaremos um novo arquivo ao package, aliás dois como veremos a seguir, para isso crie uma catkin workspace, vá para dentro dela e no terminal digite o comando abaixo para criar um package onde colocaremos o nosso nodelet:

 

 

A pasta com o nome do pacote que você escolheu, no exemplo embarcados_ros_nodelet, será criada dentro do diretório src da sua workspace, observe que os argumentos adicionais são para que durante o processo de criação sejam consideradas as dependências nesse pacote, entre elas o pacote nodelet do ROS.  

 

Para o uso dos nodelets, precisaremos criar alguns diretórios adicionais, com o intuito de configurar nossos plugins que a grosso modo, são a implementação de um ou mais nodelets, para isso crie os seguintes diretórios dentro o seu pacote:

 

 

Em launch vamos adicionar um arquivo que permita iniciar um ou mais nodes no sistema do ROS, isso é importante pois para termos pelo menos um nodelet rodando, precisamos inicializar o nodelet propriamente e seu gerenciador, como apresentamos antes, por isso criaremos um arquivo especial para o ROS para esse tipo de tarefa e utilizaremos pela primeira vez o aplicativo roslaunch. Na pasta plugins iremos configurar nosso nodelet para que ele seja buscável pelo sistema do ROS e pelo gerenciador de nodelets, para isso criaremos um arquivo especial para isso, não se preocupe muito com eles agora, iremos adicionar um template para que você usuário não comece do zero, por agora apenas crie os respectivos arquivos em suas respectivas pastas:



 

E por fim vamos adicionar os arquivos de código, para fins de simplificação vamos criar apenas a implementação do nosso nodelet, sem a necessidade do arquivo .h, este último será apenas criado como arquivo vazio para fins ilustrativos, crie-os com o comando exemplo abaixo:

 

 

Se tudo foi criado corretamente, você deve ter a seguinte estrutura de diretório, veja a imagem abaixo:

 

 

Agora vamos preencher todos os arquivos, começando pelo CMakeLists.txt, nele adicionaremos o fonte de nosso nodelet, tome nota que diferentemente de um ros_node, um nodelete é compilado como uma shared_lib, o que muda um pouco a forma de como devemos editar esse arquivo, veja abaixo como ele deve ficar:

 

 

Notem que devemos usar a macro add_library para colocar todos os arquivos .cpp que irão fazer parte da lib que será gerada do nosso nodelet, atenção para o primeiro argumento, separado por um espaço, esse é o nome que a lib do nosso nodelet vai levar, aproximando a grosso modo, é o nome do nosso executável. target_link_libraries também merece atenção, é nela onde ligamos as demais libs, incluindo as dependências, repare que o primeiro argumento é a nome da lib do nosso nodelet. Vamos agora adicionar um pouco de código, a implementação de nosso nodelet mínimo é mostrada abaixo:

 

 

Não existe muito o que comentar desse nodelet, ele apenas reage publicando uma mensagem no tópico /nodelet_shout quando algum outro processo publica no tópico /nodelet_listen, o código específico do nodelet raramente irá além desse exemplo, a partir dessa implementação o leitor deve ser encorajado a escrever algum nodelet útil para o ROS como drivers para sensores ou mesmo controladores de juntas ou rodas de um robô e fazer todos esses blocos se “falarem”.

Agora precisamos fazer roslaunch, nodelet, e o gerenciador enxergarem nosso arquivo .cpp ai em cima como um nodelet, para isso primeiro vamos configurar o nosso nodelet como plugin, vejam o exemplo abaixo:

 

 

O arquivo é auto-explicativo, basicamente você deve dizer onde está a shared lib do nodelet, não esqueça do prefixo “lib” no nome do executável no nosso exemplo, embarcados_nodelet deve ser referenciado como libembarcados_nodelet, dentro de class name , você deve adicionar a declaração da sua classe, como o nome é igual a definição, tanto type quando name, tem o mesmo valor, TestNodelet, importante, embarcados_ros_nodelet, antes do operador de escopo (::), é o namespace da classe, igual você fez no código, em caso de dúvidas volte na última linha de embarcados_nodelet.cpp e veja como você exportou a classe usando a macro PLUGIN,  em type deve estar igual.

Agora ja sabemos descrever ao gerenciador como é o layout do nodelet a ser carregado, é hora de dizer ao roslaunch como achar o gerenciador e esse nodelet, para isso adicione as linhas abaixo no seu arquivo .launch:

 

 

Também auto-explicativo, o arquivo .launch sempre deve ser adicionado quando o aplicativo ROS desejar fazer uso da ferramenta roslaunch , bastando dizer quais nodes ou nodelets serão carregados ao executar esse arquivo, a sintaxe de exemplo acima não muda a menos que o executável receba parâmetros ou argumentos.

 

E por último e não menos importante, devemos editar o arquivo package.xml, esse arquivo vem pronto e usável por padrão, porém precisamos deixar ele ciente de que esse pacote possui plugins, ou seja nodelets, veja como ele deve ficar:

 

 

A única linha que precisa mudar, está dentro de export nela devemos adicionar onde fica o arquivo que descreve o plugin (o nosso nodelet) atenção ao nome dado nele dentro da pasta plugin, se houver erros o gerenciador ficará incapacitado de localizar seu nodelet e carregá-lo.


Testando seu primeiro Nodelet

 

Agora que temos nossa workspace configurada, podemos testar nosso nodelet, mas primeiro precisamos compilar nosso pacote e atualizar o ambiente do ROS, para isso vá para o diretório de topo da sua workspace, de dentro dele digite no terminal:

 

 

Em caso de sucesso, você deve ver algo parecido com isso aqui no seu terminal, ao final do processo de build:

 

Agora atualize o ambiente do ROS:

 

 

Tudo pronto, agora podemos carregar nosso nodelet e o gerenciador utilizando o utilitário roslaunch, para isso execute o comando (dica! Como você atualizou o ambiente do ROS no comando anterior, o ROS sabe do seu arquivo .launch, assim sendo o tab funciona como auto-completar).



 

Se tudo funcionar corretamente você verá uma série de mensagens, algumas mostrando que o nodeler foi corretamente carregado, veja abaixo:

 

 

Agora vamos ver se nosso nodelet está vivo, primeiro abra outra janela do seu terminal e digite:

 



Você deve ver, além dos tópicos padrão do ROS, os tópicos criados que você adicionou na implementação lá no .cpp:

 

 

Mas não sabemos se o nodelet ainda está vivo, para isso, vamos escutar o tópico /nodelet_shout, esse tópico deve soltar alguma mensagem se falarmos qualquer coisa no tópico que o nodelet escuta, para isso digite:

 

 

A tela deverá ficar assim e travada, até que alguma mensagem apareça neste tópico (dica! Essa é uma das formas de se fazer subscribe diretamente pela linha de comando do ROS):

 

 

Abra agora uma terceira janela no terminal, utilizaremos a ferramenta pub do utilitário rostopic, e adicionaremos uma mensagem qualquer no tópico /nodelet_listen, para isso digite:



 

Atenção a cada, ponto, aspas, entre outros, se o formato do tópico não bater com o tipo da mensagem o ROS não vai aceitar que você publique, o parâmetro -r 10, serve para publicar uma nova mensagem a cada 10Hz. Se tudo funcionar corretamente, você deverá ver algo assim na janela que está esperando dados em /nodelet_shout:

 

 

Pronto! Você fez seu primeiro nodelet funcionar! Agora não pare por aí, utilize as facilidades da classe Nodelet e crie mais nodelets para conversarem entre si, como já dito antes, a conversa entre dois nodelets no mesmo gerenciador é zero-copy com baixíssimo overhead.

 

Conclusão


Configurar e implementar um pacote com suporte a nodelets não é tão trivial quanto utilizar os tradicionais nós do ROS, os ros_nodes, apesar disso seu uso é extremamente simples além do ganho em desempenho que pode ser significativo entre aplicações que exigem alto consumo de CPU como streams de vídeo, ou pilhas de controle real-time, além disso a complexidade de fazer o setup dos nodelets da primeira vez é compensada pelo fato da maioria dos arquivos serem feitos apenas da primeira vez que o setup do projeto for feito. Com isso, temos mais uma ferramenta poderosa do ROS que exploraremos em posts futuros, fiquem ligados e não esqueçam de comentar, colocar suas dúvidas abaixo e em caso de problemas referenciar o projeto completo no meu Github, até a próxima!

 

 

Referências

 

1 - CACACE, Jonathan - Mastering ROS for Robotics Programming - 2nd Ed.

2 - http://wiki.ros.org/nodelet/Tutorials/Running%20a%20nodelet

3 - http://www.clearpathrobotics.com/assets/guides/ros/Nodelet%20Everything.html

4 - https://github.com/uLipe/embarcados_ros_nodelet

NEWSLETTER

Receba os melhores conteúdos sobre sistemas eletrônicos embarcados, dicas, tutoriais e promoções.

Obrigado! Sua inscrição foi um sucesso.

Ops, algo deu errado. Por favor tente novamente.

Licença Creative Commons Esta obra está licenciada com uma Licença Creative Commons Atribuição-CompartilhaIgual 4.0 Internacional.

Felipe Neves
Desenvolvedor de sistemas embarcados apaixonado pelo que faz, divide seu tempo entre trabalhar no Venturus desenvolvendo firmware de tudo quanto é coisa, na Aeolus Robotics se envolvendo com o que há de mais legal em robótica e na Overlay Tech desenvolvendo algumas coisas bem legais para o campo de motion control. Possui mestrado em engenharia elétrica pela Poli-USP e possui interesse em tópicos como: Software embarcado, sistemas de tempo real, controle, robótica móvel e manipuladores, Linux embedded e quase qualquer coisa por onde passe um elétron.

2
Deixe um comentário

avatar
 
2 Comment threads
0 Thread replies
0 Followers
 
Most reacted comment
Hottest comment thread
2 Comment authors
Lucas CoelhoMARCOS LIMA FERNANDES Recent comment authors
  Notificações  
recentes antigos mais votados
Notificar
Lucas Coelho
Visitante
Lucas Coelho

Melhor tutorial de nodelets que vi na internet, incluindo os em inglês. Obrigado por compartilhar!

MARCOS LIMA FERNANDES
Visitante
MARCOS LIMA FERNANDES

parabéns!!! mas não entendo nada disso!!