По первоначальному замыслу эта статья должна была стать моей первой публикацией на Хабре, но не сложилось. Немногим более года назад, наткнувшись на статью про шахматные доски, я вспомнил о своём старом проекте антропоморфного шахматного робота и было взялся за текст, но спустя пару недель у меня родилась дочь, и работа снова остановилась.
Сам проект растянулся на долгое время и оказался, в целом, не слишком удачным (хотя и был доведён до условно рабочего состояния). Из-за длительных перерывов как минимум один раз мне пришлось переписывать код практически с нуля – я уже не мог понять, как именно он работает и, главное, почему не работает. В итоге проект всё же был доведён до логического завершения и функционирующего прототипа, но по ряду причин я считаю его практически провальным – о них я расскажу в конце статьи.
Задумал его очень давно, в 2015 году. Я вернулся в нашу лабораторию инновационной ерунды (я никогда не помнил этого длинного и, по сути, ни о чём не говорящего названия) со стажировки в Испании и мой научный руководитель загорелся идеей построить что-то красивое, что могло бы служить интересной демонстрацией технологий для студентов. По крайней мере, мне так помнится, возможно, цель была другая. В любом случае, он хотел построить автоматизированную машину для готовки бургеров, в которой процесс готовки был бы наглядно показан человеку, ожидающему этот самый бургер. Мне эта идея не понравилась по многим причинам. Во-первых, это не особенно интересно. Интересно будет только когда ты видишь это в первый раз, следующий бургер уже будет заказан только ради бургера. Во-вторых, это будет сопряжено с огромным количеством непредвиденных технических сложностей, ведь наш творческий коллектив по специальности были инженеры автомобильного транспорта, а не пищевой промышленности. И, в-третьих, проблемы с различными разрешениями, типа санэпидемстанции, для такой установки виделись мне весьма туманными. А кроме того, эту штуку ещё нужно было кому-то обслуживать. В общем, затея в моих глазах была сомнительно��. Пока мы раздумывали и открещивались, у меня родилась идея о создании робота, который бы мог играть с человеком в шахматы, физически переставляя фигуры. Однако и эта идея тогда не имела ни четкой формы, ни конкретного плана по воплощению, поэтому я её руководству не озвучил.
Однако, я продолжал над этим размышлять. Мне вспомнились примеры из истории, а именно фальшивые роботы типа «турка», в котором на самом деле помещался человек. Понемногу в голове сложился образ: хочу антропоморфного робота, у которого был бы торс и как минимум одна рука для передвижения фигур, ну и голова, само собой, чтобы созерцать поле и сверлить взглядом противника.

❯ Концепт проекта и начало изготовления
В какой-то момент возникла мысль, что робот может быть терминатором модели Т-800, которого после победы человечества перепрограммировали под эту задачу. Думаю, что вполне очевидно, почему я назвал проект Чесстером – просто потому, что это слияние двух слов chess и terminator.
Потом из Севастополя я уехал в Испанию делать PhD. Свободное время у меня было, а также был 3д принтер, который я собрал дома специально для перевозки в самолёте.

Вернулся к моей задумке, когда стал изучать Python по роду своей основной деятельности. Тогда я интереса ради забил в поиск, есть ли библиотеки, посвященные шахматам, и нашел python-chess. После изучения этой библиотеки, кирпичики начали складываться во что-то толковое. Теперь стало более-менее понятно, чего я хочу и как это воплотить. За механику я особенно не волновался (и как показала практика, напрасно), а вот программная часть вызывала большие опасения. И я начал постепенно собирать робота, не имея полного представления каким он должен получиться и как должен работать. Это была одна из моих первых ошибок, но, с другой стороны, собирая конструкцию практически из мусора, который у меня набирался тоже не сразу (различные моторы были набраны в основном из принтеров и сканеров с помоек), избежать этого пути было сложно.
Начал я с головы, т.к. масштаб, по сути, её и определялся. Принтер имеет очень маленькую рабочую зону, всего 120х120 мм и, чтобы не сильно заморачиваться с разрезанием и последующей сборкой головы из огромного количества частей (в итоге собирал из четырёх), я решил сделать робота немного меньше настоящего Шварцнегера. В сети я нашёл несколько 3д моделей черепа Т-800, но ни одна из них не подходила для моих целей на 100%. Пришлось осваивать Meshmixer и немного Blender, чтобы дорабатывать модели в соответствии с моими представлениями. Во-первых, голова должна была «говорить», то есть открывать рот в такт речи. По идее, робот должен был отпускать комментарии по ходу игры, спрашивать, не знаете ли вы, где живёт Сара Конор, он же хочет отправить открытку, честно. Функционал разговора я пытался опробовать, но делал это очень примитивно (я вообще тогда не очень сильно разбирался ни в электронике, ни в программировании): Ардуина слушала звуковой выход компа и открывала рот робота, когда уровень сигнала превышал определённый порог. Это была довольно глупая затея, так как очевидно, что робот будет открывать рот как минимум с запаздыванием, обусловленным временем реакции сервы. Я думал как бы можно было организовать задержку, но так толком и не придумал, и этот функционал остался не реализованным.

