Dificuldades no posicionamento do end effector link on eezybotarm

Recentemente passei à fase do pick and place no eezybot arm2, mas pelos vistos vou encalhar nesta fase.

O problema que encontrei é a dificuldade no posicionamento do end effector link on eezybotarm mk2 quando a mesma é efectuada por um script em python.

Não consegui efectuar um único movimento baseado na indicação da pose de destino com a interface do moveit move group em python. O que me deixa desconcertado face ao sucesso em resolver a cinemática inversa e planear trajectorias usando o rviz. Especialmente quando se usa Allow Aproximate IK Solutions activo.

A mensagem habitual é a seguinte:

[ INFO] [1590451846.572774885]: ABORTED: No motion plan found. No execution attempted.

Tentei várias soluções sem qualquer sucesso. Primeiro tentei alterar a tolerancia relativamente ao objectivo com o uso dos seguintes métodos:

move_group.set_goal_tolerance(0.1)
move_group.set_goal_position_tolerance(0.01)
move_group.set_goal_orientation_tolerance(0.1)

Nestas experiências o que consegui foi explicitar uma tolerancia tão grande que a decisão era nao se mexer porque já estava no sitio.

Como não tive sucesso, numa segunda fase tentei usar outro IK solver. Encontrei boas referencias relativamente ao bio_ik disponivel no seguinte endereço, mas também não tive sucesso.

  • https://github.com/TAMS-Group/bio_ik

Nota: O bio_ik solver á semelhança do LMAK também permite a especificação da rotação sobre o eixo z (joint_1) no eezybotarm mk2.

Por último descobri indicações que o seguinte método tinha o mesmo efeito que o Allow Aproximate IK Solutions, activo.

move_group.set_joint_value_target(pose_goal, "eef_link", True)

Mais uma vez não tive qualquer sucesso ao efectuar o posicionamento do braço no script de python.

Neste caso acresce a seguinte mensagem de aviso

[ WARN] [1590451841.449395412]: Returning dirty link transforms

Links

https://answers.ros.org/question/329107/moveit-approximate-ik-solutions/

 

fix moveit tutorials error on build

Quando se segue o  tutorial do moveit o pacote moveit_tutorials não compila por não encontrar o ficheiro moveit_cpp.h.

A solução é não clonar a master branch mas sim a melodic-devel

So… to fix moveit tutorials  error on build:

cd ~/ws_moveit/src

trocar

git clone https://github.com/ros-planning/moveit_tutorials.git -b master

por

git clone https://github.com/ros-planning/moveit_tutorials.git -b melodic-devel

Moveit gripper command

No artigo sobre a exploração do eezy bot arm mk2  com o ros moveit com base em mensagens FollowJointTrajectoryAction, ficou em aberto o controlo da pinça ou garra (gripper, claw?).

Pois esse artigo refere-se a controlo do posicionamento do end effector, e não ao controlo da acção do end effector propriamente dito.

Sobre o assunto não encontrei propriamente muita informação mas no entanto, destaco os seguintes:

  • https://answers.ros.org/question/307638/how-to-grasp/
  • https://answers.ros.org/question/227576/how-to-configure-the-gripper_action_controller-correctly/

Os dois artigos acima forneceram as informações importantes para configurar a parte do ros moveit, ou seja as alterações ao ficheiro config/controllers.yaml descritas mais abaixo.

A outra fonte de inspiração, que se aplicou às alterações do node eba_arm_control.py de modo a implementar o action server para o GripperCommand, foi um ficheiro do nxdefiant, que reproduzo na integra no meu repositório, e está disponivel no seguinte endereço:

https://github.com/inaciose/roscode/blob/master/ros/moveit-trajectory-griper-action-server-node.py

Deste script extrai apenas as partes necessárias ao controlo da pinça. Note-se que o script também implementa o feedback e o cancel, quer no GripperCommand quer no FollowJointTrajectory.

Alterações ao pacote ebamk2_moveit_config

Ao ficheiro config/controllers.yaml anteriormente criado no pacote de configuração do moveit para o braço eezybotarm mk2, cujo conteúdo original era o seguinte:

controller_manager_ns: /
controller_list:
  - name: arm_controller
    action_ns: follow_joint_trajectory
    type: FollowJointTrajectory
    joints: [joint_1, joint_2, joint_3, joint_6]

