Переключение задатчиков

Раздел описывает принципы переключения походок и анимаций.

Требования к систем переключения

На основе базовых требований к архитектуре выдвигаются следующие специфичные ограничения для подсистемы переключения походок:

  • Должна быть возможность переключения различных модулей-задатчиков движения (gait_*, animation_*). Однако способ взаимодействия с высшим уровнем не определен.

  • Список компонент-задатчиков неизвестен, требуется возможность удобного добавления новых задатчиков. (Отсутствие сводной таблицы задатчиков, общих списков ресурсов, нужных каждому).

  • Задатчик может быть запущен только в определенных условиях (позе). Должен быть механизм их проверки, по возможности, неотделимый от самого задатчика. Он нужен для достижения двух целей: 1) сказать высшему уровню, что переключение невозможно; 2) исключить переключение в некорректной позе.

  • Задатчики пассивны: они запускаются только по внешнему событию (команде верхнего уровня).

  • С каждым задатчиком ассоциирован список ресурсов. Любой ресурс должен быть в эксклюзивном использовании. Однако, по отношению к данному задатчику ресурсы можно разделить на 2 группы:

    • необходимые --- работа задатчика невозможна/бессмысленна без обладания этим ресурсом (пример: походка и ноги),
    • вспомогательные --- задатчик сохраняет работоспособность и без обладания этим ресурсом (пример: походка и голова/хвост).
  • Задатчик исполняется либо бесконечно долго (походка) и прерывается по внешнему событию, либо его исполнение завершается за конечное время (анимация некого действия).

  • Перехват вспомогательных ресурсов. Можно представить ситуацию, в которой один задатчик должен перехватывать ненадолго некоторый ресурс. Например, робот следит головой за объектом или сморит, куда идет (продолжительная работа задатчика). Запускается анимация "мотнуть головой", "зевок". После ее исполнения управление возвращается исходному задатчику.

Взаимодействие с высшим уровнем

В целом, интерфейс ожет быть индивидуальным для каждого задатчика.

Общая идея состоит в том, что верхний уровень в любой момент времени может активировать нужный ему задатчик и он гарантированно заработает или сообщит о невозможности запуска. Требования указывают, что задатчики с конечным временем исполнения должны реализовывать ROS Action Protocol. Это позволяет сразу же получить операции активации, деактивации, извещения о состоянии (не получилось, в работе, завершено).

Аналогичный подход можно использовать для задатчиков с продолжительным исполнением, однако возможны иные варианты. Здесь все зависит от характера входа задатчика. Например, модуль походки может получать целевую позицию в абсолютных координатах, либо желаемую скорость. В последнем случае actionlib не так удобен, т.к. скорость потенциально обновляется каждый цикл управления. Однако для передачи скорости можно ввести в интерфейс задатчика специальный порт.

Для осуществления проверки условий запуска без передачи управления задатчику следует ввести отдельную операцию.

Взаимодействие с подсистемой управления движения

  • Входные порты с разного типа: текущей поза робота (желаемая или измеренная), датчики касния, прочее.

  • Выходной порт с неполной желаемой позой робота в угловой СК (JointState) или декартовой СК (CartesianState).

  • Выходной порт с опорными коэффициентами (распределение веса SupportState). Для анимаций выходом почти всегда будет отсутсвие касания.

Реализация механизма переключения

Реализация механизма переключения включает две части: 1. Компонент-арбитр. 2. Клинетская часть (плагин, загружаемый в задатчик).

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

Плагины обладают единым внутренним инерфейсом (для задатчика), что позволяет использовать один задатчик с разными механизмами выделения ресурсов.

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

Способ представления ресурсов

При передачи сообщений используется список строк (vector<string>), этот тип поддерживает ROS и OROCOS. На уровне клинетов и арбитра можно использовать более эффективные сопсобы: set, map.

Базовая реализация

Базовая реализация основана на асинхронном обмене сообщений через порты:

  1. От задатчика к арбитру (ResourseRequest): список желаемых ресурсов и их приоритеты в разных сотояниях.
  2. От задатчика к арбитру (ResourseRequesterState): сотояние задатчика.
  3. От арбитра к задатчику (ResourseAssigment): отношение ресурсов-задатчик.

Логика работы клиентской части

Задатчик может быть в 3-х состояних: активн (operational), активен и запрашивает ресурсы (operational pending), неактивен и запрашивает ресурсы (pending), неактивен (nonoperationl).

  • nonoperationl -> pending происходит внешним воздействием (операция, сообщение) или по внутренней инициативе задатчика.

    По внешней команде компонент проверяет условия запуска. Если они не удовлетворены, то процедура не начинается, а высший уровень уведомляется о отказе (способ извещения может быть разный: результат операции, отдельным сообщением, механизм actionlib).

    Если условия запуска удовлетворены, то компонент переводится в состояние "pending", формируется запрос ResourceRequest. Указываются запрашиваемые ресурсы (протейшая реализация) и их приоритеты для сотояний "активен" и "pending" (реализация с приоритетами).

  • pending/operational pending -> operational/nonoperatonal происходит по получению сообщения ResourceAssigment с результатом обработки соответсвующего ResourceRequest. Сообщения до него должны игнорироваться.

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

  • operational -> nonoperatonal/operational проиходит в двух ситуациях:

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

    • По внутренненней инициативе или внешнему событию (запрос на деактивацию с высокого уровня).

    В обоих ситуациях изменение сотояния передается арбитру сообщением ResourceRequesterState. При этом арбитр должен получть информацию, в ответ на какое расперделение ResourceAssigment послано это сообщение.

  • operational -> operational pending, если запрос ресурса формируется в сотоянии "operational".

  • Пользовательский код задатчика, управляющий движенение, вызывается только в состоянии активен.