Ещё хотелось, чтобы робот мог вращать глазами. Эта задумка родилась после пересмотра первого терминатора, проведенного для освежения памяти. В самом конце есть сцена, когда Т-800 заходит внутрь завода и пытается понять, прислушиваясь, где прячется Сара. В этот момент он медленно переводит взгляд с одного места на другое и это очень выразительно смотрится. Я хотел того же эффекта, плюс он мог бы смотреть противнику в глаза во время игры, что должно было бы оказывать некоторое психологическое давление.

По поводу привода глаз моя первая идея была, в некотором роде, вдохновлена реальными человеческими глазами, только вместо мышц, глаза должны были приводиться в движение тросиками от сервоприводов. Я считал, что будет достаточно 4 тросика и два сервопривода на глаз. Однако собрав прототип, оказалось, что двигать глазом можно, а вот точное положение задавать не получается, по крайней мере с теми материалами и устройствами, что были в моем распоряжении. Делать терминатора со взглядом сиамского кота как-то не хотелось, поэтому пришлось искать другое решение. Далее мне подумалось, что управлять глазами можно только с двумя сервами, не разделяя управление каждый глазом. В этом случае робот не мог бы «фокусировать» взгляд, а смотрел бы на две тысячи ярдов, в прочем ему можно, у него ПТСД. Но даже это решение я в результате воплощать не стал, так как визуальные эффекты было решено оставить на закуску и сначала реализовать боевой функционал, а потом, увы, и вовсе было решено избавиться от этого проекта.
Дальнейший рост робота был довольно странным – из головы вырос позвоночник, который упирался в основание. В качестве этого основания была взята металлическая пластина от подставки старого условно плоского телевизора. Телевизор был весьма массивный и пластина была соответствующей. Кроме того, своей формой она очень хорошо подходила. В качестве опоры плечевого пояса была взята банка от чипсов «Принглс». Она была вклеена в позвоночник для чего в оном был сделан вырез по диаметру банки.
Также робот должен был наклонять голову вниз и вверх, переводя таким образом взгляд с шахматной доски на противника. Для этого в позвоночнике, в районе третьего – четвертого шейного позвонка, был размещен шарнир, а на спине, в уже упомянутой банке, жили две сервы, которые тянули голову вверх. Дешёвые MG90S в определенных положениях со своими задачами не справлялись, поэтому сильно наклонять голову было нельзя. Увы, и эту задумку я не реализовал и привод подъёма головы не был подключен к управляющей плате.