Foi removida a joint_6 da lista dos joints para o FollowJointTrajectory, e adicionadas as seguintes linhas que definem o controlador para a pinça (gripper) do robot.

  - name: gripper_controller
    action_ns: gripper_action
    type: GripperCommand
    joints: [joint_6]
    action_monitor_rate: 20
    goal_tolerance: 0.002
    max_effort: 100
    stall_velocity_threshold: 0.001
    stall_timeout: 1.0

Após as alterações, o conteúdo do ficheiro config/controllers.yaml ficou conforme apresentado abaixo.

controller_manager_ns: /
controller_list:
  - name: arm_controller
    action_ns: follow_joint_trajectory
    type: FollowJointTrajectory
    joints: [joint_1, joint_2, joint_3]
  - name: gripper_controller
    action_ns: gripper_action
    type: GripperCommand
    joints: [joint_6]
    action_monitor_rate: 20
    goal_tolerance: 0.002
    max_effort: 100
    stall_velocity_threshold: 0.001
    stall_timeout: 1.0

Com esta alteração o moveit passa a publicar mensagens nos tópicos: gripper_controller/gripper_action/goal.

Se o nosso node nao implementar o action server para estas mensagens o moveit suspende o uso do controller.

Nessa situação aparecem no terminal as seguintes mensagens de aviso e erro

[ WARN] [1590357841.286752284]: Waiting for gripper_controller/gripper_action to come up
[ERROR] [1590357847.287084048]: Action client not connected: gripper_controller/gripper_action

Esta mensagem deixa de aparecer quando o nosso node, no meu caso o  eba_arm_control.py, responde adequadamente.

Alterações ao eba_arm_control.py

As alterações ao node não foram muito grandes, e encontram-se resumidas abaixo.

Incluir a importação das bibliotecas de mensagens usadas.

# gripper
from control_msgs.msg import GripperCommandAction, GripperCommandActionResult, GripperCommandFeedback

def __init__(self): 

No metodo de inicialização da classe foi adicionado o código que declara e inicializa o actionlib server.

        # Set up action GripperCommandAction
        self.action_server_gripper = actionlib.SimpleActionServer("/gripper_controller/gripper_action", 
                                        GripperCommandAction, 
                                        self.do_action_gripper_callback, False)
        self.action_server_gripper.start()

def do_action_gripper_callback(self, goal):

Este método é novo e é o responsavel pelo processamento das mensagens recebidas. Ou seja abre e fecha a pinça e devolve o resultado.

    def do_action_gripper_callback(self, goal):
        print ('Received gripper goal %.2f'%goal.command.position)		

	# adjust position and commit to hardware
        self.c(goal.command.position)
        self.publish_servos_state(self.robot_joints)

	# Action done, either canceled or position reached
        self._gripper_result.status = 3 # 3 = GoalStatus.SUCCEEDED
        self._gripper_result.result.position = goal.command.position
        self._gripper_result.result.stalled = False
        self._gripper_result.result.reached_goal = True
        self.action_server_gripper.set_succeeded(self._gripper_result.result)
        print('gripper move complete')

Para histórico, foi feita uma cópia do node eba_arm_control.py sem as alterações para contemplar o gripper, para o seguinte ficheiro:  eba_arm_follow_trajectory.py.

Considerações finais sobre grasp, gripper e afins

Apesar de ter chegado a uma implementação, ela não é completamente satisfatória.

Em primeiro lugar porque, conforme artigo indicado abaixo, pelo que percebi não existe um interface para o GripperCommand no rviz do moveit.

https://answers.ros.org/question/313637/openclose-end-effector-with-moveit-rviz/

Por outro lado, parece que existe mais que um tipo de solução para este tipo de problema.

Conforme o link seguinte, pode ser usadas soluções baseadas no, pick and place pipeline, moveit_grasps e no moveit_tasks_constructor.

https://answers.ros.org/question/350706/open-gripper-using-grippercommand-in-pick-and-place-pipeline/

Provavelmente voltarei mais tarde a este assunto

Alterações ao pacote ebamk2_moveit_config

Daquilo que aprendi, das descrições e exemplos que encontrei na web sobre a exploração de um braço robótico real, e de baixo custo, com o ros moveit! é que existem quatro ficheiros importantes, que tem de ser criados ou alterados no pacote que resulta da execução do moveit_setup_assistent.

