# Capítulo 1 - Artigo 4 - Publicador > Keyboard ## 4.0 - Introdução Até agora nós criamos um ambiente para o desenvolvimentos de projetos ROS, instalamos o ROS completamente, levantamos um nó pronto do ROS (o joy) e vimos seu comportamento. Criamos um nó muito simples e agora vamos criar um nó um pouco mais complexo. A ideia é criar um joy só que usando o teclado. Copiaremos então o funcionamento do joy para criar esse nosso pacote. > [eu quero criar aqui uma mensagem customizada, uma string com stamped, acho que seria legal para dar uma complexidade maior e documentar isso. Acho que isso poderia ser feito depois] ## 4.0.1 - Sumário Geral 1. Introdução do Capítulo 2. Instalando o ROS 3. Nó 4. Publicador < Estamos aqui 5. Subscritor 6. Raspberry Pi 7. Nó Raspberry 8. Launch ## 4.0.2 - Sumário local [TOC] ## 4.1 - Criando um workspace e um PKG - Crie um workspace. Esse será um ambiente para multiplos pacotes. O Workspace é um ambiente de desenvolvimento de um projeto. Um projeto pode ter diversos pacotes. ```bash= user@host:~$ distrobox enter --root humbleV1 user@humbleV1:/home/USER$ mkdir -p ~/ros2_ws/src ``` *(estou assumindo que você já fez o tutorial 1,2 e 3)* - Vamos agora criar um pacote [https://docs.ros.org/en/humble/Tutorials/Beginner-Client-Libraries/Creating-Your-First-ROS2-Package.html](https://docs.ros.org/en/humble/Tutorials/Beginner-Client-Libraries/Creating-Your-First-ROS2-Package.html) ```bash user@humbleV1:/home/USER$ cd ~/ros2_ws/src user@humbleV1:~/ros2_ws/src$ source /opt/ros/humble/setup.bash user@humbleV1:~/ros2_ws/src$ ros2 pkg create --build-type ament_python --license Apache-2.0 key_pkg user@humbleV1:~/ros2_ws/src$ cd key_pkg/key_pkg/ ``` - Feito o pacote. Precisamos então criar nosso nó. ### 4.1.1 Criando o nó ROS de leitura do teclado ```bash user@humbleV1:~/ros2_ws/src/key_pkg/key_pkg$ touch node_pub_key.py ``` - Depois abrimos o arquivo criado e colocamos esse código python: ```python!=1 from pynput import keyboard import time,rclpy from rclpy.node import Node from std_msgs.msg import String pub = None keysPressed = [] def onKeyPress(key,k): keysPressed = k try: keysPressed.append(key.char) except AttributeError: keysPressed.append(str(key)) def getKeys(timeout_seconds = 5): with keyboard.Listener(on_press=onKeyPress): start_time = time.time() while not (time.time() - start_time > timeout_seconds): pass def publish(): global pub,keysPressed getKeys(timeout_seconds = 0.5) msg = String() msg.data = "" for i in keysPressed: msg.data += str(i) + ' ' if not ((pub is None)): pub.publish(msg) keysPressed = [] def main(args=None): global pub rclpy.init(args=args) node = Node("key_node") pub = node.create_publisher(String, '/key', 1) while True: publish() main() ``` - Esse código será explicado no 4.1.3 ```bash user@humbleV1:~/ros2_ws/src/key_pkg/key_pkg$ cd ~/ros2_ws ``` - Depois de feito isso você terá que mudar o arquivo “package.xml”,adicionando as dependencias do seu código. Ele estará assim: ```xml=7 ... <license>Apache-2.0</license> <test_depend>ament_copyright</test_depend> ... ``` - terá que adicionar 3 linhas entre essas duas linhas, ficando assim: ```xml=7 ... <license>Apache-2.0</license> <exec_depend>rclpy</exec_depend> <exec_depend>geometry_msgs</exec_depend> <exec_depend>sensor_msgs</exec_depend> <test_depend>ament_copyright</test_depend> <test_depend>ament_flake8</test_depend> ... ``` - E, por fim, você terá que modificar o setup.py: ```python!=1 ... tests_require=['pytest'], entry_points={ 'console_scripts': [ 'key_node = key_pkg.node_pub_key:main', ], }, ) ``` - O setup.py é quem diz como o colcon irá construir seu nó. - Será necessário instalar as dependencias do seu programa agora. Também será necessário especificar uma biblioteca, pelo uso padrão do Humble. ```bash user@humbleV1:~/ros2_ws$ pip3 install pynput user@humbleV1:~/ros2_ws$ rosdep update user@humbleV1:~/ros2_ws$ rosdep install -i --from-path src --rosdistro humble -y ``` Agora você já pode buildar seu nó. Para isso rode: ```bash ~$ colcon build --packages-select key_pkg --symlink-install ``` ### 4.1.2 Rodando o Nó e verificando Depois que seu pacote foi devidamente buildado, você poderá usar o ROS para chamar o seu nó publicador de teclado. ```bash user@humbleV1:~/ros2_ws$ ros2 run key_pkg key_node ``` Com outro terminal aberto, rode: ```bash user@host:~$ distrobox enter --root humbleV1 user@humbleV1:/home/USER$ ros2 topic list ``` depois que você rodar esse ultimo comando, deve parecer algo assim: ![image](https://hackmd.io/_uploads/HJgEYpvsp.png) Você irá então olhar a saída do código: ```bash user@host:~$ distrobox enter --root humbleV1 user@humbleV1:/home/USER$ ros2 topic echo /key ``` Digite qualquer coisa em qualquer um dos terminais e veja a saída: ![image](https://hackmd.io/_uploads/ByPbc6Dop.png) Como podemos ver, o nó está publicando as entradas do teclado. Com isso já temos um nó publicador que pode ser usado para controlar disositivos usando o teclado. ### 4.1.3 Explicando o código Bom, vamos entender o código. Primeiro vamos dividi-lo em 3 partes. A main, o publish e o getKeys. #### 4.1.3.1 getKeys Vou começar falando da getKeys, para isso vou isolar as funções e criar um novo código, mais simples, que chama ela. ```python= from pynput import keyboard import time keysPressed = [] def onKeyPress(key): global keysPressed try: keysPressed.append(key.char) except AttributeError: keysPressed.append(str(key)) def getKeys(timeout_seconds = 5): with keyboard.Listener(on_press=onKeyPress): start_time = time.time() while not (time.time() - start_time > timeout_seconds): pass getKeys() print("\nTecla: " + str(keysPressed)) ``` Bom, se você rodar o código acima, você verá que ele irá esperar você digitar qlqr coisa por 5 segundos e depois irá printar em uma lista, letra por letra o que você digitou. Bom, getKeys é uma função que recebe um número time e possui uma variável global que precisaser instanciada ao chama-la, o seu nome é keysPressed e é nela que ficam guardados as teclas no determinado tempo, se você não passar tempo nenhum, o padrão será usado: 5. E como ela funciona? A sua primeira linha (linha 13 do código acima) faz o seguinte: Pega o método Listener da classe keyboard e atribui uma função para um dos atributos. O with inicializa a execução desse método. Assim, quando alguma tecla é pressionada, a função onKeyPress é chamada. Depois as linhas 14 a 16 é basicamente uma função que segura a execução da linha 13 pelo timeout_seconds definido. A função onKeyPress definida na linha 6 é quem recebe a tecla e escreve na lista keysPressed. A função getKeys é quem gatinha a função onKeyPress pelo tempo desejado e preenche com a tecla. #### 4.1.3.2 main Com isso explicado vamos para a main: ```python= from pynput import keyboard import time,rclpy from rclpy.node import Node from std_msgs.msg import String pub = None ``` ```python=32 def main(args=None): global pub rclpy.init(args=args) node = Node("key_node") pub = node.create_publisher(String, '/key', 1) while True: publish() main() ``` Acima está o recorte da main sem as outras funções, apenas o que a main precisa. Na linha 1 a 4 são as importações das classes usadas no código. na linha 6 é instanciada como None a variável pub. Ela será usada como publicadora e ela é instanciada como none pq haverá um teste na hora de sua publicação, para só mandar publicar quando de fato ela estiver atribuida. O motivo para isso é que quando o projeto vai crescendo, é muito fácil alguma váriável de publicação não ser instanciada, então fazemos isso para entender a origem de eventuais bugs. a próxima linha a ser executada nesse recorte ai é a 43, chamando a função main. Ela ta separada ai pq está preparada para caso precise escalar, colocando try e argumentos para diferentes execuções. A primeira coisa que ela faz é acessar a variável pub ali na linha 33, depois inicia o ros e define o nó. ```python=38 pub = node.create_publisher(String, '/key', 1) ``` Assim, na linha 38 cria um publicador desse nó. Um nó pode publicar para N lugares diferentes. Vemos que ele instancia o pub com esse publicador. Assim os métodos de publicação estão atrelados a esse objetos. Assim, eu posso criar diversos publicadores pro mesmo nó. Vemos que ele tem 3 variáveis de entrada. A primeira é o tipo de mensagem que será publicada, no caso String. A segunda é o nome do tópico que será publicado. Quem desejar ler o que esse nó ta publicando, terá que usar esse nome para acessar. O último é o "qos_profile", basicamente define quantos tópicos publicará por vez. Se você deseja que o publicador crie uma fila de publicações, para depois publicar, basta aumentar o número. Por fim chegamos nas ultimas 2 linhas. Basicamente o que estou fazendo é chamando a função publish enquanto for vontade do operador. A ideia é que é esse while que repete a execução de publicação. Sem ele o programa iria publicar uma unica vez e matar o nó. #### 4.1.3.4 publish Sobre a função publish: ```python=22 global pub,keysPressed getKeys(timeout_seconds = 0.5) ``` Primeiro ele busca as variáveis de publicação e a lista de teclas pressionadas. Depois ele pede para preenche-la nos próximos meio segundos. Aqui o código fica parado nesse tempo esperando o operador colocar as letras que deseja e assim, preencher essa variável keysPressed. ```python=24 msg = String() msg.data = "" for i in keysPressed: msg.data += str(i) + ' ' ``` Então ele cria uma msg para a publicação e a constrói pegando a lista e criando uma string onde cada letra ta espaçada por espaço. Isso da linha 27 dele colocar uma letra da lista e um espaço é para poder separar. Isso é porque não existe tipo lista para publicação. Vi que o espaço a função não preenche com espaço vazio, e sim com "key.space". Pensei então que se eu separar por espaço, eu poderia fazer um .split(" ") e eu teria minha lista de volta. Funcinou e você verá isso no tutorial do Keyboard + Gazebo. ```python=28 if not ((pub is None)): pub.publish(msg) keysPressed = [] ``` A parte final é basicamente ele publicando a msg construída anteriormente e limpando a lista para ser preenchida novamente. ------- Agora você aprendeu a escrever um nó publicador que publica mensagens com dados. Sua próxima tarefa é criar um subscritor.