Постепенно я сделал торс и начал работать над рукой. Так как бандит был одноруким, я решил поместить актуатор, поднимающий руку, в левом плече, пропустив вал, на котором сидела рука через плечи насквозь. Так было удобнее, и нагрузка распределялась лучше. Кроме вала, через плечи проходили и две шпильки М3, стягивающие фланцы с подшипниками, в которых вращался вал привода плеча. К левому фланцу и был прикреплен актуатор. Чтобы он не проворачивался вместе с фланцем, внизу была организована дополнительная опора в виде треугольника из двух стержней, которые соединяли фланец с основанием робота. Под правый фланец была сделана аналогичная опора, в основном, чтобы его разгрузить. Дело в том, что кроме нагрузки от руки, на правое плечо приходилась также нагрузка от противовеса, который был призван облегчить нагрузку на актуатор подъёма руки, когда та полностью выпрямлена и перпендикулярна линии плеч. Противовес был козырный – из нержавеющей стали, чушка, извлеченная из отходов цеха, на которой экспериментировали со сваркой (на самом деле там какой-то ЧПУ станок что-то наваривает, что за технология – не знаю). На роботе он сначала просто висел, а потом был помещен в короб, чтобы не болтался при перемещении робота, да и просто на всякий случай.
Хотя я немного забежал вперед. На самом деле, перед установкой каких-либо работающих приводов на руку, я был занят шахматной доской. Доска – это важно, так как робот не мог играть с чем угодно. Дело в том, что из-за слабости моих навыков программирования, я решил не лезть в компьютерное зрение и делать систему как можно проще. А как сделать так, чтобы робот играл вслепую? Я решил, что необходимо чтобы доска была активной и могла сообщать компьютеру своё состояние. Я уверен, что есть огромное количество различных вариантов реализации этой задачи, но я выбрал самый примитивный – матричную клавиатуру. В результате компьютер получал матрицу 8х8 состоящую из 0 (клетка пуста) и 1 (клетка занята). Зная изначальное положение фигур, можно довольно легко отслеживать прогресс партии.

Хорошо, а практическая реализация? Тут я подумал, что робот, собранный из мусора, скорее всего, особой точностью похвастаться не сможет. Значит, надо сделать такую доску, чтобы можно было гарантированно точно поставить фигуру. Для этого, я решил сделать основание фигур конусным. Таким образом, когда робот фигуру отпускает, она автоматически самоустанавливается при падении. Это дало пространство для ошибки примерно ±1 см. Ладно, а как доска вообще должна работать? Не кнопки же туда действительно ставить? Да, не кнопки. В самом низу каждой фигуры я поместил небольшой круглый магнит 10×2 мм, а в ячейках доски стояли герконы. Как видно из схемы выше, всё это подключалось к Ардуино и в принципе могло вполне быть протестировано отдельно от остального робота, что было удобно. Да, кстати, я использовал ROS (Robot Operating System) для связи разных частей робота – было очень удобно в плане коммуникации.

Хотя я и говорил раньше про основание робота, на самом деле, это было скорее основание торса. Мне нужно было что-то, что бы объединяло доску и торс. Для этого я купил старую подставку на колёсах от ЭЛТ-телевизора. У этой штуки было два разных уровня (вероятно, один из них для видеомагнитофона), что мне отлично подходило. Самый верхний уровень я отвёл под доску, а вот на уровень пониже прикрепил деревянный ящик, внутрь которого должен был встать управляющий комп (не судьба), а сверху я поставил торс робота.

Но это было позже, а до этого я продолжил работу над рукой. Желая, чтобы она была максимально похожей на оригинал, я повторял хотя бы примерно пропорции и форму, что налагало некоторые ограничения. Например, плечевая кость была сильно больше размеров поля моего принтера, и ее пришлось «сваривать» из многих частей. С этой самой костью произошёл забавный, но неприятный случай. Как-то раз я разобрал руку и сняв её с робота, оставил на столе. Жаркое испанское солнце нагрело черный пластик, и когда я вернулся к работе, оказалось, что плечо имеет уже совсем не ту форму, которую я задумывал. Пришлось греть феном и исправлять.

Чтобы сымитировать плечевой сустав достаточно, чтобы в нём было 2 степени свободы, про один актуатор (подъём руки над доской), я сказал. Он вращает сустав вокруг оси Y1. Второй расположен непосредственно в плече, он поворачивает руку вокруг Z1. Испанское солнце поворачивает руку вокруг X1. Далее на плечевой кости располагаются «бицепс» и «трицепс», как у настоящего Т-800. Активным из них является «трицепс», он и разгибает, и сгибает руку в локте. Впрочем, «бицепс» не является только лишь муляжом – в нём установлена пружина, которая компенсирует люфты механизма.