Este é um exemplo das alterações aplicadas ao pacote ebamk2_moveit_config. Pacote de configuração do moveit para braço robotico eezybotarm mk2.

Algumas notas sobre o hardware real. Os actuadores em cada articulação são servo motores, sem qualquer feedback.

Existem 4 joints, correspondentes a 4 servo motores. Incluindo o da pinça.

Provavelmente deveria estar apenas a enumerar 3, decidi colocar o joint6 que é o da articulação da pinça (o que abre e fecha a pinça). Irei saber depois. Quando abordar o controlo da pinça com o moveit.

Cada um dos motores é referido como um joint_n, pelo que os termos joint_1, joint_2, joint_3, e joint_6, referem os motores. Os nomes usados são importantes e devem ser os nomes das joints existentes no ficheiro urdf usado como base para o pacote de configuração do moveit para o eezybotarm mk2

controllers.yaml

O primeiro passo é criar o ficheiro (ebamk2_moveit_config/config/controllers.yaml) com o seguinte conteúdo:

controller_manager_ns: /
controller_list:
  - name: arm_controller
    action_ns: follow_joint_trajectory
    type: FollowJointTrajectory
    joints: [joint_1, joint_2, joint_3, joint_6]

Este ficheiro vai origem ao tópicos

/arm_controller/follow_joint_trajectory/cancel
/arm_controller/follow_joint_trajectory/feedback
/arm_controller/follow_joint_trajectory/goal
/arm_controller/follow_joint_trajectory/result
/arm_controller/follow_joint_trajectory/status

joint_names.yaml

O segundo passo é criar o ficheiro (ebamk2_moveit_config/config/joint_names.yaml) com o seguinte conteúdo:

controller_joint_names: [joint_1, joint_2, joint_3, joint_6]

ebamk2_moveit_controller_manager.launch.xml

O terceiro passo é substituir ros_controllers.yaml por controllers.yaml no ficheiro abaixo.

(ebamk2_moveit_config/launch/ebamk2_moveit_controller_manager.launch.xml)

O conteúdo será o seguinte:

<launch>

<!-- loads moveit_controller_manager on the parameter server which is taken as argument 
if no argument is passed, moveit_simple_controller_manager will be set -->
<arg name="moveit_controller_manager" default="moveit_simple_controller_manager/MoveItSimpleControllerManager" />
<param name="moveit_controller_manager" value="$(arg moveit_controller_manager)"/>

<!-- loads ros_controllers to the param server -->
<rosparam file="$(find ebamk2_moveit_config)/config/controllers.yaml"/>
</launch>

robot_planning_execution.launch

O ultimo passo é criar o ficheiro: (ebamk2_moveit_config/launch/robot_planning_execution.launch)

Este launch file é o usado para iniciar os nodes necessários para o braço robótico  executar os movimentos planeados no ros moveit.

<!-- connect to micro controller -->
<arg name="baud" default="57600"/>
<arg name="port" default="/dev/ttyUSB0"/>

<node pkg="rosserial_python" type="serial_node.py" name="init_serial_node">
<param name="baud" value="$(arg baud)"/>
<param name="port" value="$(arg port)"/>
</node>

<!-- load the joint names configuration file manualy created -->
<rosparam command="load" file="$(find ebamk2_moveit_config)/config/joint_names.yaml"/>

<!-- load the robot_description parameter before launching ROS-I nodes -->
<include file="$(find ebamk2_moveit_config)/launch/planning_context.launch" >
<arg name="load_robot_description" value="true" />
</include>

<!-- run the actionlib moveit messages to robot hardware interface node -->
<node pkg="rosarm_control" type="eba_arm_control.py" name="arm_controller" output="screen"/>

<!-- publish the robot state (tf transforms) -->
<node name="robot_state_publisher" pkg="robot_state_publisher" type="robot_state_publisher" />

<!-- include automatic generated launch files -->
<include file="$(find ebamk2_moveit_config)/launch/move_group.launch">
<arg name="publish_monitored_planning_scene" value="true" />
</include>

<include file="$(find ebamk2_moveit_config)/launch/moveit_rviz.launch">
<arg name="rviz_config" value="true"/>
</include>

</launch>

Partes relevantes deste artigo provêm das instruções descritas no documento no seguinte endereço:

