Первый узел-издатель на rclpy

Пишем первый настоящий узел — издатель, который шлёт команды скорости нашему роботу.

Издатель (publisher) — объект узла, созданный методом create_publisher, через который узел отправляет сообщения в топик.

Что мы строим

Сделаем узел, который каждые полсекунды публикует команду «ехать вперёд» в топик /cmd_vel. Наш base_controller подхватит её и закрутит колёса. Это минимальный «мозг», заставляющий робота двигаться.

Полный код издателя

Код помечен language-text: для запуска нужен ROS-рантайм, в браузере он не исполнится.

import rclpy
from rclpy.node import Node
from geometry_msgs.msg import Twist

class DriveForward(Node):
    def __init__(self):
        super().__init__('drive_forward')
        # издатель: тип Twist, топик /cmd_vel, глубина очереди 10
        self.pub = self.create_publisher(Twist, '/cmd_vel', 10)
        # таймер: вызывать tick каждые 0.5 секунды
        self.timer = self.create_timer(0.5, self.tick)

    def tick(self):
        msg = Twist()
        msg.linear.x = 0.2      # 0.2 м/с вперёд
        msg.angular.z = 0.0     # без поворота
        self.pub.publish(msg)
        self.get_logger().info('Еду вперёд: 0.2 м/с')

def main():
    rclpy.init()
    node = DriveForward()
    rclpy.spin(node)
    node.destroy_node()
    rclpy.shutdown()

if __name__ == '__main__':
    main()

Разбор по строкам

  • create_publisher(Twist, '/cmd_vel', 10) — создаёт издателя: тип сообщения, имя топика и глубина очереди (QoS depth) 10. Очередь буферизует сообщения, если подписчик не успевает.
  • create_timer(0.5, self.tick) — просит executor вызывать tick дважды в секунду. Это правильный способ периодической работы — без time.sleep, который заблокировал бы узел.
  • msg.linear.x = 0.2 — заполняем поле скорости. Единицы — метры в секунду.
  • self.pub.publish(msg) — отправляем сообщение в топик.

Как проверить

Запустив узел, в другом терминале смотрим, что он реально шлёт:

ros2 topic echo /cmd_vel

Вывод:

linear:
  x: 0.2
  y: 0.0
  z: 0.0
angular:
  x: 0.0
  y: 0.0
  z: 0.0
---

Как работает под капотом

Таймер — это событие внутри executor. Каждые 0.5 с executor вызывает tick. Метод publish не отправляет данные мгновенно по сети сам: он передаёт сообщение в очередь издателя DDS, а DDS уже доставляет его всем подписчикам топика согласно QoS. Поэтому publish почти не блокирует узел — тяжёлую работу по доставке берёт на себя middleware.

Частые ошибки

  • Использовать time.sleep в цикле вместо таймера. Это блокирует executor, и узел перестаёт реагировать на остальное.
  • Создавать издателя внутри tick. Издателя создают один раз в __init__, иначе будут утечки и хаос в discovery.
  • Забыть импортировать тип сообщения. from geometry_msgs.msg import Twist обязателен.

Итоги

  • Издателя создают методом create_publisher один раз в __init__.
  • Периодическую публикацию делают через create_timer, а не time.sleep.
  • publish кладёт сообщение в очередь DDS, дальше доставкой занимается middleware.
  • Проверять издателя удобно командой ros2 topic echo.
Проверьте себя
1. Каким методом создают издателя в rclpy?
Acreate_subscription
Bcreate_publisher
Ccreate_service
Dcreate_node
2. Почему для периодической публикации используют create_timer, а не time.sleep?
Atime.sleep не существует в Python
Btime.sleep заблокировал бы executor и узел перестал бы реагировать
CТаймер быстрее процессора
DЭто одно и то же
3. Что реально делает publish(msg)?
AМгновенно сам рассылает данные по TCP
BПередаёт сообщение в очередь издателя DDS, а доставкой занимается middleware
CСоздаёт новый топик
DОстанавливает узел