Пойдём дальше по руке. За локтем у нас идёт предплечье, где в самом основании расположен следующий актуатор, поворачивающий руку в запястье. Это уже знакомая нам серва MG90S. Разумеется, непосредственно на неё нельзя повесить всё предплечье – она чрезвычайно хилая, поэтому конструкция вращается вокруг оси (видимой на картинке выше), посаженная на пару подшипников. В такой конфигурации серве хватает мощи, чтобы вращать предплечье/запястье. А дальше… актуаторов нет. Ну, вернее, почти нет. Дальше у меня стоял только актуатор захвата, но на кинематику руки он не влиял. Меня спросят – а как же это будет работать с четырьмя степенями свободы, если в человеческой (и, я подозреваю, что в терминаторской тоже) руке их аж семь, не считая пальцев?! А фигово это будет работать. Но будет. Зато примитивно просто в управлении и конструкции.
Что же до захвата, то сначала я не очень представлял как его осуществить, думал делать хотя бы один реально двигающийся палец. Но такая система точно бы не отличалась стабильностью, и на выручку пришли магниты. Наверху каждой фигуры вклеивался маленький магнит, что-то вроде 3×6 мм, а в полом большом пальце робота ходил вверх-вниз магнит помощнее, 6×20 мм, который мог стабильно удерживать в воздухе любую фигуру. Как происходило расцепление, если магнит был постоянным? Его просто втягивала тросиком внутрь пальца ещё одна серва MG90S, расположенная в районе локтя.
Тут я понял, что мои фигуры придётся переделывать. Во-первых, я их уже напечатал без пазов под магниты. А во-вторых, когда я начал проводить первые эксперименты с подъёмом руки, я понял, что часть моих фигур – слишком высокие и робот просто не сможет поднять их достаточно высоко, чтобы перенести через другие фигуры на поле или наоборот – не сможет перенести другие фигуры через них. Я имею в виду всё, что выше пешки. Да и пешки можно было уменьшить. В результате под замену пошло всё, кроме коней, им я просто отрезал верхушку (которой у них, кстати, и не должно было быть – её я присобачил туда для механического захвата) и вклеил магнит. Таким образом, я сделал фигуры трёх разных высот: самые мелкие – пешки, чуть выше – ладьи, кони и слоны, а самые высокие, естественно – ферзь и король. Разница в высоте, правда, была незначительной – около 5 мм между каждой ступенью.

Изменения так же коснулись и расположения доски. Оказалось, что рабочая зона робота меньше, чем доска. Ну, я ж дурной и вообще никаких прикидок не делал. Но выход я нашёл – поставил доску на две мебельные линейные направляющие и стал двигать её в зависимости от того, где находится нужная клетка. Доска имела всего два положения – ближнее и дальнее, и управлялась связкой из Atmega8 и ULN2003. Контроллер принимал на вход 0 или 1 и переводил доску в нужную позицию.

❯ Немного о приводах и управлении
Помимо упомянутых серв, все остальные моторы, которые были использованы – шаговые. Сначала была идея попробовать сделать что-то вроде сервопривода, но после нескольких неудачных попыток я пришёл к выводу, что не осилю. Шаговые, напротив, я уже более-менее освоил. На обоих плечевых степенях свободы трудились б/у двигатели из принтеров и сканеров, а вот в «трицепсе» стоял новый моторчик, который ранее предназначался для врачебного девайса для урологов, который я пытался делать будучи аспирантом в Севастополе. Девайс не удался, а мотор остался. Моторчик очень узкий, но длинный и момент у него был довольно приличным для его размеров. Вообще, думаю, это единственный более-менее интересный актуатор в роботе. Он, понятное дело, линейный, оснащён одним концевиком и винтом М4. Всё это располагалось в теле «гидроцилиндра», которое было сделано из фотобарабана лазерного принтера. В штоке «гидроцилиндра» помещалась гайка. Шток не фиксировался от проворота ничем, если не был прикреплён к локтю.

Привод подъёма руки был выдран из какого-то видеомагнитофона, изначально там стоял коллекторный мотор, который я, как уже было сказано, заменил шаговым. Мотор крутил червяк, который входил в зацепление с колесом, на котором, в свою очередь, сидела шестерня с модулем 1 на 8-10 зубов, не помню. А эта шестерня вращала большое колесо на 100 зубов, которое и приводило в движение вал руки. Привод был самотормозящимся, поэтому в случае отключения питания проблем не имел. Второй плечевой привод включал довольно слабый шаговик из старого сканера вместе с редуктором, от которого дополнительным ременным приводом шла понижающая передача на поворот руки. Причём чтобы мотор сильно не грелся, я прицепил на него радиатор с северного моста какой-то материнской платы. Это, конечно, изврат, но работало.

