
Недавно МТС анонсировали очередное соревнование TrueTechChamp 2025 - в нём две части - одна с типичными "алгоритмическими" задачами, другая на "программирование роботов". Участвовать можно в любой (или в обеих), но с первой всё незамысловато - а мы поговорим о второй.
"Отборочный" этап продлится ещё больше 3 недель (до 20 октября), так что любой желающий может влиться. Эта заметка расскажет, в чём собственно заключаются задачи, и с какими сложностями мы сталкиваемся - также будут замечания организационного характера. Надеюсь это поможет тем, кто также захочет поучаствовать. Ну или просто поведает о происходящем для тех, кому любопытно, но регистрироваться неохота. Можно даже погонять роботов локально, без регистрации, скачав нужные материалы.
Как устроены задачи
В этом году организаторы используют готовый опенсорсный симулятор webots в качестве "платформы" на которой участники запускают своих роботов.
Кроме симулятора нужно скачать из репозитория задач "проект" для симулятора - в него входит описание геометрии мира, объектов - и в частности "программа-контроллер" которая всем заправляет.
В обеих открытых на текущий момент задачах "робот" - это машинка, которую нужно провести по лабиринту. Внутри "машинка" устроена как типичный колёсный робот с дифференциальным управлением, но для программиста управление просто до безобразия - нужно посылать по UDP команды из двух чисел float32:
(linearVelocity, angularVelocity)
Оба числа задают скорость - линейную скорость движения (отрицательная двигает машинку назад) и угловую скорость поворота. Упомянутая программа-контроллер принимает эти команды и "отрабатывает" соответствующие движения робота.
"Обратную связь" о своих передвижениях и положении робот получает с "датчиков" - одометра, лидара и гироскопов - причём получает он их по TCP (от программы-контроллера) - т.е. в нашем коде нужно слушать сокет и оперативно подхватывать оттуда все приходящие данные.
Из хорошего - писать можно абсолютно на любом языке - в отправку кроме своего кода нужно добавить докер-файл, который создаст подходящий образ, установит желаемые инструменты и библиотеки и запустит ваш код.
Вот простой пример, который можно использовать для начала (организаторы также предлагают демо-программу, но несколько более объёмную).
import socket, struct, time
# запускаем серверный сокет для "телеметрии" - данных с датчиков
tele = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
tele.bind(("127.0.0.1", 5600))
tele.listen(1)
conn, _ = tele.accept()
# создаём UDP-сокет чтобы отправлять команды
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
# маленькая функция для подачи команд
def send_cmd(forwardSpeed, angularSpeed, duration):
packet = struct.pack("<2f", forwardSpeed, angularSpeed)
sock.sendto(packet, ("127.0.0.1", 5555))
time.sleep(duration)
send_cmd(-1, 0, 6) # едем задним ходом из "гаража" 6 секунд
send_cmd(0, 0, 2) # останавливаемся пару секунд
send_cmd(0, 1, 5) # поворачиваем влево в течение 5 секунд
send_cmd(0.5, -0.3, 10) # неторопливо движемся вперёд заворачивая направо
Эта программа, как видно, не использует информацию с датчиков а вместо этого "программирует" маршрут временными промежутками. Тем не менее включить приём телеметрии оказывается необходимо, иначе программа-контроллер загруженная в webots не начинает отрабатывать движение.
Робот довольно "физический" в том смысле что его моторы не разгоняются и не останавливаются мгновенно - мы задаём требуемую линейную и угловую скорость - но пройдёт какое-то время прежде чем эти значения будут достигнуты. Именно с этим связана вторая команда в данном примере.
Кроме того на обе скорости есть ограничения (1 м/c и 2 рад/c).
Следует оговориться - этот код годится для локальных экспериментов - если же вы собираетесь участвовать, используйте как минимум хост для отправки команд из переменных среды, чтобы он правильно проставлялся при выполнении в докере. Лучше же вообще скопируйте секцию разбора параметров из demo.py
.
Дальнейшие подробности рассмотрим на примере первой задачи.
Задача 1 - две комнаты с уточками
Текст задания выражен одной фразой:
Цель: робот сам доезжает до финиша (белый прямоугольник в середине второй зоны) по территории с простыми препятствиями.
Картину "мира" этой задачи вы можете видеть на изображении выше - "лабиринт" в этот раз фиксированный, то есть проверяется на таких же двух комнатах с такими же препятствиями, какие предоставлены участникам. Цель - белый прямоугольник во второй комнате - мы видим на левом краю. Стартует робот из огороженного 6 уточками "гаража" у правой стены.
Я сперва хмыкнул "звучит тривиально" - тут как будто максимум сложности - это с сокетами. А так можно запрограммировать фиксированный маршрут на "таймаутах" как в примере выше. Машинка начинает двигаться из "гаража" справа, отъезжает назад, а потом маневрирует, поворачивая сперва влево, затем вправо, и т.п...

Но не всё так просто!
Наши команды достигают "программу-контроллер" по сети, хотя и локальной - но в общем, асинхронно. Это означает что несмотря на точно заданные задержки истинное время получения команды может чуть-чуть отличаться (это связано и с устройством внутреннего цикла "контроллера" - можно посмотреть код). Кроме этого можно заметить индикатор скорости выполнения в верхней панельке окна webots - на моём нынешнем ноуте я вижу что он прыгает где-то между 0.95 и 1.04 - т.е. нужно понимать что симулятор и наша программа живут в риалтайме, но выполняются идеально-синхронно.
Из-за этого от прогона к прогону маршрут будет получаться немножко различающийся - и чем дальше, тем больше накапливается ошибка. А достаточно где-то задеть за стену - машинка немного повернётся и наши "тонкие расчёты" уже сбиты.
По-видимому без данных "телеметрии" тут не справиться. Их формат описан так:
9 чисел float32 (36 байт):
odom_x — одометрия X (м)
odom_y — одометрия Y (м)
odom_th — угол робота (рад)
vx — скорость по X (м/с)
vy — скорость по Y (м/с)
vth — угловая скорость (рад/с)
wx — угловая скорость по оси X (roll rate, рад/с)
wy — угловая скорость по оси Y (pitch rate, рад/с)
wz — угловая скорость по оси Z (yaw rate, рад/с)
Я человек наивный и с программированием роботов не знаком - поэтому решил первым делом воспользоваться данными с "одометра" - первыми тремя числами.
То есть не писать "двигаться с такой-то скоростью столько секунд" а "двигаться с такой-то скоростью пока X > -1
" например, чтобы выехать из гаража (в этих комнатах X "одометра" направлен изначально снизу вверх, а Y справа-налево).
С первыми несколькими командами всё получается неплохо, но по мере того, как удаётся довести робота до "мостика" между комнатами становится очевидной проблема, о которой знатоки наверняка уже догадались.
Одометр не очень точный - колёса, очевидно, немного проскальзывают - и снова накапливается ошибка, так что секунд 30 спустя при попытке повернуть машинку под нужным углом легко промахиваешься градусов на 5-10.
Эта проблема известна ещё с древних времён - особенно, мореплавателям. До изобретения надёжных способов навигации по звёздам, солнцу и хронометру (или когда они недоступны, например в облачную погоду) оставалось только ориентироваться "по счислению" - по компасу мы знаем направление, по лагу скорость - считаем приблизительно по часам и дням сколько прошли - и откладываем на карте. Накапливающиеся ошибки из-за ветров и течений погубили немало кораблей...
Придётся использовать лидар
Его "зона зрения" изображается интерактивно сектором, ограниченным тонкими голубыми линиями. Заметает угол 90 градусов перед машинкой (т.е. по 45 градусов влево и вправо) и возвращает данные в виде 360 чисел - то есть примерно через каждые четверть градуса.
Тут возникает две проблемы. Во-первых - а как именно этот массив чисел использовать? Например, если перед тобой препятствие в виде уточки - то какие-то "лучи" лидара будут попадать в них, а какие-то между ними. Значения в массиве будут сильно разниться. Даже если получишь расстояние до стены - необязательно понимаешь, в какое именно место стены лидар сейчас "светит". Конечно в теории легче сориентироваться по углу между двумя стенами, например. Но запрограммировать такой расчёт может быть нетривиально (впрочем, знатоки вероятно порекомендуют использовать готовые библиотеки).
В общем тут простор для идей и разных способов. Например, одно из первых что пришло в голову - можно "выровнять" угол поворота машинки поворачиваясь перед стеной так, чтобы значения с лидара стали симметричны (в центральном диапазоне). Однако этот "суперхак" я не рекомендую использовать, если вы хотите проехать как можно быстрее. Пока не очень понятно из официальной информации, но видимо если решений будет много, в следующий этап будут отобраны те, у кого время прохождения оптимально. Тем не менее "поиграть" с ним забавно - можно исследовать наглядно, например, эффект "перерегулирования" - когда машинка колеблется с ассимптотически уменьшающейся амплитудой около нужного направления.
Вторая проблема - лидар тоже даёт не слишком точные значения. В частности, значения нескольких последовательных "лучей" падающих на стену могут иметь не монотонный характер... Или это стены не очень ровные... Наверняка вы придумаете как с этим поступить.
Текущие Результаты
Пока у соревнования нет официальной "таблицы результатов" и приходится ориентироваться на скупые замечания в общих чатах. Кому-то удалось проехать за 66 секунд. Кто-то смог улучшить результат до 64. Моё решение занимает чуть больше 70 секунд - но хуже того, не всегда добирается до финиша. Впрочем есть ещё немало времени чтобы усовершенствовать алгоритм. Один из участников утверждает что согласно его математическому моделированию идеальное время около 55 секунд.
Демо-видео прохождения лабиринта не привожу т.к. это будет жирный спойлер.
Задача 2 - исследование лабиринта
Формулировка задания - ещё короче на этот раз:
Цель: робот автономно находит центр из генерируемого лабиринта со слепыми развилками.

В этой задаче не будет уточек - и стенки все ортогональны. У знакомых, и даже у членов команды, кто не очень внимательно вник в суть, эта задача вызывает обманчивое впечатление простоты. Они говорят что-то в духе:
можно использовать алгоритм
А*
, представив лабиринт в виде матрицы 16x16
Ну А*
был бы оверкилл для такого маленького лабиринта, хватило бы и волнового алгоритма. Впрочем есть две проблемы:
лабиринт на котором будут проверяться решения, неизвестен (в отличие от первой задачи), так что "представить лабиринт" невозможно - машинка должна проехать по всем закоулкам и разобраться в лабиринте сама, "в реалтайме"
робот, как упоминалось, физический - такой же как и в первой задаче - он не двигается точно от центра к центру клетки, не поворачивает "на месте" - и собственно геометрию стен вынужден распознавать с помощью лидара - притом он довольно крупный - в клетках разворачиваться ему не оыень легко
Дополнительно чтобы усложнить жизнь в лабиринте разбросаны несколько "конусов" - небольшие препятствия за которые можно зацепиться.
Центр лабиринта для наглядности отмечен красной буквой X.
В прошлом году было немного другое задание - там роботу нужно было с нескольких попыток исследовать геометрию лабиринта - а потом проехать в центр за минимальное время. На этот раз как будто об этом речь не идёт, поэтому не вполне ясно, нужно ли пытаться оптимизировать время прохождения (поскольку проехать нужно за одну попытку, для этого не так много возможностей).
Об организации
Я не стал бы писать этот раздел, но если ничего не сказать, то создастся впечатление, будто я заодно с организаторами или работаю на МТС :)
Не стал бы писать - следуя пословице "о мёртвых или хорошо или ничего".
Мягко говоря - потенциал для улучшений просто огромный. Даже кажется что компания не сама организует это соревнование, а аутсорсит его какой-то небольшой и не очень профессиональной команде энтузиастов. Не имею в виду, конечно, обидеть никого - но предупредить желающих поучаствовать как-то нужно.
При регистрации вы обнаружите что нужно указать емейл и телефон, а потом заполнить довольно объёмную анкету (где работаете, на каком стеке, какой у вас телеграм и т.п.)
Telegram, как будто, опциональный - но для создания команды в "роботах" её нужно зарегистрировать вступив в группу в ТГ. Вообще всё общение с организаторами и поддержкой - в ТГ. Собственного мессенджера на портале соревнований не предусмотрено.
Отправка заданий - с помощью коммита в специально выделенный на команду репозиторий. Это кажется удобным - коммитить может любой участник - а засчитывается "лучший" вариант.
После первого коммита запустился какой-то пайплайн и через час свалился с невнятным сообщением про таймаут. Следующие коммиты даже не стартуют пайплайн. Я-то думал, пайплайн будет обеспечивать проверку каждого коммита - но, оказывается, ошибся.
В чате коллеги пролили свет на происходящее:
-- Там в гитлабе не привязано ни одного раннера, так что и не запустится)
-- но ведь кому-то удавалось уже получить результаты... и первый-то раз пайплайн запустился...
-- Ато автодевопс нахимичил пайп, а про раннеры не знал (смайлики). А результаты, видимо, указаны полученные локально, плюс иногда организаторы запускают сами у себя
Когда в чат подошёл кто-то от лица организаторов (в выходные всё как будто вымерло) я смог получить такие уточнения:
-- Запускаем на сервере раннерами, но пока перепроверяем результаты, чтобы ничего не пропустить
-- так вы вручную их запускаете "на сервере раннерами"?
-- запускаем автоматически
-- Спасибо... а как результаты-то узнать?
-- Результаты у нас уже есть :) В крайнем случае опубликуем отдельную ссылку в чате на результаты проверок. Также покажем вам детальные логи проверки на днях (не в выходные)
-- прекрасно, но мне тоже хотелось бы знать результаты собственных посылок :)
-- всё перепроверим и опубликуем
Тут можно пофантазировать и предположить что организаторов извиняет то что они (гипотетически) в основном ориентируются и сосредотачивают усилия на следующие два раунда - оба будут использовать, вроде бы, реальных роботов. Отборочный с симулятором, поэтому, возможно вызывает затруднения. Впрочем, от этого не легче.
Нюансы для тех, кто захочет участвовать
Из важного ещё полезно упомянуть - формат данных телеметрии описан в пояснениях не полностью. Перед пакетом идут ещё 4 байта (о которых ничего не сказано) - размер собственно пакета (хотя размер в общем-то фиксированный). С первых попыток это маленько запутывает. Надеюсь, никто не осудит, если я просто приведу рабочую функцию (пример) для считывания пакета вместо дополнительных пояснений:
def read_tele():
global x, y, th, lidar
bytes = teleConn.recv(48)
# четыре байта (размер в uint32) игнорим, потом 9 float32 и еще один uint32
hdr, x, y, th, vx, vy, vth, wx, wy, wz, n = struct.unpack('<xxxx4s9fI', bytes)
bytes = teleConn.recv(n * 4)
lidar = list(struct.unpack('<%sf' % n, bytes))
Другой источник путаницы - с docker-compose файлом. В условии вроде бы указано что коммитить нужно только Dockerfile и свой код. В чатах от организаторов говорится что docker-compose.yml тоже нужен. При этом пример, предоставленный организаторами, по крайней мере под Linux по-моему не работает (а утверждают что проверяют под Linux). Чтобы его пофиксить следует:
убрать
5555:5555/udp
из разделаports
(иначе будет жаловаться что порт занят - ведьudp_diff.py
его слушает снаружи)добавить
"host.docker.internal:host-gateway"
в разделextra-hosts
(раздел тоже добавить)не обращать внимание на комментарии "для linux / для windows" и такие строки не трогать
Заключение
Тут прямо сказать ощущения противоречивые - с одной стороны, конечно, здорово что компания подвизается провести такое масштабное мероприятие - и видно что в задачи вложено достаточно труда.
С другой стороны, отборочный этап идёт с 8 сентября, а сейчас уже месяц почти заканчивается. Видно по чатам что какие-то вещи доделывают и исправляют "по ходу" - в частности даже 3-ю задачу пока не публикуют, обещая выбрать попроще или посложнее в зависимости от того насколько хорошо будут получаться у участников первые две.
Наверное за это время уже пора привести процесс в порядок. И технические средства тоже. Участие в соревновании в котором не только не видишь прогресса других команд, но даже своих результатов, по-видимому, нужно дожидаться день-другой - это может оттолкнуть многих серёзных участников - и тем самым сильно снижает уровень мероприятия.
Что же - пожелаем организаторам к следующему году подтянуться, а всех кому задачи показались любопытными - приглашаем регистрироваться (и создавать команды) чтобы попробовать-таки свои силы. Если вам совсем не хочется этого делать, но интересно погонять роботов в этих двух задачах - воспользуйтесь подсказкой ниже. Счастливо!
Скрытый текст
Репозиторий с задачами публичный, но не упомянут открыто. Поэтому используйте ROT13 на следующей "фразе": uggcf://tvg.ebobg.ploregrpu.nv/punzc_obg/gnfx1