Um programa subscriber

Sumário

  1. Escrevendo uma função callback
  2. Criando um objeto subscriber
  3. Dando controle ao ROS
  4. Compilando e executando subpose

Até agora, nós vimos um exemplo de programa que mostra mensagens. Essa é, com certeza, apenas metade da história quando se trata da comunicação com outros nós através de mensagens. Vamos agora dar uma olhada no programa que se inscreve em mensagens publicadas por outros nós.

Continuando a usar turtlesim como uma plataforma de teste, vamos nos inserir no tópico /turtle1/pose no qual turtlesim_node atua1. Mensagens nesse tópico descrevem o pose — um termo referente a posição e orientação — do turtle. Lista 3.5 mostra um pequeno programa que se inscreve nessas mensagens e as resumem para nós via ROS_INFO_STREAM. Embora algumas parte deste programa já devem ser familiares, existem três novos elementos.

Lista 3.5

Um programa ROS chamdo subpose.cpp que se inscreve a dados pose editados por um robô turtlesim.
1 // Esse programa se inscreve no turtle1/pose e mostra suas
2 // mensagens na tela.
3 #include <ros/ros.h>
4 #include <turtlesim/Pose.h>
5 #include <iomanip> // for std :: setp recision and std :: fixed
6
7 // Uma função callback. Executada cada vez que uma nova mensagem
8 // pose chega.
9 void pose MessageReceived (const turtlesim :: Pose&msg ) {
10 ROS_INFO_STREAM(std :: setprecision (2) << std :: fixed
11 << "position =("<< msg.x << " , " << msg.y << ")"
12 << "␣direction=" << msg.theta);
13 }
14
15 int main (int argc, char ∗∗ argv) {
16 // Inicializa o sistema ROS e se torna um nó.
17 ros :: init(argc, argv, "subscribe_to_pose");
18 ros :: NodeHandle nh ;
19
20 // Criar um objeto subscriber.
21 ros :: Subscriber sub = nh.subscribe ("turtle1/pose" , 1000,
22 &poseMessageReceived);
23
24 // Deixe o ROS assumir o controle .
25 ros :: spin();
26 }

Escrevendo uma função callback

Uma diferença importante entre publishing e subscribing é que um nó de subscribing não sabe quando as mensagens chegarão. Para lidar com esse fato, devemos inserir qualquer código que responda a mensagens recebidas dentro de uma função callback, a qual o ROS convoca uma vez para cada mensagem que chega. Uma função subscriber callback se parece com isto:

void function_name(const package_name::type_name &msg) {
. . .
}

O package_name e type_name são os mesmos que para publising: Eles fazem referência a classe de mensagem do tópico no qual nós pretendemos nos inscrever. O corpo da função callback tem então acesso a todos os campos na mensagem recebida, e pode armazenar, usar, ou descartar aquela informação como achar melhor. Como sempre, nós devemos incluir o cabeçalho apropriado que define essa classe.

No exemplo, nosso callback aceita mensagens do tipo turtlesim::Pose, então o cabeçalho que precisamos é turtlesim/Pose.h. (Nós podemos aprender que esse é o tipo de mensagem correto usando o rostopic info; relembrar a seção 2.7) O callback apenas imprime alguns dados da mensagem, incluindo seus membros de dados x, y, e theta, por meio da ROS_INFO_STREAM. (Nós podemos aprender quais áreas de dados o tipo de mensagem tem usando rosmsg show, novamente da seção 2.7) Um programa real iria, claro, no geral fazer algo significativo trabalhar com a mensagem.

Note que funções de subscriber callback tem um tipo de retorno vazio. Um pouco de atenção deve confirmar que isso faz sentido. Já que é trabalho do ROS chamar essa função, não há espaço no nosso programa para nenhum valor de retorno não-vazio.

Criando um objeto subscriber

Para nos increvermos em um tópico, nós criamos um ros::Subscriber object:

ros::Subscriber sub = node_handle.subscribe(topic_name,
queue_size, pointer_to_callback_function);

Essa linha tem várias partes móveis( das quais a maioria tem correspondente na declaração de um ros::Publisher):

  • O node_handle é o mesmo node handle que nós já vimos várias outras vezes.

  • O topic_name é o nome do tópico no qual nós desejamos nos inserir, na forma de uma string. Esse exemplo usa “turtle1/pose”. Novamente, nós omitimos a barra principal para fazer dessa string um nome relativo.

  • O queue_size é o tamanho inteiro da mensagem fila para esse subscriber. Normalmente, você pode usar um valor grande como 1000 sem se preocupar muito sobre o processo de enfileiramento.

⏩ Quando novas mensagens chegam, elas são armazenadas em uma fileira até que o ROS tenha a chance de executar a sua função callback. Esse parâmetro estabelece um número máximo de mensagens que o ROS vai armazenar naquela fileira de uma vez. Se novas mensagens chegarem enquanto a fileira estiver cheia, as mensagens mais antigas não processadas serão descartadas para abrir espaço. Isso pode parecer, superficialmente, muito parecida com a técnica usada para uma mensagem publishing, mas difere de uma maneira importante: O ritmo em que o ROS consegue esvaziar uma fileira publishing depende do tempo levado para transmitir efetivamente as mensagens aos subscribers e está amplamente fora do nosso controle. Em contrapartida, a velocidade com a qual o ROS esvazia uma fileira subscriber depende do quão depressa nós processamos os callbacks. Contudo, nós podemos reduzir a probabilidade da sobreposição de uma fileira subscriber por (a) certificando que nós permitimos os callbacks acontecerem através ros::spin ou ros::spinOnce, frequentemente, e (b) reduzindo a quantidade de tempo consumido por cada callback.

  • O ultimo parâmetro é um apontador para a função callback que o ROS deve executar quando mensagens chegam. Em C++, você pode levar um apontador a uma função usando o operador e-comercial (&, “endereço de”) no nome da função. No nosso exemplo, se parece com isto:
     &poseMessageReceived
    

