Аннотация
В данной работе рассматривает пример создания симуляционной модели четырёхколёсной мобильной платформы с рулевым управления по типу Аккреманна, с использованием фреймворка ROS, контроллер написан на языке Python. В качестве среды симуляции выбрана Gazebo. Приведены примеры описания архитектуры робота, посредством формата urdf, разработан собственный контроллер для управления колёсами модели, а также показан общий подход к проектированию симуляционных моделей робототехнических систем.


Источники
Исходный код:
Полезные статьи:
Полезные видео:
Предварительные требования
Установленный
ROS(ROS Noetic);Созданный
ROS;Python 3.x.
Я использую технологию контейнеризации Docker (и вам рекомендую, пример использования), устраняет необходимость в развёртывании виртуальной среды (Ubuntu). Для воспроизведения графического интерфейса (Rviz, Gazebo) использую VcXsrv Windows X Server.
Перед запуском контейнера вам необходимо настроить и запустить X server (VcXsrv). Использование VcXsrv - это простой подход к подключению графического интерфейса в контейнере Docker и локальной машине. Вы можете узнать больше здесь.
Подробная настройка поддержки ROS GUI. Если вам нужны дополнительные настройки, прочитайте здесь.
После запуска можно воспроизводить графический интерфейс из контейнера. Применение: при запуске VcXsrv рекомендую переключить язык на английский, иначе возникнут проблемы с заполнением полей в RViz.
Создание пакета ROS
Здесь я привёл, как выглядит мой пакет ROS, подробно о построении пакетов ROS можно ознакомиться в официальной документации. Всё это необходимо для нормального запуска проекта.
cmake_minimum_required(VERSION 3.0.2) project(custom_ackermann_steering_controller_ros) ## Find catkin macros and libraries find_package(catkin REQUIRED COMPONENTS roscpp rospy std_msgs) catkin_package() include_directories( ${catkin_INCLUDE_DIRS}) catkin_install_python( PROGRAMS scripts/ackermann_steering_controller.py scripts/simple_control.py scripts/keyboard_teleop.py DESTINATION ${CATKIN_PACKAGE_BIN_DESTINATION})
<?xml version="1.0"?> <package format="2"> <name>custom_ackermann_steering_controller_ros</name> <version>0.0.1</version> <description>custom_ackermann_steering_controller_ros</description> <maintainer email="anton42@yandex.ru">Anton Pisarenko</maintainer> <license>MIT</license> <buildtool_depend>catkin</buildtool_depend> <exec_depend>roscpp</exec_depend> <exec_depend>rospy</exec_depend> <exec_depend>rviz</exec_depend> <exec_depend>std_msgs</exec_depend> <exec_depend>tf</exec_depend> <exec_depend>urdf</exec_depend> <exec_depend>xacro</exec_depend> <exec_depend>controller_manager</exec_depend> <exec_depend>gazebo_ros</exec_depend> <exec_depend>gazebo_ros_control</exec_depend> <exec_depend>joint_state_controller</exec_depend> <exec_depend>position_controllers</exec_depend> <exec_depend>velocity_controllers</exec_depend> <exec_depend>robot_state_publisher</exec_depend> <exec_depend>rqt_robot_steering</exec_depend> </package>
Введение в геометрию Аккерманна
Геометрия Аккерманна является фундаментальным принципом в проектировании систем управления для четырехколесных транспортных средств, обеспечивая оптимальный поворот путем корректировки углов поворота передних колес. Этот принцип особенно актуален для мобильных роботов с рулевым управлением, где необходимо достичь эффективного, прогнозируемого и безопасного поворота.
Основы геометрии Аккерманна
Геометрия Аккерманна основана на идее, что все колеса должны быть ориентированы к центру поворотного круга во время поворота. Это уменьшает проскальзывание и износ колес, улучшая управляемость. Для достижения этого, передние колеса поворачиваются под разными углами. В идеале линии, проведенные через оси вращения всех колес, должны пересекаться в одной точке на оси задних колес (изображение для ознакомления, взято из Wikipedia.

Ключевые элементы
База колес (Wheelbase, W): Расстояние между центрами передней и задней осей.
Ширина колеи (Track width, T): Расстояние между левым и правым колесами на одной оси.
Радиус поворота (Turning Radius, R): Радиус круга, который описывает центр транспортного средства при повороте на максимальный угол.
Математическое выражение
Геометрия Аккерманна требует, чтобы углы поворота передних колес ( для левого и
для правого) были настроены так, чтобы выполнялось следующее условие:
где:
- радиус поворотного круга транспортного средства,
- расстояние между осями ведущих колёс и ведомых колёс,
- ширина между центрами колёс на одной оси,
- углы поворота для левого и правого передних колес соответственно.
В робототехнике
В контексте мобильных роботов геометрия Аккерманна позволяет точно определить углы поворота передних колес для заданного радиуса поворота. Это особенно важно при проектировании алгоритмов управления, где необходимо достигнуть плавного и точного поведения робота при маневрировании.
Создание имитационной URDF модели робота (Xacro)
Создание любой имитационной модели в ROS начинается с проектировании структуры, описывающей конструкцию робота в формате urdf: звенья, соединения, их массо-инерционные характеристики, другие физические свойства, а также трансмиссия.
Проектирование URDF (Unified Robot Description Format) файла для робота с системой управления Ackermann может быть довольно сложной задачей, поскольку требует учёта множества деталей, включая физические размеры, веса, и расположение компонентов робота. Для простоты мы спроектируем небольшого робота на четырёх колёсах, где передние колёса управляются согласно принципу управления Аккерманна. Построенную модель можно дальше расширять и модифицировать в соответствии с конкретными требованиями проекта.
Парадигма проектирования роботов, представленная в ROS, рекомендует описывать роботов в формате urdf, однако для описания некоторых сложных конфигураций можно использовать sdf, очень похожий формат, с некоторыми отличиями. Принцип формирования конечного файла: xacro -> urdf -> sdf. Таким образом в конченом итоге ros работает с sdf файлом. Для ознакомления с доступными тегами при описании файла можно обратиться к документации. В нашем случае мы остановимся на urdf(xacro).
Проектируемая конструкция робота URDF должна включать в себя следующие элементы:
Соединения для поворота колёс: Для передних колёс, которые поворачиваются для управления направлением, должны быть определены соединения типа
revolute, позволяющие вращение вокруг вертикальной оси.Суставы для вращения колёс: Все колёса (и передние, и задние) должны иметь соединения, позволяющие им вращаться для имитации движения, тип
continuous.Трансмиссии (Transmissions): В
URDFнеобходимо описать передачи, соединяющие каждое соединение с соответствующим контроллером вros-controls. Это включает в себя определение типа актуатора (например, электродвигатель) и метода управления (например, по скорости, позиции или усилию).
Для увеличения масштабируемости и гибкости проекта выполним небольшую декомпозицию структуры робота, а именно создадим несколько файлов в директории urdf:
main.xacro- основной файл, который описывает робота, подключает все другие файлы описания (xacro);common_macro.xacro- файл который содержит часто используемы фрагменты кода, глобальные перченные, применимые для других файлов;corpus.xacro- файл, описывающий корпус.wheel.xacro- файл описывающий колесо.
Робота описываем в формате xacro, так как это даёт нам больше возможностей и масштабируемость кода. Файлы располагаем в директории urdf корневого проекта чтобы любой проектировщик понял, что там находится описание робота.
Такой принцип декомпозиции был принят из соображения: каждый уникальный элемент структуры должен быть описан отдельно от других, непосредственно сборка также описана отдельным файлом (как при проектировании узлов - детали, сборочные единицы).
Для разрабатываемой структуры робота примем простые формы геометрии: в качестве корпуса - параллелепипед, в качестве колёс - цилиндры. В дальнейшем можно заменить простые формы на более детализированные модели в формате stl или dae, если это необходимо
<?xml version="1.0"?> <robot name="common_macro" xmlns:xacro="http://www.ros.org/wiki/xacro"> <!-- *********************************************************************** --> <!-- Константы --> <xacro:property name="PI" value="3.14"/> <!-- *********************************************************************** --> <!-- Инерция для цилиндра --> <xacro:macro name="cylinder_inertia" params ="m r h"> <inertial> <mass value="${m}"/> <inertia ixx="${m*(3*r*r+h*h)/12}" ixy="0.0" ixz="0.0" iyy="${m*(3*r*r+h*h)/12}" iyz="0.0" izz="${m*r*r/2}"/> </inertial> </xacro:macro> <!-- *********************************************************************** --> <!-- Базовая матрица инерции --> <xacro:macro name= "inertial_matrix" params="mass"> <inertial> <mass value="${mass}"/> <inertia ixx="1.0" ixy="0.0" iyy="1.0" iyz="0.0" izz="1.0" ixz="0.0"/> </inertial> </xacro:macro>
<?xml version="1.0"?> <robot name="corpus" xmlns:xacro="http://www.ros.org/wiki/xacro"> <xacro:include filename="$(find custom_ackermann_steering_controller_ros)/urdf/common_macro.xacro" /> <!-- *********************************************************************** --> <xacro:macro name="corpus" params=" link_name length width height mass"> <link name="${link_name}"> <visual> <origin rpy= "0 0 0" xyz= "0 0 0"/> <geometry> <box size="${length} ${width} ${height}"/> </geometry> <material name="Blue"> <color rgba="0.0 0.0 1.0 1.0"/> </material> </visual> <collision> <origin rpy= "0 0 0" xyz= "0 0 0"/> <geometry> <box size="${length} ${width} ${height}"/> </geometry> </collision> <xacro:inertial_matrix mass="${mass}"/> </link> <gazebo reference ="${link_name}"> <material>Gazebo/Blue</material> </gazebo> </xacro:macro> </robot>
<?xml version="1.0"?> <robot name="wheel" xmlns:xacro="http://www.ros.org/wiki/xacro"> <xacro:include filename="$(find custom_ackermann_steering_controller_ros)/urdf/common_macro.xacro" /> <!-- *********************************************************************** --> <xacro:macro name="wheel" params=" link_name radius length mass"> <link name="${link_name}"> <visual> <origin rpy="${PI/2} 0 0" xyz="0 0 0"/> <geometry> <cylinder length="${length}" radius="${radius}"/> </geometry> <material name="Black"> <color rgba="0.0 0.0 0.0 0.9"/> </material> </visual> <collision> <origin rpy="${PI/2} 0 0" xyz="0 0 0"/> <geometry> <cylinder length="${length}" radius="${radius}"/> </geometry> </collision> <xacro:cylinder_inertia m="${mass}" r="${radius}" h="${length}"/> </link> <gazebo reference ="${link_name}"> <material>Gazebo/Black</material> </gazebo> </xacro:macro> </robot>
<?xml version="1.0"?> <robot name="ackermann_robot" xmlns:xacro="http://www.ros.org/wiki/xacro"> <!-- *********************************************************************** --> <!-- Include xacro--> <xacro:include filename="$(find custom_ackermann_steering_controller_ros)/urdf/common_macro.xacro" /> <xacro:include filename="$(find custom_ackermann_steering_controller_ros)/urdf/corpus.xacro" /> <xacro:include filename="$(find custom_ackermann_steering_controller_ros)/urdf/wheel.xacro" /> <!-- *********************************************************************** --> <!-- Переменные --> <xacro:property name="PI" value="3.14"/> <xacro:property name="corpus_len" value="1.0"/> <xacro:property name="corpus_width" value="0.5"/> <xacro:property name="corpus_height" value="0.2"/> <xacro:property name="corpus_mass" value="5"/> <xacro:property name="wheel_radius" value="0.15"/> <xacro:property name="wheel_width" value="0.08"/> <xacro:property name="wheel_mass" value="0.5"/> <xacro:property name="angle_wheel_limit" value="${PI/4}"/> <!-- *********************************************************************** --> <xacro:corpus link_name="base_link" length="${corpus_len}" width="${corpus_width}" height="${corpus_height}" mass="${corpus_mass}" /> <!-- *********************************************************************** --> <xacro:wheel link_name="front_left_wheel" radius="${wheel_radius}" length="${wheel_width}" mass="${wheel_mass}"/> <xacro:wheel link_name="front_right_wheel" radius="${wheel_radius}" length="${wheel_width}" mass="${wheel_mass}"/> <xacro:wheel link_name="rear_left_wheel" radius="${wheel_radius}" length="${wheel_width}" mass="${wheel_mass}"/> <xacro:wheel link_name="rear_right_wheel" radius="${wheel_radius}" length="${wheel_width}" mass="${wheel_mass}"/> <!-- Суставы для соединения передних колес с корпусом --> <joint name="front_left_wheel_joint" type="revolute"> <parent link="base_link"/> <child link="front_left_wheel"/> <origin xyz="0.25 0.3 0" rpy="0 0 0"/> <axis xyz="0 0 1"/> <limit lower="${-angle_wheel_limit}" upper="${angle_wheel_limit}" effort="1000" velocity="1"/> </joint> <joint name="front_right_wheel_joint" type="revolute"> <parent link="base_link"/> <child link="front_right_wheel"/> <origin xyz="0.25 -0.3 0" rpy="0 0 0"/> <axis xyz="0 0 1"/> <limit lower="${-angle_wheel_limit}" upper="${angle_wheel_limit}" effort="1000" velocity="1"/> </joint> <!-- Суставы для соединения задних колес с корпусом (без поворота) --> <joint name="rear_left_wheel_joint" type="continuous"> <parent link="base_link"/> <child link="rear_left_wheel"/> <origin xyz="-0.25 0.3 0" rpy="0 0 0"/> <axis xyz="0 1 0"/> </joint> <joint name="rear_right_wheel_joint" type="continuous"> <parent link="base_link"/> <child link="rear_right_wheel"/> <origin xyz="-0.25 -0.3 0" rpy="0 0 0"/> <axis xyz="0 1 0"/> </joint> </robot>
Здесь мы описали звенья и соединения (взаимное расположение звеньев). При этом мы использовали переменные, это позволяет гибко и быстро выполнять конфигурирование проекта.
Добавим трансмиссию для соединений. Трансмиссия свяжет соединение с контроллером через определённый интерфейс.
Чтобы добавить трансмиссии для каждого колеса в urdf файл робота, необходимо расширить определение робота, включив в него элементы <transmission>. Эти элементы связывают суставы (<joint>) с актуаторами через интерфейсы hardware_interface, позволяя ros_control управлять физическими или симуляционными актуаторами.
Эти блоки <transmission> добавляются в конец urdf (xacro) файла (в нашем случае xacro), после определений всех суставов (<joint>) и перед закрывающим тегом </robot>. Они указывают, что для каждого колеса используется своя трансмиссия, связывающая суставы с актуаторами через определенные интерфейсы. Для передних управляемых колес используется PositionJointInterface, что подразумевает управление позицией (углом поворота), а для задних ведущих колес — VelocityJointInterface, управляющий скоростью вращения колес.
Вынесем описание трансмиссий в common_macro.xacro. Так мы уменьшим количество кода и увеличим читаемость.
Расширим common_macro.xacro:
<!-- *********************************************************************** --> <!-- Трансмиссия поворота колёс --> <xacro:macro name="Front_transmission" params="joint_name"> <transmission name="${joint_name}_transmission"> <type>transmission_interface/SimpleTransmission</type> <joint name="${joint_name}"> <hardwareInterface>hardware_interface/PositionJointInterface</hardwareInterface> </joint> <actuator name="${joint_name}_actuator"> <hardwareInterface>hardware_interface/PositionJointInterface</hardwareInterface> <mechanicalReduction>1</mechanicalReduction> </actuator> </transmission> </xacro:macro> <!-- *********************************************************************** --> <!-- Трансмиссия вращения колес --> <xacro:macro name="Back_transmission" params="joint_name"> <transmission name="${joint_name}_transmission"> <type>transmission_interface/SimpleTransmission</type> <joint name="${joint_name}"> <hardwareInterface>hardware_interface/VelocityJointInterface</hardwareInterface> </joint> <actuator name="${joint_name}_actuator"> <hardwareInterface>hardware_interface/VelocityJointInterface</hardwareInterface> <mechanicalReduction>1</mechanicalReduction> </actuator> </transmission> </xacro:macro>
Расширим main.xacro:
<!-- Трансмиссия для переднего левого управляемого колеса --> <xacro:Front_transmission joint_name="front_left_wheel_joint"/> <!-- Трансмиссия для переднего правого управляемого колеса --> <xacro:Front_transmission joint_name="front_right_wheel_joint"/> <!-- Трансмиссия для заднего левого ведущего колеса --> <xacro:Back_transmission joint_name="rear_left_wheel_joint"/> <!-- Трансмиссия для заднего правого ведущего колеса --> <xacro:Back_transmission joint_name="rear_right_wheel_joint"/>
После добавления всех необходимых суставов и передач, URDF модель готова к использованию с ros-controls для реализации механизмов управления. ��щё один важным элементом является добавление в описание робота информацию для gazebo (gazebo_ros_control). Расширим файл main.xacro:
<gazebo> <plugin name="gazebo_ros_control" filename="libgazebo_ros_control.so"> <robotNamespace>/</robotNamespace> </plugin> </gazebo>

Интеграция с ros_control для Gazebo
После добавления соединений и трансмиссий в urdf (в нашем случае xacro), необходимо настроить параметры для ros_control в YAML конфигурационном файле. Чтобы настроить ros_control, нужно создать конфигурационный файл YAML (обычно его располагают в директории config), который является стандартом для проектов с использованием ROS. Конфигурационны файл определяет контроллеры для каждого колеса, а также содержит переменные, необходимые для расчётов, таких как: расстояние между передним и задним мостом (wheel_base), расстояние между центрами колёс на одной оси (wheel_track), наименования "топиков" для подписки и публикации и другие настраиваемые параметры. Можно туда же включить любые настраиваемые параметры, что увеличит гибкость конфигурации.
Создаём файл
ackermann_steering_config.yaml в директории `config.
ackermann_steering_controller: # Параметры ros_control joint_state_controller: type: joint_state_controller/JointStateController publish_rate: 50 wheel_base: 0.5 # Расстояние между передним и задним мостом wheel_track: 0.5 # Расстояние между колёсами на оси max_steering_angle: 0.7854 # Максимальный угол поворота колёс в радианах cmd_vel_topic: "/cmd_vel" # Топик для подписки на команды управления steering_angle_topic: "/steering_angle" # Топик для публикации угла поворота drive_velocity_topic: "/drive_velocity" # Контроллеры для управляемых передних колес front_left_wheel_position_controller: type: position_controllers/JointPositionController joint: front_left_wheel_joint front_right_wheel_position_controller: type: position_controllers/JointPositionController joint: front_right_wheel_joint # Контроллеры для ведущих задних колес rear_left_wheel_velocity_controller: type: velocity_controllers/JointVelocityController joint: rear_left_wheel_joint rear_right_wheel_velocity_controller: type: velocity_controllers/JointVelocityController joint: rear_right_wheel_joint
Этот конфигурационный файл задает контроллер состояния суставов (joint_state_controller), который публикует состояние всех суставов робота. Также определены контроллеры для каждого из передних и задних колес. Для передних колес используются контроллеры позиции (JointPositionController), а для задних — контроллеры скорости (JointVelocityController), что соответствует их функциям в системе под рулевым управлением типа Аккерманна.
Примечание, ошибка связанная с `pid` контроллера для симуляции в `Gezebo` не существенна. Если вы используете `pid` контролеры почитайте официальную [документацию](https://wiki.ros.org/pid).
[ERROR] [1710926628.173440521]: No p gain specified for pid. Namespace: /gazebo_ros_control/pid_gains/front_left_wheel_joint
Чтобы использовать этот конфигурационный файл при запуске вашего ROS-нода, его необходимо загрузить через roslaunch, но этот шаг мы опишем немного позже
Такой подход облегчает управление конфигурациями и позволяет легко адаптировать ПО под различные условия эксплуатации без необходимости изменения исходного кода.
Реализация контроллера рулевого управления Аккерманна
Для реализации рулевого управления по типу Аккерманна с использованием ROS и в интеграции с ros-controls, создадим собственный контроллер: определим взаимосвязи, а также определим логику управления колёсами. Для этого создадим скрипт на python. Определим новый класс, в котором инициализируем ROS ноду. Обязательно необходимо выполнить чтение параметров из системы, которые мы определяли на предыдущем шаге. Далее нашу ноду подпишем на топик управления в данном случае /cmd_vel и с некоторой периодичностью будем считывать с неё сообщения. Имея топик команды, реализуем логику расчёта геометрии Аккерманна, а именно: определим функцию расчёта улов поворота колёс, функцию расчёт скорости приводных колёс.
Далее приведён файл контроллера (файлы скриптов python располагаем в директории /scripts), я постарался сделать максимально понятно. Не забудьте добавить скрипт в файл сборки CmakeList.txt если вы делаете проект самостоятельно.
ackermann_steering_controller.py:
#!/usr/bin/env python # -*- coding: utf-8 -*- import rospy from geometry_msgs.msg import Twist from std_msgs.msg import Float64 import math class AckermannSteeringController: def __init__(self): self.name = '/ackermann_steering_controller' rospy.init_node('ackermann_steering_controller', anonymous=True) # Загрузка параметров из конфигурационного файла self.wheel_base = rospy.get_param(self.name+'/wheel_base', 1.0) self.cmd_vel_topic = rospy.get_param(self.name+'/cmd_vel_topic', '/cmd_vel') self.drive_velocity_topic = rospy.get_param(self.name+'/drive_velocity_topic', '/drive_velocity') self.wheel_track = rospy.get_param(self.name+'/wheel_track', 0.5) self.max_steering_angle = rospy.get_param(self.name+'/max_steering_angle', 0.7854) # Подписка на топик с командами управления rospy.Subscriber(self.cmd_vel_topic, Twist, self.cmd_vel_callback) # Publishers для отправки команд на контроллеры колес self.front_left_wheel_pub = rospy.Publisher(self.name+'/front_left_wheel_position_controller/command', Float64, queue_size=1) self.front_right_wheel_pub = rospy.Publisher(self.name+'/front_right_wheel_position_controller/command', Float64, queue_size=1) self.rear_left_wheel_pub = rospy.Publisher(self.name+'/rear_left_wheel_velocity_controller/command', Float64, queue_size=1) self.rear_right_wheel_pub = rospy.Publisher(self.name+'/rear_right_wheel_velocity_controller/command', Float64, queue_size=1) def cmd_vel_callback(self, data): # Получение линейной скорости и угловой скорости linear_velocity = data.linear.x angular_velocity = data.angular.z # Вычисление радиуса поворота из угловой и линейной скорости if angular_velocity == 0 or linear_velocity == 0: radius = float('inf') else: radius = linear_velocity / angular_velocity # Вычисление углов поворота для передних колес left_steering_angle, right_steering_angle = self.calculate_steering_angles(radius) # Адаптация скорости задних колес left_rear_wheel_velocity, right_rear_wheel_velocity = self.calculate_wheel_velocities(radius, linear_velocity) # Публикация углов поворота и скоростей self.front_left_wheel_pub.publish(Float64(left_steering_angle)) self.front_right_wheel_pub.publish(Float64(right_steering_angle)) self.rear_left_wheel_pub.publish(Float64(left_rear_wheel_velocity)) self.rear_right_wheel_pub.publish(Float64(right_rear_wheel_velocity)) # Расчёт углов поворота def calculate_steering_angles(self, radius): if radius == float('inf'): return 0.0, 0.0 left_angle = math.atan(self.wheel_base / (radius + self.wheel_track / 2)) right_angle = math.atan(self.wheel_base / (radius - self.wheel_track / 2)) return left_angle, right_angle # Расчёт скорости вращения колёс def calculate_wheel_velocities(self, radius, linear_velocity): if radius == float('inf'): return linear_velocity, linear_velocity left_velocity = linear_velocity * (radius - self.wheel_track / 2) / radius right_velocity = linear_velocity * (radius + self.wheel_track / 2) / radius return left_velocity, right_velocity if __name__ == '__main__': try: controller = AckermannSteeringController() rospy.spin() except rospy.ROSInterruptException: pass
Примечание: не забудьте сделать скрипт исполняемым, если будете запускать скрипт вручную, несовместно с `rosrun`:
chmod +x scripts/ackermann_steering_controller.py
В этом контроллере учитываются углы поворота для каждого из передних колёс, которые вычисляются с учетом радиуса поворота и ширины колеи. Кроме того, для имитации дифференциального поворота, скорости задних колес адаптируются в зависимости от угла поворота, обеспечивая тем самым более реалистичное управление.
Этот контроллер теперь будет подписан на топик /cmd_vel, ожидая сообщения типа Twist, которые содержат информацию о линейной и угловой скорости. На основе этих данных контроллер вычислит угол поворота для рулевого управления и опубликует их в соответствующие топики.
Реализация системы управления роботом
Чтобы заставить модель робота двигаться в симуляторе, использующем ROS (например, Gazebo), необходимо отправить соответствующие команды на топик (в нашем случае /cmd_vel), который управляет движением робота. На вход принимает сообщения типа geometry_msgs/Twist. Сообщение типа Twist позволяет задать линейную и угловую скорость по направлениям осей. Эффективность и точность выполнения таких команд зависит от характеристик вашего робота и настроек симулятора. Также важно учитывать, что постоянная отправка команд скорости без остановки может привести к тому, что робот будет продолжать движение до тех пор, пока не получит команду остановиться или изменить скорость.
Для реализации управления роботом через клавиши клавиатуры в ROS, можно использовать пакет teleop_twist_keyboard, который позволяет отправлять сообщения geometry_msgs/Twist в топик /cmd_vel, управляя таким образом линейной и угловой скоростью робота. Однако мы реализуем собственную реализацию управления через клавиатуру (стелки вверх, вниз и т.д.), напишем простой Python скрипт, используя библиотеку curses для считывания нажатий клавиш без необходимости нажимать Enter.
#!/usr/bin/env python # -*- coding: utf-8 -*- import rospy from geometry_msgs.msg import Twist import curses # Функция для обработки нажатий клавиш def keypress(stdscr): # Инициализация ROS rospy.init_node('keyboard_teleop', anonymous=True) pub = rospy.Publisher('/cmd_vel', Twist, queue_size=1) rate = rospy.Rate(10) # 10hz twist = Twist() # Отключаем буферизацию ввода, делаем getch неблокирующим stdscr.nodelay(True) stdscr.clear() while not rospy.is_shutdown(): try: key = stdscr.getch() # Получаем код нажатой клавиши stdscr.refresh() # Выбор действия в зависимости от нажатой клавиши if key == curses.KEY_UP: twist.linear.x += 0.1 elif key == curses.KEY_DOWN: twist.linear.x -= 0.1 elif key == curses.KEY_LEFT: twist.angular.z += 0.1 elif key == curses.KEY_RIGHT: twist.angular.z -= 0.1 elif key == ord('s'): twist.linear.x = 0 twist.angular.z = 0 elif key == ord('q'): break pub.publish(twist) rate.sleep() except Exception as e: # Обработка исключений (например, при закрытии окна) break # Использование curses для обработки нажатий клавиш curses.wrapper(keypress)
Этот скрипт инициализирует ROS-узел, создает публикатор для топика /cmd_vel, а также использует библиотеку curses для считывания нажатий клавиш и публикует сообщения Twist в топик /cmd_vel. Используйте стрелки для управления скоростью и направлением движения, 's' для остановки и 'q' для выхода.
Чтобы запустить скрипт, сохраните его в файл, например, keyboard_teleop.py, сделайте файл исполняемым (chmod +x keyboard_teleop.py) и запустите его, используя rosrun или можно добавить в launch файл пакета. На забудьте добавить скрипт в CmakeList.txt.
Формирование файла запуска (launch)
Завершающим этапом является подготовка файла запуска (.launch). На самом деле файл запуска формируется на всех стадиях написания проекта, постоянно расширяется, изменяется и корректируется.
Файл запуска (launch-файл) в ROSиграет важную роль в управлении запуском и конфигурацией узлов, параметров, пространств имен и других элементов системы. Эти файлы написаны по правилам XML, но с расширением .launch. Они предоставляют механизм для автоматизации сложного процесса запуска множества узлов и настройки их взаимодействия.
Централизованное управление:
Организация запуска:
Launchфайлы позволяют запускать множество узлов одной командой, упрощая и централизуя процесс запуска комплексных систем, включая различные драйверы, алгоритмы и инструменты визуализации.Конфигурация - через эти файлы можно настраивать параметры узлов, задавая значения параметров, которые будут использоваться узлами во время их работы.
Гибкость и масштабируемость
Параметризация - позволяет задавать параметры запуска, делая систему гибкой и легко адаптируемой под разные условия и конфигурации.
Пространства имен - поддерживает использование пространств имен для изоляции узлов, что облегчает управление большими и сложными системами.
Упрощение разработки и тестирования:
Многократное использование -
launchфайлы могут быть организованы таким образом, чтобы обеспечивать переиспользование в различных частях проекта или даже между проектами.Тестирование - облегчает процесс тестирования, позволяя быстро запускать и переконфигурировать систему под разные сценарии и условия тестирования.
Основные элементы launch файла:
<node>- элемент для запуска узла. Задает имя пакета, тип (имя исполняемого файла), имя узла и дополнительные параметры.<param>- устанавливает параметр на сервере параметров ROS. Может задавать значения напрямую или через файл.<rosparam>- загружает параметры в сервер параметров ROS из YAML-файла, что позволяет управлять более сложными структурами данных.<group>- группирует узлы и параметры, позволяя применять к ним общие настройки, например, пространство имен.<include>- включает другой launch файл, что обеспечивает модульность и возможность повторного использования кода.<arg>- определяет аргументы, которые можно передать в launch файл, делая его конфигурацию еще более гибкой.
Использование launch файлов в ROS значительно упрощает процесс разработки, запуска и управления робототехническими системами, позволяя сосредоточиться на решении задач, а не на рутинном управлении запуском компонентов системы.
Для запуска этих контроллеров с ros_control, добавьте соответствующий раздел в файл запуска (.launch), который загружает эту конфигурацию и инициализирует controller_manager с этими контроллерами.
Для загрузки конфигурации контроллеров, определенной в YAML файле, и инициализации controller_manager с этими контроллерами, вы можете создать файл запуска .launch. Этот файл будет включать в себя несколько ключевых элементов: загрузку описания ��обота в параметр robot_description, загрузку конфигурации контроллеров из YAML файла, запуск robot_state_publisher для публикации состояния суставов и трансформаций, а также запуск controller_manager.
Итак приступим. Снова прибегнем к декомпозиции файла запуска, разбив его на несколько локальных файлов, а именно: основной файл запуск - ackermann_run.launch, он будет подключать другие необходимые файлы запуска, а также может содержать переэнные аргументы; файл запуска инструмента Rviz - rviz.launch, для анализа конструкции робота, проверки звеньев, а также взаимного расположения звеньев (удобно в процессе проектирования периодически проверять конструкцию, обнаружить ошибку на ранних этапах проектирования); файл запуска симуляции, в нашем случае Gazebo - gazebo.launch, который содержит информацию о симулянтом мире, о необходимых подключаемых расширениях (controller_manager) и другое.
Начнём с формирования файла для rviz.
<?xml version="1.0" encoding="UTF-8" ?> <launch> <!-- *********************************************************************** --> <arg name="model" default="$(find custom_ackermann_steering_controller_ros)/urdf/main.xacro"/> <!-- *********************************************************************** --> <!-- Загрузка URDF модели --> <param name="robot_description" command="$(find xacro)/xacro $(arg model)"/> <!-- Запуск rviz --> <node name="rviz" pkg="rviz" type="rviz"/> <!-- *********************************************************************** --> <!-- Запуск сервисов --> <node name="robot_state_publisher" pkg="robot_state_publisher" type="robot_state_publisher"> </node> <!-- <node name="joint_state_publisher_gui" pkg="joint_state_publisher_gui" type="joint_state_publisher_gui" > </node> --> <node name="joint_state_publisher" pkg="joint_state_publisher" type="joint_state_publisher" > </node> </launch>
Сервис robot_state_publisher использует URDF, указанный параметром robot_description, и положения суставов из раздела joint_states для вычисления прямой кинематики робота.
Сервис joint_state_publisher и joint_state_publisher_gui считывает параметр robot_description с сервера параметров, находит все нефиксированные соединения и публикует сообщение JointState со всеми определенными соединениями. В свою очередь joint_state_publisher_gui предоставляет графический интерфейс для изменения параметров соединения (вращения колёсами к примеру). Запускать нужно что-то одно, если запустить оба сервиса, то они будут между собой конфликтовать.
Примечание: `joint_state_publisher_gui` не работает должным образом вместе с gazebo (нет возможности повлиять на соединения).
При первом запуске rviz после добавления робота в рабочую область рекомендуется сохранить конфигурацию запуска rviz в директорию rviz и изменить файл запуска:
<arg name="rvizconfig" default="$(find custom_ackermann_steering_controller_ros)/rviz/base.rviz"/> <node name="rviz" pkg="rviz" type="rviz" args="-d $(arg rvizconfig)"/>
Тем самым, при последующих запусках нам не придётся всё заново открывать и настраивать.
Следующим файлом будет являться gazebo.launch.
<?xml version="1.0" encoding="UTF-8" ?> <launch> <!-- *********************************************************************** --> <!-- Аргументы --> <arg name="paused" default="true"/> <arg name="use_sim_time" default="true"/> <arg name="gui" default="true"/> <arg name="headless" default="false"/> <arg name="debug" default="false"/> <arg name="model" default="$(find custom_ackermann_steering_controller_ros)/urdf/main.xacro"/> <arg name="world" default="$(find custom_ackermann_steering_controller_ros)/world/empty_world.world" /> <arg name="ackermann_steering" default="$(find custom_ackermann_steering_controller_ros)/config/ackermann_steering_config.yaml"/> <!-- *********************************************************************** --> <!-- Загрузка параметров конфигурации YAML --> <rosparam file="$(arg ackermann_steering)" command="load"/> <!-- *********************************************************************** --> <!-- Загрузка мира для Gazebo empty_world.launch --> <include file="$(find gazebo_ros)/launch/empty_world.launch"> <arg name="debug" value="$(arg debug)" /> <arg name="gui" value="$(arg gui)" /> <arg name="paused" value="$(arg paused)" /> <arg name="headless" value="$(arg headless)" /> <arg name="use_sim_time" value="$(arg use_sim_time)" /> </include> <!-- *********************************************************************** --> <!-- Загрузка URDF модели --> <param name="robot_description" command="$(find xacro)/xacro $(arg model)"/> <!-- *********************************************************************** --> <!-- Размещение робота в симуляции --> <node name="urdf_spawner" pkg="gazebo_ros" type="spawn_model" respawn="false" output="screen" args="-x 0.0 -y 0.0 -z 0.5 -unpause -urdf -model name_robot -param robot_description"/> <!-- *********************************************************************** --> <!-- Загрузка менеджера контроллеров --> <node name="controller_manager" pkg="controller_manager" type="controller_manager" respawn="false" output="screen" args=" spawn /ackermann_steering_controller/joint_state_controller /ackermann_steering_controller/front_left_wheel_position_controller /ackermann_steering_controller/front_right_wheel_position_controller /ackermann_steering_controller/rear_left_wheel_velocity_controller /ackermann_steering_controller/rear_right_wheel_velocity_controller" /> </launch>
Подробнее про специфическую конфигурацию мира Gazebo можно прочитать здесь.
На что стоит обратить внимание - вызов сервиса менеджера пакетов, в который в качестве аргументов передаются параметры контроллеров, заданных нами ранее в файл конфигурации (yaml).
В завершении напишем основной файл запуска - ackermann_run.launch.
<?xml version="1.0" encoding="UTF-8" ?> <launch> <!-- *********************************************************************** --> <arg name="model" default="$(find custom_ackermann_steering_controller_ros)/urdf/main.xacro"/> <arg name="rvizconfig" default="$(find custom_ackermann_steering_controller_ros)/rviz/base.rviz"/> <arg name="ackermann_steering" default="$(find custom_ackermann_steering_controller_ros)/config/ackermann_steering_config.yaml"/> <!-- *********************************************************************** --> <!-- Запуск gazebo --> <include file="$(find custom_ackermann_steering_controller_ros)/launch/gazebo.launch"> <arg name="model" value="$(arg model)"/> </include> <!-- *********************************************************************** --> <!-- Запуск rviz --> <include file="$(find custom_ackermann_steering_controller_ros)/launch/rviz.launch"> <arg name="model" value="$(arg model)"/> <arg name="rvizconfig" value="$(arg rvizconfig)"/> </include> <!-- *********************************************************************** --> <!-- Запуск ноды контроллера Аккерманна --> <node pkg="custom_ackermann_steering_controller_ros" type="ackermann_steering_controller.py" name="ackermann_steering_controller" output="screen"/> </launch>
Здесь должно быть всё достато чнопонятно, мы вызываем написанные нами ранее файлы запуска rviz.launch и gazebo,launch, а также запускаем ноду контроллера рулевого управления Аккерманна.
Подведём итоги файлов запуска:
Запускает
Gazeboс пустым миром для симуляции.Загружает описание
URDFробота в параметры ROS, используя xacro для обработки файла.xacro.Запускает
robot_state_publisher, который читает описание робота из параметровROSи публикует состояние суставов и трансформации для использования в других нодах, таких как визуализация вRViz.Запускает ноду контроллера, реализованную в предыдущих шагах.
Опционально запускает
RVizс предварительно настроенным конфигурационным файлом для визуализации робота и его окружения.Загружает пользовательскую конфигурацию для ноды контроллера из
YAMLфайла.
Симуляция
Видео YouTube
rqt_graph:

rviz+gazebo:

Реализация с добавленим текстуры карты, описание как это можно сделать здесь:

Заключение
Данная реализация имитационной модели не идеальная и является примером реализации подобной методологии. В дальнейшем модель можно усложнить, интегрировать в неё другие модули (камеру, датчики расстояний и т.д.). В работе возможны ошибки и недочёты, буду рад вашей оценки со стороны.