https://industrial-training-master.readthedocs.io/en/melodic/_source/session3/Build-a-Moveit!-Package.html

 

eezybotArm MK2 ROS moveit! basic integration

Depois de conseguir por a funcionar soluções menos apropriadas para movimentar o eezybotarm mk2 no ros moveit!, como, por exemplo, conduzir o robot em espelho com a apresentação do rviz, por subscrição do tópico fake_controller_joint_states, quando se corre o demo.launch, consegui implementar uma forma mais adequada de exploração do eezybotarm mk2 com o moveit!. com base nas mensagens follow joint trajectory.

Passo 1 – Criar ou adaptar de alguma fonte o firmware para o microcontrolador que é o responsavel directo pelos actuadores. Neste caso do eezybotarm mk2 os servo motores.

Passo 2 – Criar ou adaptar de alguma fonte o node ros com o actionlib server que recebe as mensagens joint_trajectory_action, processa as e publica ao longo do tempo a sequência dos ângulos a aplicar a cada actuador em cada passo.

Passo 3 – Criar a descrição do braço robotico num ficheiro com o formato urdf.

Passo 4 – Criar o pacote de configuração do braço robótico para o moveit! com o moveit_setup_assistant.

Passo 5 – Alterar o pacote de configuração do braço robótico para o moveit!  pois é necessário criar e alterar ficheiros de configuração (yaml) e de lançamento (launch). Ver alterações ao moveit config de um braço para ligação eficaz ao nosso hardware.

Passo 6 – executar o comando de lançamento de nodes:

roslaunch ebamk2_moveit_config ebamk2_planning_execution.launch

Deverá aparecer o rviz com a representação do braço e o interface de controlo que permite a selecção da nova posição do braço robotico.

Para aceder ao controlo sobre o braço é necessário ter o Display:  MotionPlaning. Para adicionar, caso necessário, clicar no botão Add na área de Displays e seleciona-lo.

Nesta solução não se usa um hardware interface em conformidade com as especificações e interface do ros control. O  termo controllers que é usado nas vaŕias informações disponiveis sobre o assunto deixa-me um bocado confundido, até por que encontrei informação nesse sentido (ver link abaixo). Mas tanto quanto percebi,  o controller não necessita de ter uma hardware interface que siga o padrão do ros control.

https://answers.ros.org/question/331384/building-a-custom-hardware-interface-for-moveit/

O que indicamos como controller determina o namespace de um conjunto de tópicos, com os nomes tipicos dos actionlib servers e com tipos de mensagens apropriados, como por exemplo, no caso das indicações da trajectoria do braço, o control_msgs/FollowJointTrajectoryActionGoal.

O nosso node, e que é indicado como controller, tem de ter um action  server que cuide do processamento dessas mensagens, enviando os dados necessários para o controlo de posição dos diversos servo motores a partir das informaçoes de posição (e …velocidade e esforço), existentes nas mensagens recebidas.

Nesta minha primeira implementação do node com um action server para mensagens da familia FollowJointTrajectory adoptei o script meArm.py em python que descobri no pacote typetyp.

Sobre este node em c++ ver o seguinte link.

https://answers.ros.org/question/192739/implement-moveit-on-real-robot/

Outra nota importante é que não existe qualquer feedback do hardware, pelo que a posição das articulações que compõem as joint_states publicadas são todas meras copias das posições desejadas. Para podermos ter publicadas as posições reais, teremos, caso do ebamk2, de usar servos com feedback e escrever o codigo adequado para usar esse feedback no nosso node.

O proximo passo é o controlo da pinça, já que este tipo de mensagens apenas trata do posicionamento do end effector, e não diz nada sobre como este se comporta.

 

typepyt

O pacote typepyt explora o pequeno braço robótico meArm no ROS. Tendo em consideração que eezyBotArm partilha o mesmo tipo de cadeia cinemática com o meArm decidi explorar este pacote de software disponível no seguinte endereço:

https://github.com/inaciosef/TyPEpyt

Nota: o fork explorado foi:

https://github.com/matrhint/TyPEpyt

Logo ao inicio chamou-me a atenção a forma como os ficheiros estavam dispostos no pacote. Normalmente não existem ficheiros urdf e launch na raiz.