⚠️ Não cometa o erro comum de escrever () (ou até (msg)) depois do nome da função. Esses parênteses (e argumentos) são necessários apenas quando você realmente quer chamar uma função, não quando você quer levar um apontador a uma função sem chamá-la, como nós estamos fazendo aqui. O ROS supre os argumentos exigidos quando chama sua função callback

⏩ Comentário na sintaxe do C++: O e-comercial é na verdade opcional e muitos programas o omitem. O compilador pode dizer que você quer um apontador para a função, ao invés do valor devolvido pela execução da função, porque o nome da função não é seguido de parênteses. A sugestão do autor é inclui-lo, pois faz o fato de que estamos lidando com um apontador mais óbvio para leitores humanos.

Você pode notar que, durante a criação do ros::Subscriber object, nós não mencionamos explicitamente o tipo da mensagem em parte alguma. De fato, o método subscribe é antecipado e o compilador C++ deduz o tipo de mensagem correto baseado no tipo de dado do ponteiro função callback que fornecemos.

⚠️ Se você usar o tipo errado de mensagem como argumento para sua função call-back, o compilador não será capaz de detectar esse erro. No lugar disso, você verá mensagens de erro sobre tempo de execução reclamando sobre a incompatibilidade de tipo. Esses erros podem, dependendo do momento, vir tanto de nós publisher quanto subscriber.

Um fato potencialmente contra intuitivo sobre ros::Subscriber objects é que é deveras raro realmente chamar qualquer um de seus métodos. Ao invés disso, a vida útil daquele objeto é a parte mais relevante: Quando construímos um ros::Subscriber, nosso nó estabelece conexões com quaisquer publisher do tópico nomeado. Quando o objeto é destruído - seja por sair de cena ou por exclusão de um objeto criado pelo novo operante - essas conexões são derrubadas.

Dando controle ao ROS

A complicação final é que o ROS só executará nossa função callback quando nós dermos a permissão explícita para tal. Existem na realidade dois jeitos levemente diferentes de realizar isso. Uma versão se parece com isto:

ros::spinOnce();

Esse código pede que o ROS execute todos os callbacks pendentes de todas as inscrições dos nós e então devolver o controle para nós. A outra opção se parece com isto:

ros::spin();

Essa alternativa para ros::spinOnce() solicita ao ROS para esperar e executar call-backs até que os nós se encerrem. Em outras palavrass, ros::spin() é grosseiramente equivalente a este ciclo:

while(ros::ok()) {
ros::spinOnce();
}

A questão de se devemos usar ros::spinOnce() ou ros::spin() se resume a isto: Seu programa tem algum trabalho repetitivo para fazer, além de responder a call-backs? Se a resposta for “Não”, então use ros::spin(). Se a resposta for “Sim”, então uma opção razoável é escrever um ciclo que realize o outro trabalho e chame ros::spinOnce() periodicamente para processar callbacks. Lista 3.5 usa ros::spin() porque o único trabalho daquele programa é receber e sintetizar mensagens pose de entrada.

⚠️ Um erro comum nos programas subscriber é omitir erroneamente tanto ros::spinOnce quanto ros::spin. Nesse caso, o ROS nunca tem a oportunidade de executar sua função callback. Omitir ros::spin irá provavelmente fazer o seu programa parar logo depois de iniciar. Omitir ros::spinOnce pode fazer com que aperente que nenhuma mensagem está sendo recebida.

Compilando e executando subpose

Esse programa pode ser compilado e executado assim como os dois primeiros exemplos que nós vimos.

⚠️ Não esqueça de assegurar que seu pacote tem uma dependencia no turtlesim, a qual é necessária porque nós estamos usando o tipo de mensagem turtlesim/Pose. Veja a seção 3.3 para lembrar como declarar essa dependência.

Uma amostra da saída do programa, de quando tanto o turtlesim_node quanto pubvel também estavam operando, aperece como Lista 3.6.

Lista 3.6

Amostra de saída do subpose, mostrando mudanças graduais no pose do robô.
[ INFO ] [1370972120.089584153] : position = (2.42 , 2.32) direction = 1.93
[ INFO ] [1370972120.105376510] : position = (2.41 , 2.33) direction = 1.95
[ INFO ] [1370972120.121365352] : position = (2.41 , 2.34) direction = 1.96
[ INFO ] [1370972120.137468325] : position = (2.40 , 2.36) direction = 1.98
[ INFO ] [1370972120.153486499] : position = (2.40 , 2.37) direction = 2.00
[ INFO ] [1370972120.169468546] : position = (2.39 , 2.38) direction = 2.01
[ INFO ] [1370972120.185472204] : position = (2.39 , 2.39) direction = 2.03

1 Como nós sabemos que turtlelism_node atua nesse tópico? Uma forma de descobrir é iniciar esse nó e depois usar rostopic list, rosnode info, ou rqt_graphpara ver os tópicos sendo editados. Veja a seção 2.7.