Умный квест в реальности: демоны и проводки

Многие слышали про квесты в реальности — перенесенные в наш мир игры жанра escape the room. Решаешь головоломки, получаешь ответы, проходишь на следующий этап. Закончить нужно за час, в итоге открывается дверь на выход. Но немногие знают, как они устроены внутри. В этой статье мы заглянем за кулисы одного из таких квестов, а также сравним его с другими в техническом плане.Как разработчик эскейп-румов, я видел реализации некоторых из них. Обычно квест делится на никак не связанные блоки, которые вместе реализуют возможность прохождения. Тем не менее, для нашего квеста мы использовали централизованную архитектуру. Постараюсь рассказать, какие плюсы и минусы этих подходов, а также опишу, какие задачи решает «техник», создающий квесты в реальности, т.к. эта тема не очень хорошо раскрыта в Интернете.

2805c2badad14a41b59bd8dfd69799b7.png

Стандартный подходЛогически сценарий квеста делится на задачи. Например, заказчик хочет, чтобы «ввел код в клавиатуру ⇒ загорелась лампочка». Соответственно, задача для техника здесь — сделать клавиатуру, которая зажигает лампочку при вводе правильного пароля. На ум сразу приходит Arduino, которая контролирует лампочку и клавиатуру. Так обычно и делается.34219b247f744a1aa16e101a320a9f3f.pngСтандартная архитектура

В итоге получаем отдельные, никак друг с другом не связанные задачи, которые вместе и являются квестом. У такого подхода есть серьезный минус: отсутствие гибкости. Необходимость внести изменения приводит к долгому ремонту, требующему лезть в сложнодоступные места, и даже ломать стены и потолки, чтобы получить доступ к заветным компонентам. Изменения в квесте вызваны:

Изменением в сценарии. В процессе строительства происходит постоянно, после иногда. Поломкой. Компоненты дешевые, поэтому это тоже происходит не редко Требованиями людей, которые тоже делают квест: строители, декораторы, … Простой пример: задача готова. Но требуется поменять цвет на управляемом прожекторе. Для этого нужно идти в то место, где установлена ардуина, подключать ноутбук, искать код к задаче, менять его, загружать, а потом тестить. Хорошо, если получится с первого раза. По моему опыту не редки случаи, когда такие, казалось бы, элементарные изменения приводили к серьезным последствиям: случайно задев какой-нибудь провод, рушится вообще вся задача или горит компонент. Квест останавливается, у команды истерика.

Еще пример. Оператор (человек, отвечающий за работу квеста и принимающий игроков) звонит и говорит, что что-то сломалось (например, после ввода кода лампочка не загорается — симптомы). Чтобы это починить, требуется приехать на место, подключить ноутбук и начать разбираться. Ведь неизвестно, что сломалось: лампочка, провода, контроллер или что-то еще. Приходится проводить некоторые тесты, исключая возможности. Это занимает время, особенно, когда проблема «то есть, то нет», т.е. лампочка иногда загорается (все хорошо), а иногда нет.

682199603a364478b1aaa8599865f304.pngКошмарный сон техника

При отладке приходится часами стоять в неудобных позах с ноутбуком, т.к. ардуина намертво закреплена вместе с кучей проводов, которые трогать не хочется: все сломается. В пыли, грязи, боясь пошевелиться. С операторами, которые периодически заходят и спрашивают «ну когда уже, там люди ждут». В такие моменты сравниваешь свою деятельность с проституцией и рассматриваешь возможности соответствующего карьерного роста.

23213ae5be2a47e18ba94d835f29373e.jpgРеализация одной из задач в одном из квестов. Сверху в центре ардуина.

Самая худшая ситуация здесь — вмешательство в разбиение на задачи. Например, заказчик хочет, чтобы клавиатура из задачи 1 влияла на лампочку в задаче 2. Это значит, что нужно тянуть провода от одной ардуины к другой и менять код в обоих.

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