На каждом приводе стояло по одному концевому выключателю, чтобы определять начальное положение и от него затем отсчитывать текущую позицию. Итого, у меня было три шаговика с тремя концевиками. Чем же это всё удобно управлять? Мне, как человеку уже построившему пару 3D принтеров, ответ был очевиден – связкой RAMPS 1.4 – Arduino Mega. На плате RAMPS есть также достаточно выходов под сервомоторы, что удобно. Итого, в состав робота, включая доску входило две ардуины (это если не считать сдохшей жёлтой коробки). Питалось всё это добро от старого ATX-блока питания – их у меня было в достатке.
В качестве компьютера, гоняющего Ubuntu и ROS, предполагалось использовать старый ПК на Pentium 4, собранный из универовского мусора. Однако, это было бы, в случае успешного завершения проекта. Поэтому до стационарного выделенного специально ПК дело не дошло, и я использовал свой ноутбук. Конкретно версии Ubuntu и ROS я не помню, да это и не важно. Код узлов, я тоже думаю, особого смысла нет приводить, только опишу в целом как всё работало. Для этого посмотрим на то, что выводит rqt_graph.

Наверное, начать лучше всего с доски. Это узел /ard_1 работает, как это подразумевает название, на Ардуине Nano, что в доске. Я уже говорил, что это устройство не делает различий между фигурами и шлёт сообщение в тему /board примерно такого содержания: 1111111111111111000000000000000000000000000000001111111111111111 (если это начальная позиция). Более понятно это выглядит так:

Ардуина шлёт сообщение при любом изменении в этой матрице. Это нужно для определения хода и человека, и робота. С человеком понятно – роботу надо знать, куда он походит, а ход робота надо определять, чтобы быть уверенным, что робот таки смог передвинуть фигуру(ы) правильно. Анализом положений доски занимается узел /move_reader. Он слушает ещё и тему /move_finished, куда идут сообщения о завершении хода. Узел работает следующим образом. В нём есть список состояний доски, опустошаемый при завершении хода. Эти позиции анализируются по количеству состояний доски, по количеству фигур и по изменению их положений. Например, есть два состояния и количество фигур одно и то же. Это может быть обычный ход, скажем e2e4, если мы видим, что одна из фигур теперь стоит в другом месте, а может быть игрок просто поднял и опустил фигуру в том же месте (чего, кстати, делать нельзя). Или, например, три состояния: первое – исчезает одна фигура; второе – исчезает вторая фигура; и третье – фигура появляется в том же месте, где раньше одна исчезла. Это значит, что кто-то кого-то сожрал.
Результат выдаётся в обычном шахматном формате (как пример – тот же е2е4) в тему /move_seen. Эту тему слушает «мозг» всего робота – /ai_chesster. Именно тут находится ИИ, который и решает, куда будет ходить робот. В качестве ИИ используется вяленая ры��а популярный шахматный движок stockfish. У этого узла есть две дополнительные функции. Первая – проверить, что ход человека правильный, и вторая (читерская) – подсказывать мне лучшие ходы (лошадью ходи, век воли не видать!). Подло? Зато аутентично – человечество победило потому, что ему помог Т-800. Где бы был без него этот ваш Джон Коннор? Да, и я не прикидываюсь хорошим игроком и не тешу себя надеждой рыбку обыграть своими силами. Зачем я вообще сделал такую функцию, если, по сути, робот играет сам с собой? В основном, для тестирования. Сложность я бы потом подстроил, а вначале хотелось попробовать длинные партии, чтобы убедиться в правильной работе машины. Играл бы сам – партии были бы короткими, и Т-800 таки заборол бы человечество в лице меня.
Узел /ai_chesster пишет в две темы: /current_board_position и /robot_move. Обе они слушаются узлом /robot_move_translator, основная задача которого – определить, какие действия надо проделать роботу, чтобы физически осуществить ход на доске. Для этого надо знать и ход, и «видеть» доску (в /current_board_position позиция записывается в стандартной шахматной нотации Форсайта – Эдвардса). После чего определяется последовательность действий руки, которая передаётся по одному движению далее в тему /move_to_make. Когда механика осуществила это движение, сообщение об этом принимается из темы /robot_finished и передаётся команда на следующее действие. Когда последовательность закончится, узел рапортует об этом в /move_finished.
А дальше следует самая тупая часть моего проекта – узел /move_processor. Почему? Сейчас расскажу. Эта штука непосредственно командует второй Ардуиной (узел /ard_2) через тему /robot_data. Здесь петля, подобная предыдущей – Ардуина получает команду, исполняет, рапортует в /result, получает следующую. А тупость в том, как задаётся команда. Я ведь не стал делать кинематическую модель робота. Мне стыдно за это, но тогда я просто не знал, как это сделать. Вместо этого я просто составил три таблицы в MS Excel 8×8, где лежали команды для перемещения руки на заданную клетку. И координаты в этих командах я задавал вручную. Да, 64 клетки в каждой из трёх таблиц (192 позиции) я задавал вручную. А самое замечательное – при малейшем повреждении робота, его ремонте, длительном простое, изменении погоды на Марсе – эти координаты сбивались. И приходилось задавать всё заново. Пару часов каждый раз. Это –фиаско.
Теперь объясню немного подробнее, почему три таблицы. Опять же из-за того, что нет кинематической модели. Это для трёх положений фигур на/над каждой клеткой – полностью поднятое для переноса, полностью опущенное и поднятое так, чтобы нижний конус фигуры был на пару миллиметров выше отверстия клетки. Зачем так? Потому, что в силу устройства руки, при опускании и поднятии, фигура не движется строго вертикально. Значит, надо задать несколько точек, чтобы фигура точно встала на место (это особенно важно при опускании). Минимальное количество этих точек в траектории – 3, отсюда и количество таблиц.
Это главная причина, по которой я считаю свой проект неудачным. Остальные, пожалуй, просто дополняют первую. Во-первых – отсутствие нормального проектирования и сборки (я просто не знаю реальных размеров и конфигурации робота). Во-вторых – применённые материалы. Робот очень хлипкий, PLA деформируется от нагрузок и температуры. В-третьих – неточность привода вращения запястья сервой, как следствие проблемы с позиционированием фигур.
В целом, на этом можно закончить объяснения логики работы системы. Единственно, что ещё забыл упомянуть – так как жёлтая коробка сдохла (этот узел имел прозаическое название /yellow_box), то сообщение в тему /move_finished о завершении хода человека я каждый раз писал в терминал сам вместо того, чтобы на кнопку жать.
❯ Работало ли всё это?
Да, работало. В первый раз я его почти довёл до рабочего состояния во времена морового поветрия, но к сожалению, робот потерпел крах. Его случайно толкнула жена и, так как центр тяжести у него был смещён назад, он просто завалился.

Исправлять надо было много чего, в том числе устойчивость, для чего крестовина с колёсами была повёрнута на 90 градусов. Однако времени у меня уже было не особо много – сначала я уехал на стажировку в Рим, потом была активная работа над завершением диссертации и её защита. В результате, робот пережил переезд, и вот когда я сидел без работы, ожидая постдок, я занялся им снова, и именно тогда я и переписал почти весь код до того вида, в котором он описан выше, так как не мог понять ничего из того, что вроде бы работало раньше.
На самом деле я играл с Чесстером всего один раз, это была его лебединая песня. Передо мной уже маячил переезд во Францию, куда его было решено не брать. Робот получился слишком громоздкий и слишком хрупкий, к тому же без ясного будущего. Поэтому я подготовил его и заснял с нескольких ракурсов, плюс вёл запись рабочего стола моего ПК, пока он всё это гонял. Ход игры можно видеть ниже. Я забыл упомянуть, что когда робот берёт фигуры противника, он их выкидывает в сторону. Для этого я собирался связать специальную сетку, как у бильярдных столов на лузах стоят, но до этого тоже не дошло и пришлось просто на пол что-то подложить.
На этом проект завершился, после чего робот был разобран, осталась только пластиковая черепушка.

Новости, обзоры продуктов и конкурсы от команды Timeweb.Cloud — в нашем Telegram-канале ↩

Близкое по теме:
