> For the complete documentation index, see [llms.txt](https://voltbro.gitbook.io/new_bazovyi-kurs-po-ros/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://voltbro.gitbook.io/new_bazovyi-kurs-po-ros/lesson4/subscriber.md).

# Подписчик (Subscriber)

{% embed url="<https://youtu.be/Vh6C78DlOMY>" %}
.
{% endembed %}

### Введение

***Подписчик*** - это инструмент, который принимает сигналы, передающиеся внутри системы управления.

Вне зависимости от того, какие это сигналы: данные лидара, одометрия, видео с камеры, положения сервоприводов или любые другие, их всегда передает, т.е. *публикует* ***Издатель***, а принимает, т.е. *подписывается* на них ***Подписчик***.

Например, лидар детектирует препятствия вокруг робота и при помощи ***Издателя*** передает данные об этом в ROS в топик **/scan**. Очевидно, что нам надо как-то получить эти данные для использования в наших программах. Так вот, инструмент, которым мы считываем эти данные из сообщений, это и есть ***Подписчик***.

Чтобы продолжить то, что мы делали ранее ([урок про Издателя](/new_bazovyi-kurs-po-ros/lesson3/publisher.md)), сигнал, который мы будем считывать сейчас это будет сообщение “**Hello ROS**” от, уже написанного нами ***Издателя***.

### Пишем код

Назовем нашу программу: *subscriber.py* и сохраним ее в папку *ros-cource* в корневой папке.

Итак начнем: Как мы помним у нас есть *Издатель*, который публикует строку “**Hello ROS**” в топик с именем “*welcome\_topic*”.

Для того, чтобы использовать сущность *Подписчика* ROS в Python, нам надо подключить библиотеку **rospy**.

```python
#! /usr/bin/env python3
# -*- coding: utf-8 -*-

import rospy
```

Теперь давайте определим тип и структуру данных, которые мы будем принимать при помощи нашего *Подписчика*. Тут все просто: имя топика и тип данных определяется *Издателем*, который публикует сообщения для нас.

Вспоминая нашего уже написанного *Издателя*, мы определим тип данных как строка, так как передаётся сообщение “**Hello ROS**” с типом данных *String*. Топик будет “*welcome\_topic*”, потому что так мы написали в нашей прошлой программе.

```python
from std_msgs.msg import String
```

Теперь создадим и зарегистрируем в ROS нашу *ноду* стандартным методом *init\_node()*:

```python
rospy.init_node("subscriber_node")
```

Теперь давайте создадим объект *Подписчик*. В отличие от урока про [*Издателя*](/new_bazovyi-kurs-po-ros/lesson3/publisher.md) при создании объекта *Издатель*, мы пользовались его методом `publish` для публикации наших сообщений. У *Подписчика* мы не будем пользоваться его методами и поэтому, мы можем не передавать ссылку на экземпляр *Подписчика* ни в какую переменную:

```python
rospy.Subscriber
```

Теперь, нам необходимо указать название топика из которого *Подписчик* будет получать данные и название структуры этих данных:

```python
rospy.Subscriber("/welcome_topic", String, callback)
```

Третьим аргументом при инициализации *Подписчика* является **имя функции обратного вызова**. Разберём поподробнее, что такое **функция обратного вызова** для *Подписчика*.

В общем случае это функция, которая вызывается каждый раз, когда в топике на который подписан *Подписчик* появляются сообщения, то есть, каждый раз когда *Издатель* публикует сообщение, ROS оповещает об этом *Подписчика* и передает ему сообщение от *Издателя* с заполненной структурой данных. Объект *Подписчик* же в свою очередь принимает это сообщение, вызывает **функцию обратного вызова** внутри нашей программы, и передает в нее полученную структуру данных в качестве аргумента.

Давайте напишем **функцию обратного вызова**, которая будет выводить на экран всю переданную в нее в качестве аргумента структуру данных:

```python
def callback(msg): 
    print(msg)
```

Функция принимает объект структуры данных в качестве аргумента и просто печатает его при помощи функции `print()`.

{% hint style="info" %}
Небольшая ремарка. В связи с тем что программа в Python выполняется последовательно, мы должны определить нашу функцию обратного вызова до того, как укажем ее в *Подписчике*, поэтому необходимо расположить её перед вызовом *Подписчика*.
{% endhint %}

Теперь давайте напишем основной цикл нашей программы.

Используем стандартный ROS цикл:

```python
while not rospy.is_shutdown():
```

А в самом цикле используем метод библиотеки rospy - `spin`. Сам метод ничего не делает, кроме того, что не даёт нашему Подписчику закрыться пока мы не выйдем из цикла:

```python
while not rospy.is_shutdown(): 
    rospy.spin()
```

Вот и все! Мы с вами написали программу *Подписчика*! Давайте сохраним её, запустим и посмотрим как она работает.

### Запускаем и тестируем

Запустим `roscore` и оставим его запущенным в терминале.

Откроем новый терминал напишем python3 и полный путь до файла с нашей программой:

```python
python3 ros_course/subscriber.py
```

Запустилось. Но ничего не происходит… Правильно, ведь публикация-то сообщений не идет. И наша программа начнет выводить данные на экран, только тогда когда мы запустим программу ***Издателя*** (***publisher.py***).

Давайте откроем еще один терминал и запустим нашего ***Издателя***:

```python
python3 ros_course/publisher.py
```

Наш *Подписчик* начал принимать сообщения от нашего *Издателя*! Всё работает так как надо!

![](https://278680980-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F8k5AErXJnw5qW5Fo5HbH%2Fuploads%2Fb2rJ5759BxVXlR1R1CLO%2Fsubscriber1.png?alt=media\&token=01ae1c70-f7ea-4aec-b95c-eef76cfd32ca)

```python
#! /usr/bin/env python3
# -*- coding: utf-8 -*-

import rospy
from std_msgs.msg import String

rospy.init_node("subscriber_node")

def callback(msg): 
    print(msg)

rospy.Subscriber("/welcome_topic", String, callback)

while not rospy.is_shutdown(): 
    rospy.spin()
```

В этом примере мы ничего не делаем с теми данными, которые нам поступают от *Издателя, е*сли не считать, того, что мы выводим их на экран. На следующем уроке разберем программу, которая принимает данные с робота