Централизованный подход Как решить эту проблему? Как обеспечить быстрый доступ ко всем устройствам, а также возможность быстро изменять связи между различными компонентами? Логично соединить все устройства в сеть и управлять ими с одного компьютера.Каждое событие (в примере выше это нажатие на кнопку) будет обрабатываться центральным компьютером, и, если нажатия кнопок составляют правильный пароль, он отдаст команду зажечь лампочку. Если требуется внести изменения (например, поменять пароль или сделать, чтобы лампочка зажигалась на 3 секунды вместо 5, или вообще зажечь свет в другой комнате), нужно просто изменить код на компьютере. Стены ломать не нужно.

c2f68b91bde341f1ab53d2f03764ecfc.png

В нашем квесте используется такая архитектура. Постараюсь рассказать поподробнее, как она реализована, и какие трудности возникали при разработке.

Реализация Для начала список периферии: Управление GPIO (свет, замки). Информация с GPIO (кнопки, датчики) Светодиодные RGB-прожекторы Мониторы, показывающие видео синхронно (видео-стена) с возможностью менять скорость и добавлять текст в runtime Дверной глазок с изображением ИК-приемник Большая клавиатура на стене, кнопок которой нужно касаться ладонью RFID-датчики Матричные клавиатуры Светодиодное табло с текстом Шаговый двигатель для стрелочных механических советских часов Всё это плавно раскидано по 6 комнатам.Скажу сразу: заказчиком была поставлена цель собрать квест из спичек и желудей наименьшей стоимости. Поэтому компоненты выбирались соответственно (компьютеры с avito, например). Осложняющие обстоятельства не позволили использовать самые подходящие технологии. Например, в самый горячий момент уволился парень, владеющий магией фоторезиста, из-за чего пришлось использовать платы для прототипирования (Arduino). Короче говоря, собрали как умели из того, что было.

В качестве управляющего устройства используется обычный ПК с Debian. На нем запущен демон, контролирующий весь квест. В качестве сети используется Ethernet и протокол UDP. Ethernet хорошо поддерживается как компьютерами, так и периферией (avr). UDP прост в реализации, а поэтому существуют хорошие библиотеки для МК, его поддерживающие.

471bf7a325404b1c8d91b2f64255def7.jpgСерверная квеста. Один компьютер (нижний слева) управляет всем, остальные нужны для показа видео.

Разнообразие периферии приводит к трем типам устройств, которые нужно подключить в сеть. Это Arduino Mega, Raspberry Pi и обычный компьютер с Debian. Последние два не вызывают вопросов, на первом стоит остановиться.

c0335af1f2074853b0251fa173c67445.png

AVR В качестве языка программирования выбран C++ (компилятор avr-g++), в качестве утилиты для сборки — CMake. В отличие от Arduino IDE это позволило автоматизировать процесс сборки и добиться большей гибкости. Например, можно получить доступ к таймерам и прерываниям, которые нужны для реализации квестовых задач, но по умолчанию заняты кодом Arduino.В качестве модуля Ethernet выбран недорогой Microchip ENC28J60 (~250р). В качестве библиотеки используется EtherCard. Она обращается к arduino-style функциям, например, pinMode. Вместо того, чтобы переписывать библиотеку, мы взяли из кода Arduino реализацию тех функций, которые нужны, тем самым получив «мини-версию» ядра Arduino. CMake позволяет легко подключить «мини-ядро» к проектам.

Библиотека имеет множество методов, но нам нужны только некоторые из них: послать UDP-пакет и установить Callback на получение UDP-пакета. Большие данные не будут гулять в нашей сети, поэтому считаем, что данные всегда помещаются в один пакет.

Система не имела бы смысла, если бы нельзя было поменять прошивку контроллера или перезагрузить его удаленно. Приходилось бы, как и раньше, ломать стены и подключать ноутбук. Мы решили эту задачу, используя проект avr-etherboot, немного изменив его для работы с Mega 2560. Работает он следующим образом: файлы .hex с прошивками хранятся на центральном компьютере и доступны по протоколу TFTP. Avr-etherboot генерирует bootloader, который прошивается по ISP в каждый контроллер. Именно ISP, т.к. прошивка по USB-UART реализована bootloader-ом Arduino, который может уже отсутствовать. При начале загрузки контроллера запускается bootloader, подключается к сети и скачивает по TFTP прошивку (каждый контроллер скачивает свою, т.к. bootloader-ы немного разные). Далее происходит работа прошивок на каждом МК. Написанный скрипт позволяет сгенерировать bootloader для каждого МК в квесте и прошить его одной командой.

