# Взаимодействие Подписчика и Издателя в рамках одной ноды. Часть 2

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

### Введение

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

В этом уроке мы перепишем нашу прошлую программу так, чтобы оба эти события - вызов функции *Подписчиком* и публикация *Издателем* были **независимы** друг от друга.

Для удобной организации доступа к переменным нашей программы мы будем использовать **классы**. Для тех из вас, кто пока чувствует себя неуверенно с принципами объектно-ориентированного программирования, наша программа будет хорошим примером использования классов.

Программа которую мы будем с вами писать, будет делать ровно то, что и предыдущая -  будет следить, чтобы наш робот проезжал ровно 1 метр.

Условие для регулятора не изменится - мы будем ехать вперед, до тех пор, пока разница между текущим значением и нулевым значением, не будет больше 1 метра, и как только она будет больше или равна 1 метру, мы остановим нашего робота.

### Пишем код

Итак, начнем: возьмем программу из предыдущего видео и добавим в нее немного объекто-ориентированной магии.

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

Оставим импорты **rospy** и типов данных на своем месте:

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

import rospy
from nav_msgs.msg import Odometry
from geometry_msgs.msg import Twist
```

А вот дальше сделаем класс который и будем описывать управление роботом.

Как вы знаете из [Базового курса про ROS (раздел про ООП)](http://learn.voltbro.ru/free/ros-intro/lesson9/class.html) - класс описывается при помощи ключевого слова class, и в целом описание класса, очень похоже на определение функции.

Давайте назовем наш класс **RobotMover**:

```python
class RobotMover():
```

И первая функция, которую обязательно надо определить в классе - это функция с названием ***\_\_init\_\_()***. Именно она вызывается, когда создается экземпляр нашего класса:

```python
class RobotMover():
    def __init__(self):
```

В неё удобно запихнуть все наши определения *Подписчиков*, *Издателей*, используемых переменных и т.д.&#x20;

Смотрите мы объявляем в ней *Издателей* и *Подписчиков* из нашей старой программы, а также переменную **vel** типа *Twist()*, которую мы и будем публиковать. Я хочу сделать это здесь в функции ***\_\_init\_()***, чтобы не тратить ресурсы компьютера каждый раз на ее инициализацию в каком-нибудь другом месте:

```python
class RobotMover():
    def __init__(self):
        rospy.init_node('one_meter_node')
        self.vel = Twist()
        self.pub = rospy.Publisher('/cmd_vel', Twist, queue_size=10)
        rospy.Subscriber('/odom', Odometry, self.cb_regulator)
```

{% hint style="info" %}
Обратите внимание, в рамках класса появляется ключевое слово ***self***. Оно указывает на то, что функция или переменная относится именно к **этому экземпляру класса**. Собственно, это сильно облегчает использование переменных внутри класса. Вы можете заполнить ***self*** переменную в одной функции класса, а прочитать ее в другой. Разумеется надо отчетливо понимать, что вы делаете и зачем, и не удивляться, когда переменная изменила свое значение, после того как несколько функций использовали её для своих целей. В целом, подход к использованию переменных класса такой же, как и к использованию глобальных переменных. Это полезная и удобная вещь, если вы пользуетесь ей по назначению. И опасная и непредсказуемая, если вы используете глобальные переменные или переменные класса для того, для чего они не предназначены. В нашей программе в переменных класса мы будем хранить **актуальное состояние объекта**. И каждый раз изменяя или считывая это состояние, мы будем понимать, что в каждый следующий момент времени это состояние будет другим. И в данном случае использовать переменные класса, для хранения состояния объекта - и удобно и оправдано.
{% endhint %}

Вернемся к написанию кода. По правилам хорошего тона, мы будем писать функции таким образом, чтобы каждая из них делала какое-то очень простое, но при этом осмысленное действие. В дальнейшем, такой подход позволит нам с легкостью модифицировать уже написанный код. Давайте опишем функцию обратного вызова. Сделаем ее максимально лаконичной. Все что будет делать эта функция, это брать значение пройденного пути по оси Х из структуры *Odometry* и присваивать его в переменную класса **self.distance\_passed**. Так как никаких других действий эта функция выполнять не будет, переименуем ее из **cb\_regulator** в **cb\_handler:**

```python
    def cb_handler(self, msg):
        self.distance_passed = msg.pose.pose.position.x
