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

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:

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.