Um programa de servidor

Sumário

  1. Escrevendo um retorno de chamada de serviço
  2. Criando um objeto servidor
  3. Dando controle ao ROS
  4. Executando e melhorando o programa servidor

Agora vamos dar uma olhada no outro lado das chamadas de serviço, escrevendo um programa que atua como um servidor. A Lista 8.2 mostra um exemplo que oferece um serviço chamado toggle_forward e também dirige um robô turtlesim, alternando entre movimentos para frente e rotações cada vez que o serviço é chamado.

Lista 8.2

Um programa chamado pubvel_toggle.cpp que muda os comandos de velocidade que ele publica, baseado em um serviço que ele oferece.
// Este programa alterna entre comandos de rotação e
// translação, baseado nas chamadas para o serviço.
#include <ros/ros.h>
#include <std_srvs/Empty.h>
#include <geometry_msgs/Twist.h>

bool forward = true;
bool toggleForward(
  std_srvs : : Empty : : Request &req ,
  std_srvs : : Empty : : Response &resp
) {
  forward = ! forward ;
  ROS_INFO_STREAM("Now␣sending␣" << ( forward ?
  "forward" : " rotate ") << "␣commands . ") ;
  return true ;
}

int main( int argc , char ∗∗ argv ) {
  ros::init( argc , argv , "pubvel_toggle") ;
  ros::NodeHandle nh ;

  // Registra nosso serviço com o mestre.
  ros::ServiceServer server = nh.advertiseService (
  "toggle_forward" , &toggleForward) ;

  // Publica comandos, usando o valor mais recente para frente,
  // até que o nó seja encerrado.
  ros::Publisher pub = nh.advertise <geometry_msgs::Twist>(
  "turtle1/cmd_vel" , 1000);
  ros::Rate rate(2);
  while ( ros::ok() ) {
  geometry_msgs::Twist msg ;
  msg.linear.x = forward ? 1.0:0.0 ;
  msg.angular.z = forward ? 0.0:1.0 ;
  pub.publish(msg) ;
  ros : : spinOnce () ;
  rate . sleep () ;
  }
}

O código para atuar como um servidor é muito semelhante ao código para inscrever-se em um tópico. Além das diferenças de nomes — devemos criar um ros::ServiceServer ao invés de um ros::Subscriber — a única diferença é que o servidor pode enviar dados de volta para o cliente, por meio do objeto de resposta e um booleano de indicação de sucesso ou falha.

Escrevendo um retorno de chamada de serviço

Assim como com as inscrições, cada serviço que nossos nós oferecem deve estar associado a uma função de retorno de chamada. Um serviço de retorno de chamada assemelha-se a:

bool function_name(
  package_name::service_type::Request &req,
  package_name::service_type::Response &resp
) {
  ...
}

ROS executa a função de chamada de retorno uma vez para cada chamada de serviço que nosso nó recebe. O parâmetro Request contêm os dados enviados para o cliente. A tarefa do retorno de chamada é preencher os membros de dados com os objetos Response. Este são os mesmos tipos Request e Response que usamos no lado do cliente acima e como tal eles requerem o mesmo cabeçalho e o mesmo pacote de dependências para compilar corretamente. A função de chamada de retorno deve retornar verdadeiro para indicar sucesso e falso para indicar falha.

No exemplo, usamos o tipo de mensagem std_srvs/Empty, no qual os lados Request e Response estão vazios, então não há processamento a ser executado para qualquer um desses objetos. O único trabalho do retorno de chamada é alternar uma variável global booleana, chamada forward, que comanda as mensagens de velocidade publicadas na main.

Criando um objeto servidor

Para associar a função de retorno de chamada com um nome de serviço e para oferecer o serviço para outros nós, nós devemos advertise o serviço:

ros::ServiceServer server = node_handle.advertiseService(
  service_name,
  pointer_to_callback_function
);

Todos esses elementos já apareceram antes.

  • O node_handle é o mesmo handle de nó antigo que nós já conhecemos e amamos.
  • O service_name é o nome de uma string serviço que nós gostaríamos de oferecer. Este deve ser um nome relativo, mas também pode ser um nome global.

