---
# System prepended metadata

title: Cap1 - Art 4 - Publicador
tags: [Apostila de Introdução Geral no LEAD - 2024]

---

# 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.

