3.10.4 Пример патрульного бота с использованием деревьев поведения
Мы уже видели, как с помощью SMACH можно запрограммировать робота на патрулирование ряда путевых точек, одновременно контролируя уровень заряда его батареи и подзаряжая ее при необходимости. Давайте теперь посмотрим, как мы можем сделать то же самое с помощью пакета pi_trees.
Наша тестовая программа называется patrol_tree.py и находится в подкаталоге rbx2_tasks/nodes. Прежде чем взглянуть на код, давайте попробуем его использовать.
Начните с того, что возьмите имитацию робота TurtleBot, пустую карту и имитацию симулятора батареи:
Далее, запустите RViz с конфигурационным файлом nav_tasks.rviz:
Наконец, запустите сценарий программы patrol_tree.py:
Робот должен сделать две петли вокруг квадрата, останавливаясь, чтобы перезарядиться, когда это необходимо, а затем остановиться. Давайте теперь посмотрим на код.
Ссылка на источник: patrol_tree.py:
Давайте взглянем на ключевые строки сценария:
Мы начинаем с импорта библиотеки pi_trees_ros, которая, в свою очередь, импортирует основные классы pi_trees из библиотеки pi_trees_lib. Первый ключевой блок кода включает в себя создание навигационных задач, показанных ниже:
Здесь мы видим почти ту же процедуру, что и при использовании с SMACH, хотя теперь мы используем SimpleActionTask из библиотеки pi_trees вместо SimpleActionState из библиотеки SMACH.
Обратите внимание на параметр, называемый reset_after в аргументах функции SimpleActionTask. Мы устанавливаем этот параметр в значение «ЛОЖЬ» для задач move_base, присвоенных путевым точкам, но для задачи стыковки move_base мы устанавливаем его в значение «ИСТИНА». Напомним, что, когда поведение или задача в дереве поведения завершается успехом или неудачей, она сохраняет этот статус бесконечно долго, пока не будет сброшена. Это свойство "памяти" очень важно, потому что в каждом цикле выполнения мы считываем статус каждого узла в дереве, что позволяет нам постоянно проверять узлы состояния, статус которых мог измениться с момента последнего цикла. Однако, если робот только что успешно достиг путевой точки, мы хотим, чтобы этот статус был сохранен на следующем проходе через дерево, чтобы родительский узел продвинул последовательность к следующей путевой точке. С другой стороны, когда дело доходит до подзарядки, нам нужно сбросить навигационную задачу, как только робот состыкуется с док-станцией так, чтобы можно было выполнить этот процесс снова в следующий раз, когда батарея разрядится.
Как только мы создадим задачи навигации и стыковки, мы перейдем к построению остальной части дерева поведения. Порядок, в котором мы создаем узлы в командном файле, несколько гибок, поскольку именно иерархические отношения определяют фактическую структуру дерева. Если мы начнем с корня дерева, то наши первые узлы поведения будут выглядеть следующим образом:
Корневое поведение — это Последовательность, обозначенная надписью «ПОВЕДЕНИЕ», которая будет иметь две дочерние ветви; одна из них начинается с Селектора с надписью «СТОЯТЬ СПОКОЙНО», вторая ветвь с надписью «ЦИКЛ ПАТРУЛИРОВАНИЯ», которая использует декоратор циклов для циклического выполнения задачи патрулирования. Затем мы добавим две дочерние ветви к корневому узлу в порядке, определяющем их приоритет. В этом случае ветвь «СТОЯТЬ СПОКОЙНО» имеет более высокий приоритет, чем «ЦИКЛ ПАТРУЛИРОВАНИЯ».
Далее мы займемся остальными патрульными узлами. Сама последовательность патрулирования строится как итератор, называемый патрулем. Затем мы добавляем каждую задачу move_base в итератор. Наконец, мы добавляем весь патруль к задаче «ЦИКЛ ПАТРУЛИРОВАНИЯ».
Теперь перейдем к детализации ветви дерева «СТОЯТЬ СПОКОЙНО». Сначала мы определяем задачу «ПРОВЕРИТЬ БАТАРЕЮ» как MonitorTask в ROS теме battery_level, используя функцию обратного вызова self.check_battery (описано ниже). Далее мы определяем поведение «ЗАРЯДКА РОБОТА» как ServiceTask, который подключается к ROS сервису battery_simulator/set_battery_level и отправляет значение 100 для подзарядки имитационной батареи.
Затем мы строим задачу «ЗАРЯДИТЬСЯ» как Последовательность, дочерними задачами которой являются задачи «ОТПРАВИТЬСЯ К ДОК-СТАНЦИИ» и «ЗАРЯДКА РОБОТА». Обратите внимание на то, как мы использовали встроенный синтаксис, чтобы проиллюстрировать, как мы можем добавлять дочерние задачи в то время, когда мы создаем родительские. Аналогично, мы могли бы использовать три строчки кода:
Вы можете использовать любой синтаксис, который предпочитаете.
Мы завершаем ветвь дерева «СТОЯТЬ СПОКОЙНО», добавляя задачи «ПРОВЕРИТЬ БАТАРЕЮ» и «ЗАРЯДИТЬСЯ». Еще раз обратите внимание, что порядок очень важен, так как мы хотим сначала проверить батарею, чтобы понять, нужно ли нам ее заряжать.
Перед началом выполнения мы используем функцию print_tree() из библиотеки pi_trees для отображения представления дерева поведения на экране. Само дерево выполняется вызовом функции run() на корневом узле. Функция run() делает один проход через узлы дерева, поэтому нам нужно поместить ее в цикл.
В конце, мы имеем функцию обратного вызова для проверки батареи - check_battery()
Напомним, что эта функция была назначена задаче «ПРОВЕРИТЬ БАТАРЕЮ» в MonitorTask, которая отслеживает тему с уровнем батареи. Поэтому мы проверяем уровень заряда батареи по параметру low_battery_threshold. Если уровень ниже порогового значения, мы возвращаем статус задачи как «ОШИБКА». В противном случае мы возвращаем «УСПЕХ». Поскольку задача «ПРОВЕРИТЬ БАТАРЕЮ» является самой приоритетной задачей в селекторе «СТОЯТЬ СПОКОЙНО», то если она возвращает ошибку, селектор переходит к своей следующей подзадаче, которой является задача «ЗАРЯДИТЬСЯ».
Сценарий patrol_tree.py иллюстрирует важное свойство деревьев поведения, которое помогает отличить их от обычных иерархических машин состояний, таких как SMACH. Вы заметите, что после подзарядки робот продолжает свое патрулирование там, где он остановился, хотя нигде в сценарии мы явно не сохранили последнюю достигнутую точку пути. Помните, что в примере SMACH (patrol_smach_concurrence.py), мы должны были сохранить последнее состояние непосредственно перед подзарядкой, чтобы робот знал, где продолжить работу после зарядки. Деревья поведения по своей сути хранят свое состояние в силу свойства состояния каждого узла. В частности, если робот находится в движении к путевой точке, то навигационная задача, выполняющая работу по перемещению робота, имеет статус запущенной. Если после этого робот будет перенаправлен на док-станцию для подзарядки, то статус ранее активного навигационного статуса все еще будет работать. Это означает, что, когда робот полностью заряжен и задача «ПРОВЕРИТЬ БАТАРЕЮ» вернет «УСПЕХ», управление автоматически возвращается к запущенному навигационному узлу.
Last updated
Was this helpful?