Логика работы арбитра

Арбитр хранит:

  • Номер раунда (число полученных сообщений ResourceRequest)

  • Состояния компонент:

    • состояние компонента: активен/ожидает активации/не активен
    • желаемый набор ресурсов и их приоритеты (по ResourseRequest)
    • номер последнего запроса ResourceRequest
  • Распределение ресурсов по компонентам

Арбитр обрабатывает поступающие сообщения асинхронно.

  1. Обработка запроса ресурсов. По событию ResourceRequest:

    • Обрабатывается пришедший ResourceRequest: обновляется состояние компонента (статус задатчика ставится в ожидании активации (pending), список желаемых ресурсов, приоритеты).
    • Происходит перераспределение ресурсов по приоритетам, компоненты ожидающие активации (pending) используют приоритеты состояния "pending", активные --- "operational", не активные не участвуют в перераспределении.
    • Формируется сообщение сообщения ResourceAssigment.

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

  2. Обработка извещения о деактивации и подтверждений продолжения работы с выденнным списком ресурсов. По событию ResourceRequesterState:

    • Если задатчик активен (active), то это означает, что он подтверждает набор выделенных ему ресурсов. Он удаляется из списка компонент, от которых ожидается ответ.
    • Если задатчик деактивируется, то повторяется процедура перераспределния ресурсов для оставшихся компонент. Снова формируются ResourceAssigment и соответвующие задатчики помещаются в список компонент, от которых ожидается подтверждение.

    Сообщения ResourceRequesterState отправленные до раунда, когда задатчик в последний раз посылал ResourceRequest игнорируюся. Таким образом, ResourceRequesterState должен содержать информацию, каким ResourceRequest он был вызван.

Замечание: Контроль отношений между ResourceRequest, ResourceAssigment и ResourceRequesterState при помощи информации о раунде необходим, чтобы избежать гонок при асинхронном взаимодействии. Ожидающий активации задачик должен реагировать только на сообщения арбитра, которые уже учитываюь его запрос. Аналогично, арбитр должен регировать на ResourceRequesterState, только если оно старше последнего запроса получеенного от задатчика.

Этого можно достичь следущим способом: * Запросы помечаются уникальным request_id, монотонно возрастающим с номером запроса (uint32старшая пара байт --- номер запроса, младшая --- хэш имени). Такая структура позволяет получать request_id на стороне клиента без централизации. Альтернатива --- использовать номер запроса к арбитру, требует использования операций для посылки ResourceRequest, чтобы этот номер вернуть. * ResourceAssigment содержит request_id последних запросов всех компонент, состояние которых отлично от "не активен". * Свой request_id ResourceAssigment используется для пометки ResourceRequesterState.

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

Замечание: Алгоритм не поддерживает ненадежную среду передачи сообщений. Для этого нужны timeout.

Замечание: Для передачи сообщенией ResourseRequest, ResourseList и ResourseRequesterState можно использовать одно сообщение, просто объединив их поля и не заполняя ненужные поля при передаче. Однако делать это имеет смысл только для ResourseRequesterState и ResourseRequest, чтобы сократиь число портов и упростить развертывание.

Упрощенные реализации

  • Релизация без приоритетов с возвратом ресурсов прежднему владельцу

    • Распределние ресурсов сводится к отъему у активных компонент нужных запрашивающему ресурсов.
    • По переходу задатчика в сотояние "не активен" его ресурсы возвращаются прдыдущему владельцу, т.е. фактически происходит обычный раунд распределения, но роль приоритета играет порядковый номер запроса.
  • Реализация без приоритетов:

    • распределние ресурсов сводится к отъему у активных компонент нужных запрашивающему ресурсов.
    • не нужны ResourceRequesterState и контроль состояния компонентов. Контроль раундов на стороне клиента по-прежнему необходим.

Реализация с портами и операциями

Операция используется для ResourseRequest: арбитр возвращает номер раунда задатчику. Потенциально, это может быть быстрая ClientThread с буфферизацией запросов. ResourceAssigment содержит номер раунда, что позволяет клиенту узнать, был ли обработан его запрос.

Порты использованы для ResourceAssigment и ResourceRequesterState. Обеспечивается возможность долгой обработки на стороне клиента. Клиент переносит номер раунда в ResourceRequesterState из ResourceAssigment, что позволяет арбитру кзнать, было ли обработано конкретное распределние ресурсов.

Достоинства:

  1. Очень простой контроль раудов сообщений.

Недостатки:

  1. Могут быть сложности по совместимости операция с ROS.

Реализация с портами

Сильно усложняется контроль раундов. Уникальные индефикаторы раундов генерируюся на уровне клинета. Индефикатор сотоит из двух полей: имя клента и порядковый номер запроса клиента.

ResourceAssigment должен содержать список идентификаторов, формируемый по следующим правилам. Индентификатор запроса попадает в список, если обработан соответсвующий ему ResourseRequest. Индентификатор запроса удаляется из списока, если обработан соответсвующий ему ResourceRequesterState. Таким образом, этот список содержит идентификаторы последних запросов pending задатчиков. По этому списку клиент понимает, обработан ли его запрос.

ResourceRequesterState содержит при выходе из сотояни pending: идентификатор последнего запроса клиента, при выходе из сотояни operational: нулевой идентификатор. ИЛИ * номер раунда арбитра из ResourceAssigment Это информация позволяет арбитру понять, обработано ли ео распределение всеми компонентами в сотоянии pending.

Достоинства:

  1. Проще сопрягать с ROS.

Недостатки:

  1. Большая сложность. Но на практике в списке идентификаторов почти всегда будет один номер.

Реализация с операциями

Не рассматривается, т.к. не обеспечивает желемую асинхронность.