```

Обратите внимание на ключевое слово ***self.*** Оно требуется для передачи в качестве аргумента, во все функции класса, использующие переменные класса.

{% hint style="warning" %}
Не забудьте переименовать функцию обратного вызова в инициализации *Подписчика* !
{% endhint %}

Теперь напишем функцию которая будет управлять публикацией значений скорости в топик **/cmd\_vel**. Назовем ее ***vel\_publisher()***:

```python
def vel_publisher(self, vel_value):
```

Здесь кроме ключевого слова ***self***, мы укажем еще один аргумент, который мы будем передавать в эту функцию, для того, чтобы она именно его и публиковала:

```python
    def vel_publisher(self, vel_value):
        self.vel.linear.x = vel_value 
        self.pub.publish(self.vel)
```

Итак, функция берет передаваемый ей аргумент желаемой скорости, заполняет им значение линейной скорости по оси X переменной **vel** и публикует используя объект *Издателя*, который мы инициализируем при создании объекта класса. Так как мы уже определили *Издателя* в функции ***\_\_init\_\_()***, нашего класса, здесь мы можем просто пользоваться этим объектом и не определять его повторно.

Теперь напишем еще одну функцию, которая будет сравнивать значение переменной класса **self.distance\_passed** с расстоянием в 1 метр. И если оно меньше 1 метра, то будет вызывать другую функцию ***vel\_publisher()***, передавая ей в качестве аргумента желаемое значение линейной скорости, а если оно больше или равно одному метру, то будет вызываться все та же функция ***vel\_publisher()***, но с нулевой скоростью в качестве аргумента. Эта функция и будет нашей функцией регулятором, поэтому так ее и назовем ***regulator()***:

```python
    def regulator(self):
        print(self.distance_passed)
        if self.distance_passed >= 1:
            self.vel_publisher(0)
        else:
            self.vel_publisher(0.1)
```

Теперь давайте напишем основной цикл нашей программы. Во-первых, создадим экземпляр нашего класса и передадим ссылку на него в какую-нибудь переменную, чтобы в дальнейшем мы могли пользоваться методами нашего класса обращаясь к ним, через созданный экземпляр:

```python
r = RobotMover()
```

Дальше используем стандартный ROS цикл, который позволит нам корректно выйти из нашей программы по нажатию Ctrl+C:

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

А в самом цикле, мы будем вызывать функцию-регулятор с той частотой, которая нам будет нужна. Например 2 герца:

```python
while not rospy.is_shutdown(): 
    rospy.sleep(0.5) 
    r.regulator()
```

В отличие от прошлой программы - где регулятор, вызывался не из нашего основного цикла, а созданным нами *Подписчиком*, в данном случае именно **МЫ** определяем частоту, с который будет вызываться регулятор, то есть, фактически делаем это с той частотой, которая нам наиболее удобна, и не зависим от того, с какой частотой приходят данные из ROS.&#x20;

Например, можно применить такой аналог - в случае программы из прошлого урока, мы неотрывно смотрим на одометр и как только там появляется 1 метр, сразу останавливаемся. А в текущей реализации, мы поглядываем на одометр с удобной нам частотой, а в остальное время можем заниматься тем, чем хотим.

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

import rospy
from nav_msgs.msg import Odometry
from geometry_msgs.msg import Twist

class RobotMover():
    def __init__(self):
        rospy.init_node('one_meter_node')
        self.vel = Twist()
        self.pub = rospy.Publisher('/cmd_vel', Twist, queue_size=10)
        rospy.Subscriber('/odom', Odometry, self.cb_handler)

    def cb_handler(self, msg):
        self.distance_passed = msg.pose.pose.position.x

    def vel_publisher(self, vel_value):
        self.vel.linear.x = vel_value 
        self.pub.publish(self.vel)

    def regulator(self):
        print(self.distance_passed)
        if self.distance_passed >= 1:
            self.vel_publisher(0)
        else:
            self.vel_publisher(0.1)

r = RobotMover()

while not rospy.is_shutdown(): 
    rospy.sleep(0.5) 
    r.regulator()
```

Вот и все, сохраним нашу программу и запустим это все на роботе, чтобы посмотреть как оно будет работать.

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

Зайдем на самого робота по ssh и запустим файл:

```python
ssh pi@turtlebro37.local

python3 ros_course/pub-sub2.py
```

Наш робот проехал метр и остановился.

Теперь давайте проверим, насколько больше ошибка точности остановки по сравнению с прошлым значением. Запустите предыдущую программу и запишите конечное значение положения по оси X, сбросьте одометрую и запустите программу из этого урока.

Разница не большая, и я бы не сказал, что дело здесь только в частоте принятия решения и публикации команд. На самом деле для улучшения точности остановки робота, нам надо применить качественно другой регулятор, не релейного типа.&#x20;


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://voltbro.gitbook.io/new_bazovyi-kurs-po-ros/lesson5/pub-sub-2.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