⏩ Por causa de alguma ambiguidade percebida em como os nomes privados devem ser resolvidos, ros::NodeHandle::advertiseService se recusa a aceitar nomes privados (isto é, aqueles que começam com ~). A solucão para esta restrição é explorar o fato — um que não usamos até agora — de que nós podemos criar objetos ros::NodeHandle com seus próprios namespaces padrão específicos. Por exemplo, nós poderíamos criar um ros::NodeHandle como este:

ros::NodeHandle nhPrivate("∼");

O namespace padrão para qualquer nome relativo que nós enviamos para este NodeHandle então seria o mesmo que o nome do nó. Em particular, isto significa que se nós usarmos este handle e o nome relativo para anunciar o serviço, deve-se ter o mesmo efeito que usando nomes privados. Por exemplo, em um nó named /foo/bar, nós podemos anunciar o serviço chamado /foo/bar/baz como este:

ros::ServiceServer server = nhPrivate.advertiseService( "baz", callback );

Ou seja, este código tem o mesmo efeito que poderíamos esperar ao tentar anunciar o serviço chamado ∼baz empregando o nosso usual NodeHandle, se este handle estivesse disposto a aceitar nomes privados.

  • O último parâmetro é um ponteiro para a função de retorno de chamada. Uma introdução rápida para ponteiros de funções, incluindo alguns conselhos para potenciais erros de sintaxe, aparecem na Seção 3.4. As mesmas ideias são aplicáveis aqui.

Como com objetos ros::Subscriber, é raro chamar algum métodos de objetos ros::ServiceServer. Ao invés disso, devemos manter um controle cuidadoso do tempo de vida deste objeto, porque o serviço estará disponível para outros nós apenas até que o ros :: ServiceServer seja destruído.

Dando controle ao ROS

Não esqueça que ROS não irá executar nenhuma função de retorno de chamada até que especifiquemos, usando ros::spin() ou ros::spinOnce(). (Detalhes sobre as diferenças entre estas duas funções aparecerem, no contexto de um programa de subscrição, no final da Seção 3.4).

No exemplo, nós usamos ros::spinOnce(), ao invés de ros::spin(), porque temos outros trabalho a fazer — especificamente publicando comandos de velociade — quando não há chamadas de serviços recebidas para processar.

Executando e melhorando o programa servidor

Para testar o programa de exemplo pubvel_toggle, compile-o e execute turtlesim_node e pubvel_toggle. Com ambos executando, você pode alternar entre os comandos de movimento de translação para rotação e vice-versa chamando o serviço toggle_forward a partir da linha de comando:

rosservice call /toggle_forward

Figura 8.1 mostra um exemplo dos resultados.


Figura 8.1:Resultados da execução de pubvel_toggle com algumas chamadas manuais intermitentes para /toggle_forward.

Uma característica potencialmente inesperada deste programa é que pode haver um atraso notável entre o início do comando de chamada de rosservice e a observação de uma mudança real no movimento da tartaruga. Uma parte muito pequena desse atraso pode ser atribuída ao tempo necessário para comunicação entre a chamada de rosservice, pubvel_toggle e turtlesim_node. Contudo, a maior parte do atraso vem da arquitetura de pubvel_toggle. Você consegue ver onde?

A resposta é que, por como usamos o método sleep de um objeto ros::Rate com a frequência relativamente baixa (somente 2Hz), este programa gasta a maior parte do seu tempo dormindo. A maioria das chamas de serviço chegarão quando o sleep está executando e estas chamadas de serviço não podem executar até a chamada de ros::spinOnce(), que acontece somente a cada 0.5 segundos. Portanto, pode haver um atraso de até meio segundo antes que cada chamada de serviço possa ser tratada.

Há ao menos duas formas de contornar este tipo de problema:

  • Nós podemos usar duas threads separada: uma para publicar mensagens e outra para lidar com retornos de chamadas de serviço. Embora ROS não exija que os programas usem threads explicitamente, é bastante cooperativo se eles o fizerem.

  • Nós podemos substituir o laço sleep/ros::spinOnce por um ros::spin, e usar um retorno de chamada do timer para publicar as mensagens.

Aspectos como este podem parecer menor nesta escala — um pequeno atraso na mudança do padrão de movimento de uma tartaruga pode não ser um grande problema — mas para programas para os quais o tempo é mais importante, a diferença pode ser crucial.