Руководство · Начальный · 17 min read
Конечные автоматы для поведения робота
Спроектируйте конечный автомат робота с явными переходами, тайм-аутами, восстановлением, неблокирующими обновлениями и тестируемыми правилами безопасности.
Программы роботов часто начинаются с цикла, полного условий if: следовать по линии, пока рядом нет препятствия, поворачивать, пока не освободится путь, останавливаться при низком заряде аккумулятора и каким-то образом восстанавливаться после тайм-аута датчика. По мере роста числа поведений одно и то же условие может выдавать противоречивые команды.
Конечный автомат (FSM) делает активное поведение явным. В любой момент робот находится в одном состоянии; события и условия определяют, когда он перейдёт в другое. Шаблон достаточно мал для Arduino и достаточно структурирован, чтобы его тестировать.
Разделите состояние, событие и действие
Состояние описывает, что робот делает с течением времени: FOLLOW_LINE, AVOID, SEARCH или FAULT. Событие — это наблюдение: препятствие вошло в зону остановки, линия потеряна, истёк тайм-аут или оператор нажал остановку. Действие — это выходное воздействие: задать скорости колёс, сбросить интегратор или выдать сигнал тревоги.
Не создавайте состояния для мгновенных фактов. OBSTACLE_DETECTED обычно является событием или условием перехода; AVOIDING_OBSTACLE — состояние, потому что оно сохраняется, пока робот выполняет поведение.
Начните с таблицы переходов до написания кода:
| Текущее состояние | Условие или событие | Следующее состояние | Действие при входе |
|---|---|---|---|
| Idle | Запрошен запуск, датчики исправны | Follow | Сбросить регулятор |
| Follow | Препятствие внутри порога остановки | Avoid | Остановить движение вперёд |
| Follow | Линия отсутствует 200 ms | Search | Запомнить последнюю сторону |
| Avoid | Путь свободен 500 ms | Follow | Сбросить регулятор |
| Любое активное состояние | Тайм-аут датчика | Fault | Остановить двигатели |
| Fault | Оператор выполнил сброс, состояние исправно | Idle | Сбросить неисправность |
Таблица показывает пропущенные случаи и делает приоритет безопасности видимым.
Сделайте цикл обновления неблокирующим
Обработчик состояния должен выполнить небольшое обновление и вернуться. delay() на две секунды внутри AVOID не позволит программе увидеть запрос остановки или отказ датчика в течение этих двух секунд.
enum class State { Idle, Follow, Avoid, Search, Fault };
State state = State::Idle;
unsigned long enteredAt = 0;
void transitionTo(State next) {
state = next;
enteredAt = millis();
}
void loop() {
Sensors s = readSensors();
if (!s.healthy && state != State::Fault) transitionTo(State::Fault);
switch (state) {
case State::Idle: updateIdle(s); break;
case State::Follow: updateFollow(s); break;
case State::Avoid: updateAvoid(s); break;
case State::Search: updateSearch(s); break;
case State::Fault: updateFault(s); break;
}
}
Каждое обновление читает уже зафиксированные показания датчиков и записывает одну связную команду исполнительным механизмам. Это не позволяет разным состояниям читать меняющиеся значения в разное время внутри одного цикла.
Размещайте переходы на ясных границах
Выберите одно правило владения. Простая встраиваемая конструкция позволяет каждому состоянию запрашивать исходящие переходы, а верхняя проверка безопасности может переопределить любое активное состояние.
void updateFollow(const Sensors& s) {
if (s.obstacleMm < stopThresholdMm) {
setWheels(0, 0);
transitionTo(State::Avoid);
return;
}
if (s.lineMissingForMs > 200) {
transitionTo(State::Search);
return;
}
followLine(s.lineError);
}
Сразу возвращайтесь после перехода, чтобы старое состояние не могло также командовать исполнительными механизмами. При входе в управляющее состояние сбрасывайте историю, которая не должна переходить между поведениями, например интеграл ПИД (PID) или выборку производной.
Используйте действия выхода экономно. Большую часть очистки можно выполнить в централизованной функции transitionTo() или на входе в следующее состояние. Отдельные функции onEntry, onUpdate и onExit становятся полезны, только когда они действительно нужны нескольким состояниям.
Используйте тайм-ауты и гистерезис как условия
Физические события шумны. Если AVOID выходит сразу после превышения того же порога, которым вошёл, шум измерения может вызвать быстрый дребезг переключений состояний. Используйте два порога или время удержания: входить ниже 250 mm, а выходить только после того, как расстояние остаётся выше 350 mm в течение 500 ms.
У каждого состояния, ожидающего прогресса, нужен тайм-аут. Если робот вошёл в SEARCH, но так и не нашёл линию, он не должен вращаться вечно. После проверенного интервала переходите в FAULT или ограниченное восстановление.
Стройте таймеры на времени с момента входа или с последнего прогресса. Используйте вычитание вида now - enteredAt >= timeout: оно остаётся корректным при переполнении беззнакового таймера, если типы согласованы.
Дайте переходам безопасности приоритет
Определите, что может прервать всё: аварийную остановку, недопустимые данные критического датчика, отказ драйвера двигателя или тайм-аут сторожевого таймера. Проверяйте эти условия до переходов, зависящих от поведения, и явно задайте безопасное выходное воздействие.
Состояние FAULT должно фиксироваться до сброса, если автоматическое восстановление может запустить опасное движение. Требовать сброса оператором после устранения причины часто безопаснее, чем сразу возвращаться в FOLLOW.
Не позволяйте двум независимым автоматам состояний записывать команды одним и тем же двигателям без правила приоритета. Если на движение влияют и навигация, и управление аккумулятором, один владелец должен выбирать окончательную команду и применять ограничения безопасности.
Тестируйте переходы без движения двигателей
Перенесите решения в функции, принимающие зафиксированные показания датчиков и время, а затем возвращающие следующее состояние или команду. Подайте последовательность синтетических наборов показаний:
- Действительная линия и свободный путь оставляют робота в
FOLLOW. - Один шумный близкий отсчёт не срабатывает, если требуется удержание.
- Устойчивое близкое препятствие переводит в
AVOID, и сначала команда останавливает робота. - Свободный путь, удерживаемый в течение интервала выхода, возвращает в
FOLLOW. - Тайм-аут датчика из любого активного состояния приводит к
FAULT.
Записывайте изменения состояния как old → new, событие, вызвавшее переход, и метку времени. Непрерывные журналы каждого цикла скрывают переходы в шуме; журналы переходов объясняют, почему изменилось поведение.
Знайте, когда FSM перестаёт быть простейшей моделью
FSM хорошо работает, когда у робота небольшое число взаимоисключающих режимов. Он становится сложным, когда состояния множатся, чтобы представить комбинации вроде «перемещаться, одновременно контролируя аккумулятор и повторяя локализацию». Число переходов может расти быстрее самого поведения.
Деревья поведения объединяют переиспользуемые действия со структурой последовательности, запасного варианта и восстановления, поэтому навигационные системы вроде Nav2 используют их для сложных задач. Не вводите фреймворк дерева поведения ради четырёх состояний. Переходите, когда таблица переходов действительно станет трудно проверяемой или поведения потребуют иерархического повторного использования.
Диагностируйте типичные сбои проектирования
- Робот игнорирует остановку во время поворота: состояние содержит блокирующий цикл или задержку.
- Двигатели получают две команды за цикл: код перехода продолжает выполняться после смены состояния.
- Автомат дребезжит: шумные условия не имеют гистерезиса или удержания.
- Восстановление никогда не заканчивается: у состояния нет условия прогресса или тайм-аута.
- Неисправность мгновенно возобновляет движение: неисправности безопасности не защёлкиваются или условия сброса слишком слабые.
- Для тестов нужен весь робот: решения и доступ к оборудованию смешаны вместо использования снимков датчиков и выходов команд.
Симулятор обхода препятствий даёт полезную последовательность поведения для моделирования: движение, объезд угрозы, прибытие или остановка, когда безопасного движения не осталось. Симулятор решения лабиринта показывает другое разделение между восприятием, обновлением карты, планированием и перемещением на одну клетку. Мини-робот сумо — самый яркий тест правила защёлкивания безопасности: его рефлекс на границе должен предшествовать всем остальным состояниям из любого состояния, иначе робот выедет из круга.
Исследуйте граф
Входит в эти сборки
Проекты и учебные пути, в которые входит это руководство.
- ПроектМашинка-робот под управлением смартфона: Bluetooth или Wi-Fi
- ПроектРобот на ROS 2 находит маркер и подъезжает к нему
- ПроектСоберите мини-робота сумо: сцепление, клин и датчики края
- ПроектСоберите робота для захвата и переноса с проверкой
- Учебный путьЗахват предметов и рабочие органы: как поднять предмет
- Учебный путьРеактивная навигация: научите робота обходить препятствия
Дополнительные материалы