Uma das coisas que me chamou a atenção é que o urdf que descreve o braço robótico aparenta representar melhor a cadeia cinemática que existe num robot manipulador deste tipo, do que aquela que descobri no repositório do ntbd.

No urdf do ntdb o que acontece é que quando a haste principal se desloca, a haste horizontal que se articula no seu topo mantém o seu ângulo com a haste principal (ver video abaixo) .

No entanto na descrição do meArm as duas haste relacionam-se de forma diferente (ver video abaixo)

Como se pode observar segundo este urdf, a haste horizontal tem um ângulo relativamente a haste principal que vai variando com a posição desta. Este comportamento está mais próximo do real.

A visualização do modelo no rviz é efectuada com o seguinte comando:

roslaunch typepyt urdf_visualize.launch model:='$(find typepyt)/typepyt.urdf'

 

De seguida instalei os seguintes pacotes por serem requisitos para continuar a explorar o typepyt. O primeiro dos quais está disponível para ser instalado por clonagem e compilação

https://github.com/mintar/mimic_joint_gazebo_tutorial

O segundo é uma ferramenta de teleop para trajectórias de braços e é instalado via apt

sudo apt install ros-melodic-rqt-joint-trajectory-controller

A experiência seguinte foi lançar o lançar unico launch file que está na respectiva pasta.

roslaunch typepyt gazebo.launch

O terminal exibiu um numero considerável de erros que reproduzo parcialmente abaixo e no final a simulação no gazebo não estava funcional.

[WARN] [1589846396.527857, 0.000000]: wait_for_service(/controller_manager/load_controller): failed to contact, will keep trying
(...)
[ERROR] [1589846397.308676399, 0.120000000]: No p gain specified for pid. Namespace: /gazebo_ros_control/pid_gains/hip
[ERROR] [1589846397.309328809, 0.120000000]: No p gain specified for pid. Namespace: /gazebo_ros_control/pid_gains/shoulder
[ERROR] [1589846397.309993934, 0.120000000]: No p gain specified for pid. Namespace: /gazebo_ros_control/pid_gains/elbow1
[ERROR] [1589846397.310607059, 0.120000000]: No p gain specified for pid. Namespace: /gazebo_ros_control/pid_gains/wrist

 

De seguida experimentei lançar launch file typepyt.launch disponível na raiz do pacote.

roslaunch typepyt typepyt.launch

Também lançou uma serie de erros relacionados com o ganho do parametro p do pid.

[ WARN] [1589847824.627491156, 0.129000000]: Deprecated syntax, please prepend 'hardware_interface/' to 'PositionJointInterface' within the <hardwareInterface> tag in joint 'hip'.
[ERROR] [1589847824.628310336, 0.129000000]: No p gain specified for pid. Namespace: /gazebo_ros_control/pid_gains/hip

Os problemas foram corrigidos pela inclusão do seguinte ficheiro de configuração no launch file.

<rosparam file="$(find typepyt)/config/gazebo/gazebo_controller.yaml" command="load" />
Posteriormente consegui controlar as hastes do braço com a inclusão do trajectory teleop no ficheiro launch.
 <!-- Load teleop -->
<node name="rqt_joint_trajectory_controller" pkg="rqt_joint_trajectory_controller" type="rqt_joint_trajectory_controller" />
O repositório também contem um pacote de configuração do braço para o moveit feito com o moveit_setup_assistant.

coffee-bot

O coffee bot é o nome de um pacote do ROS (coffee_bot), e também de um repositório do github que contem quer o pacote para o ROS quer o firmware para o Arduino, necessário para explorar o braço robótico eezyBotArm MK2 com o Robotic Operating System.

O repositório original está disponivel a partir do meu fork no seguinte endereço: https://github.com/inaciosef/coffee-bot.

Este conjunto de software para o braço robótico eezyBotArm MK2 permite:

  • Comandar o braço robotico com o teclado
  • Gravar os movimentos executados
  • Reproduzir os movimentos gravados de forma automática
  • Visualizar o movimento do braço no rviz (o firmware publica mensagens sensor_msgs/JointState)

Conheci este pacote por ter visto o video abaixo no youtube.

A interação entre o node ROS e o firmware do Arduino (que é feito com base no rosserial) é efectuada com base nos tópicos x, y, z e g, que correspondem aos vários servos do braço e aceitam mensagens do tipo std_msgs/UInt16. Existe também um tópico com mensagens do tipo sensor_msgs/JointState que é publicado pelo firmware e informa a posição das várias articulações do braço robótico.