Но что делать, если в прошивке есть ошибка, и нужно ее заменить? Как перезагрузить все МК? Для этого мы используем реле (т.к. их всего в квесте около 50, используем 8-канальные), через которое проходят питания всех ардуин, кроме одной. Последняя управляет этим реле и перепрошивается по USB с центрального компьютера.

b78edbdc76a545ad8abc707232be64d7.pngВыглядит непросто, но написанные один раз скрипты позволяют перепрошить все устройства одной командой, если это требуется.

Смешное, но важное замечание: поскольку мы пользуемся отладочной платой в «продакшне», нужно постоянно думать о надежности. Не один час потрачен на дебаг кода, когда проблема была в отвалившемся проводе. Поэтому соединение периферии с МК, ENC с МК и питание сделаны при помощи проводов и клеммников PLS и BLS. Опыт показывает, что без кримпера зажать их хорошо не всегда получается, поэтому остается один вариант — пайка. Большая часть поломок в квесте, из-за которых приходится отменять игры — не проблемы в коде или логике, а банально отвалившиеся провода.

Центральный компьютер Итак, мы имеем периферию, которая может воспринимать действия игроков либо сама что-либо делать. Также имеем сценарий квеста. Стоит сказать, что это не просто последовательность действий: в нем возможны варианты. Например, можно сначала пройти комнату А, а потом комнату Б, а можно и наоборот. Есть еще и задачи, работающие «независимо», например, можно сколько угодно раз нажать на кнопку, после чего загорится пресловутая лампочка, причем сделать это можно в любой момент, независимо от пройденных этапов. Как это проще всего реализовать? Будем использовать несколько процессов. Каждый процесс будет иметь полный доступ к периферии. Также процессы будут взаимодействовать между собой. В отдельные процессы вынесем независимые задачи и ожидания событий (например, введен правильный пароль в клавиатуру). Когда событие наступает, процессы будут реагировать нужным образом.

Основной сценарий — отдельный процесс. Он ожидает событий прохождения каждой из комнат, а также дает команды периферии на выполнение действий.

Реализовано всё это на C++ и стандартных библиотеках (unistd.h, sys/*). Взаимодействие между процессами выполнено при помощи неименованных pipe-ов. Для того, чтобы из одного UDP-сокета периферии могли читать несколько процессов, используется «разветвляющие» pipe, по количеству слушающих процессов. Пакет, полученный от определенного устройства, пересылается во все эти pipe, слушающий процесс читает уже из своего pipe.

Дополнительно прикручен TCP-сервер, который позволяет управлять квестом, создавая события и показывая лог. Есть возможность общаться с периферией напрямую через него, что очень полезно при отладке, т.к. не нужно «гасить» quest-daemon, чтобы освободить порт и получить доступ к периферии. TCP-сервер дает возможность создать веб-интерфейс к квесту, позволяющий контролировать квест с планшета.

44c6a25aec4e451086c28dbcbb2adc34.pngСхема демона в контексте остальных компонентов

Пример. Пусть квест состоит из одной задачи: нужно набрать код на клавиатуре. После этого зажигается лампочка и открывается дверь на выход. В данной архитектуре работать это будет следующим образом:

Процесс 1, сценарий ждет события «запустить квест» (блокирующий вызов) Оператор нажимает на кнопку в веб-интерфейсе. По цепочке это создает соответствующее событие, сценарий продолжает работу Сценарий запускает процесс 2, «задача 1», ждет события «задача 1 пройдена» Задача 1 запускает процесс 3, «поиск правильного пароля», ждет соответствующего события от него Игрок вводит пароль. Каждое нажатие обрабатывается процессом 3. Когда во входной последовательности встретился правильный пароль, процесс 3 генерирует событие «задача 1. введен правильный пароль» Блокирующий вызов в процессе 2 завершается, процесс 2 отдает команду «зажечь лампочку». Она передается периферии. Процесс 2 генерирует событие «задача 1 пройдена» Блокирующий вызов в процессе 1 завершается, процесс 1 отдает команду «открыть входную дверь». Квест пройден 6985232e9c784ab4ae8c7481bdef7b9b.pngИллюстрация к примеру выше

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

Веб-интерфейс квеста756108ee2b18432db814aa07d191e346.png   c0cbe118e80f47ad94a55a65886da931.png

Для удобства дебага все события записываются в системный журнал: b9dace1855eb405dbff059023ee15ba3.png

Если подумать над проблемой еще немного, на ум придет такая идея: нужно разделить низкоуровневую (общение с периферией) и высокоуровневую (квестовые события) части демона. Например, по TCP. Это даст возможность использовать для перевода сценария от заказчика в код более высокоуровневый язык. Будет проще и быстрее писать и поддерживать. Идея пришла в голову, когда квест уже работал, поэтому пока не реализована.

Задачи Когда архитектура придумана, остается только подключить периферию и протестировать задачи. Ниже процесс разработки и результаты: Решения задач и отловленные косяки GPIO. Создаем callback, который реагирует на входящий UDP-пакет. В зависимости от содержимого меняет состояние пина либо опрашивает пин и отправляет результат. Оформляем в виде библиотеки. GPIO: электронные замки. Управляются через реле. Важный момент: обязательно нужно добавить диод, который будет брать на себя ток, возникающий при размыкании из-за значительной индукции замка. Иначе МК квеста будут рандомно падать при открытии дверей. Светодиодные RGB-прожекторы. Изначально управляются с пульта по ИК. Но в квесте надежнее использвать провода, поэтому будем эмулировать ИК-сигнал при помощи Arduino. Важный момент: «по воздуху» передается модулированный сигнал. По проводам от ИК-приемника на прожектор идет уже обычный. Поэтому эмулировать нужно именно его. Гуглим, находим уже готовый проект. В коде остается только убрать модуляцию, т.к. мы используем провод вместо пары ИК-приемник и ИК-передатчик. Оформляем как библиотеку, создаем callback, который будет реагировать на UDP-пакеты и добавляем в проект устройства.Небольшая особенность: на длинных проводах команды доходят «через раз». Поскольку проблема чисто электрическая, а шарящий парень уволился, пришлось решить программно, посылкой каждой команды несколько раз. Мониторы с видео. Для наших целей отлично подходит компьютер с Debian. Софт напишем сами. Это будет демон, использующий libvlc для показа видео. Можно менять такие параметры как громкость, скорость воспроизведения и т.д. Для показа текста поверх видео в runtime ищем программу. Делаем управление через UDP. Ставим в rc.d на каждом компьютере (для управления всеми сразу удобно написать скрипт, выполняющий одну команду на всех через ssh). Для установки удобно использовать checkinstall. Для реализации возможности менять направление воспроизведения для каждого файла создаем второй, «обратный» к исходному. Дверной глазок с изображением. Сам глазок достаем из видоискателя старой видеокамеры, купленной на avito (500р). Раньше в них были ЭЛТ-мониторы. Пришлось купить две, т.к. в первой был неподходящий. Во второй видоискатель был оформлен в виде отдельного модуля с входами для питания и composite-видео. Гуглим, как определить, какой из проводов видео, питание и земля (плата без надписей). Подключаем к Raspberry Pi. Используем тот же демон с libvlc, что и для компьютеров. Внимание: для того, чтобы использовать hardware acceleration на RPI, нужно собрать libvlc самим. ИК-приемник. Ищем пульт и приемник с подходящими частотами модуляции. Подключаем к Raspberry, настраиваем LIRC с нашим пультом. Пишем демон, который будет отправлять по UDP принятые команды на центральный компьютер. Сеть в Raspberry. Почему-то сеть, настроенная через /etc/network/interfaces периодически отваливается. Используем Network-Manager. Большая клавиатура на стене. Покупаем тонкие пластины железа, подпаиваем провода. Подключаем к Arduino получившиеся емкостные датчики. Пишем библиотеку с поддержкой UDP на основе CapSense. Убираем все ненужное, т.к. кнопок много, а памяти не очень. RFID-датчики. Купили двух типов: matrix-2 и cp-z. Не забываем подтянуть сигнальный провод. Пишем библиотеку с поддержкой UDP на основе OneWire. Матричные клавиатуры. Пишем библиотеку с поддержкой UDP на основе Keypad Светодиодное текстовое табло NoName. Прошивается с компьютера, умеет показывать 10 разных надписей. Требуется уметь переключать их по команде. Разбираем, вынимаем аккумулятор, запитываем от внешнего источника, выводим провода от кнопки переключения надписей наружу, подключаем к GPIO Шаговый двигатель для стрелочных механических советских часов (часы ищем на avito.ru). Подключаем драйвер двигателя L239D. Пишем свою библиотеку управления ШД с поддержкой UDP. Используем таймеры, учитываем, что циферблат круглый, есть минутная и часовая стрелка. Учитываем, что вал двигателя соединен со стрелками через шестеренки. Чтобы не мучаться с таймингом, время будет идти на часах, а центральный компьютер будет его запрашивать. Если сделать наоборот, стрелки будут идти неравномерно, с рывками из-за задержек при передаче данных.Ищем шестеренку с нужными параметрами (чтобы держалась на валу ШД, и чтобы подошла к шестеренке в часах). Читаем про параметры шестеренок на Википедии. Двигатель крепим к часам при помощи фанеры и строителя, который тоже создает квест.Замечание: для того, чтобы узнать модуль шестеренки в часах, считаем количество зубцов, измеряем диаметр линейкой.Замечание: на сайтах с шестеренками достаточно плохо работает поиск. Ищем сами и не сдаемся. Я нашел подходящую в магазине радиоуправляемых вертолетов только после 4 часов безрезультатного поиска. Питание. Для питания всех элементов используются два типа источников: БП компьютера (5В: Arduino и др.) и БП, который идет в комплекте со светодиодной лентой (12В: замки, светодиодные ленты и др.) Старый телевизор (Рубин/Рекорд/…) с произвольным видео (не вошло в квест). Подключаем Raspberry PI к антенному входу через ВЧ-модулятор. Модулятор есть в комплекте с игровой приставкой Sega Mega Drive. Ищем на avito.ru Видео с разработки:[embedded content]

Итого устройств:

6 x PC 2 x Raspberry Pi 5 x Arduino Mega + ENC28J60 40 реле Общие замечания:

Тоже интересно Блоки с компонентами должны быть хорошо закреплены, никаких резисторов, болтающихся на проводах. Для этих целей хорошо подходят макетные платы для пайки.4d6d62bff3c44e9a9a58bbcc922add21.jpgПлата для драйвера ШД В такие платы удобно впаивать клеммники, а к ним уже подключать провода.931821c5c0a0494aa3d5448a839fa765.jpgПлата для большой клавиатуры Провода удобно соединять клеммниками WAGO. Скрутка — ненадежно (особенно для ломких проводов), а пайка — долго и негибко.7eecd5d164c247a680080b921a274f46.jpgСтена с компонентами. Выглядит не очень, но есть документация. Стяжки, термоклей и термоусадка — наше всё.46c7c0c4ccf74965bec1723f1095b7d2.jpgВидоискатель в глазке Заключение Итак, мы получили квест, который очень легко менять, не занимаясь лишней работой при строительстве. Также получили удобные фичи, такие как управление через веб-интерфейс и легкий поиск ошибок. Стала возможна удаленная поддержка, т.к. у локации имеется внешний IP.Все поломки, произошедшие с момента запуска, происходят из-за «железных» проблем (сдох компьютер, оторвался провод, …). При помощи удаленного управления за короткое время удается определить, что же именно сломалось. Большую часть можно починить удаленно, сообщая операторам, что делать. Изменения квеста от заказчика также выполняются «из дома».

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

cf455561b80c4d5bac7096064372cef5.png

Если кому-то понадобится код, я создам публичный репозиторий. Основной закрыт, т.к. в нем содержится сценарий и другие секретные штуки.

Спасибо за внимание!

© Habrahabr.ru