Este pacote foi importante na elaboração da parte do pacote rosarm_control que diz respeito ao eezyBotArm.

 

 

diff_drive_controller

O pacote diff_drive_controller faz parte dos ros_controllers e aplica-se ao controlo a uma base de robot com propulsão diferencial.

O diff_drive_controller necessita de um node que implemente a interface com o hardware de acordo com o estabelecido para o ros control. Ou seja um programa baseado numa classe que herde a hardware_interface::RobotHW, e implemente os métodos que façam a ligação com o controller_manager, o diff_drive_controller e o hardware, via firmware.

O controlo é efectuado por comandos de velocidade, ou seja mensagens do tipo geometry_msgs/Twist). Destas mensagens o controlador extrai o componente x da velocidade linear e o componente z da velocidade angular (os outros componentes são ignorados), que utiliza para calcular a velocidade de cada uma das rodas. Estas velociades são então enviadas através do node com o hardware interface para cada uma das duas rodas existentes num robot 2WD. A odometria é calculada e publicada pelo controlador com base no feedback que provem do hardware através do mesmo interface.

O diff_drive_controller necessita de um conjunto de configurações que devem obedecer a critérios especificos e estarem adequadas ao hardware.

O ficheiro robot.urdf que descreve o robot deve conter as articulações (joints), neste caso das rodas, com os nomes adequados, ou em consonancia com os nomes usados no programa e no ficheiro de configuração (controllers.yaml).

Exemplo de joints relevantes num robot.urdf
<joint name="front_right_wheel_joint" type="continuous">
(...)
</joint>

<joint name="front_left_wheel_joint" type="continuous">
(..)
</joint>

O ficheiro controllers.yaml que contém a configuração dos controllers, entre os quais a do diff_drive_controller, tendo cada um deles os seus parametros de configuração. No minimo tem de conter a identificação do controller com o respectivo tipo e as articulações (joints) neste caso as rodas com o mesmo nome que está no urdf e no programa.

O conteúdo minimo para o diff_drive_controller  no controllers.yaml é o seguinte:

robot_velocity_controller:
    type: "diff_drive_controller/DiffDriveController"
    left_wheel: 'front_left_wheel_joint'
    right_wheel: 'front_right_wheel_joint'
    pose_covariance_diagonal: [0.001, 0.001, 1000000.0, 1000000.0, 1000000.0, 1000.0]
    twist_covariance_diagonal: [0.001, 0.001, 1000000.0, 1000000.0, 1000000.0, 1000.0]

Note que o nome indicado para o left_wheel e right_wheel é o mesmo que o indicado no urdf.

Outra coisa importante é que a indentação é importante para a atribuição dos parâmetros ao controlador respectivo.

A descrição do robot incluida no ficheiro robot.urdf e a configuração dos controllers no ficheiro controllers.yaml são carregadas no servidor de paramentros rod ROS (parameter server) por intermédio de um launch file com os seguintes elementos:

<param name="robot_description" command="$(find xacro)/xacro '$(find diff_drive_base1)/urdf/robot.urdf.xacro'
--inorder"/>
<rosparam command="load" file="$(find diff_drive_base1)/config/controllers.yaml"/>

No que se refere a forma como os ros controllers são executados ter em atenção que eles são executados indirectamente por referência aos parametros carregados pelo ficheiro yaml, como por exemplo no caso abaixo em que o diff_drive_controller é carregado pela evocação do robot_velocity_controller.

<node name="base_controller_spawner" pkg="controller_manager" type="spawner"
args="robot_joint_publisher robot_velocity_controller"/>

Apesar de não esta referido aqui, o robot_joint_publisher refere-se ao  joint_state_controller, que é também um ros controller e na mesma por referência á sua inclusão no ficheiro de configuração controllers.yaml.

A identificação usada no controllers.yaml para o diff_drive_controller é usada também no nome dos topicos que são publicados e subscritos pelo node que usa o controller conforme exemplo abaixo:

/robot_velocity_controller/cmd_vel
/robot_velocity_controller/odom
/robot_velocity_controller/parameter_descriptions
/robot_velocity_controller/parameter_updates

 

Fontes

http://wiki.ros.org/diff_drive_controller