Как стать автором
Обновить

Как заставить робота двигаться точно? Обзор робототехнического набора на платформе Studuino и его возможности

Время на прочтение15 мин
Количество просмотров3.7K
image

В организации, в которой я работаю, меня попросили провести небольшой вебинар по робототехнике для Московского международного салона образования 2020, который проходил в онлайн формате. Тему выступления мне оставили открытую и выдали образовательный набор по робототехнике. Так я познакомился с продукцией от японской корпорации ArTeC (это не реклама и я не являюсь сотрудником компании ArTeC – это просто мой личный опыт от общения с их продукцией). После вебинара решил написать эту статью, так как в России достаточно большую популярность получили наборы от компании Lego Education, а с этим японским конструктором я столкнулся впервые.

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

Так что, если кому интересно, что это за робототехнический набор и что можно с ним делать, прошу под кат. Итак, поехали!

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

Базовые блоки конструктора ArTeC
Базовые блоки конструктора ArTeC

image
Пример моделей из деталей конструктора

В силу специфики деталей – шахматный порядок отверстий и шипов для соединения, не всегда быстро получается подобрать нужную комбинацию деталей для достижения результата. Из достоинств, в отличие от Lego: все детали достаточно крупные, потерять такие намного сложнее. Второй плюс – не надо долго рыться в поисках нужной детали, потому что базовых деталей насчитывается всего 5-6 штук (отличие в цвете деталей не в счет). Есть и минус – пока детали абсолютно новые, то их разъединение иногда превращается в мучение – так они крепко цепляются друг за друга. На помощь для их расцепления приходит специальный инструмент, который есть в наборе.

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

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

Для начинки основного управляющего блока японцы сделали свою версию платы Arduino, доработав ее, и назвали Studuino. Они добавили в нее драйвер управления DC двигателями и вывели линейку под разъемы на 3 контакта (стандартный BLS-3, такие ставят на сервоприводы). На каждом таком разъеме присутствует питание: плюс и минус, а также один из пинов Arduino платы. Все разъемы подписаны, так что ошибиться при подключении к ним тяжело.

Платформа Studuino
Платформа Studuino

Для совместимости со стандартными шилдами Arduino на штатных местах присутствуют разъемы, такие же как и на Arduino.

Основным отличием от платы Arduino является то, что напряжение питания контроллера составляет 3,3В. Объясняется это тем, что питание центрального управляющего модуля осуществляется через поставляемый в комплекте батарейный отсек на 3 батарейки AA (в сумме дают 4,5В). Аккумулятора, к сожалению, не предусмотрено, его нельзя даже купить на сайте у производителя в качестве дополнительной опции.

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

Центральный блок ArTeC
Центральный блок ArTeC

В качестве исполнительных механизмов производитель предлагает два DC мотора с редукторами и до 8-ми сервоприводов собственной конструкции (их количество может меняться в зависимости от комплектации набора). Моторы подключаются через специальные разъемы, которые есть на плате.

DC моторы с редукторами и их подключение
DC моторы с редукторами и их подключение

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

Простой робот-машинка с двумя DC двигателями
Простой робот-машинка с двумя DC двигателями

Управление двигателями осуществляется через задание логических состояний выводов D2, D4, D3 для двигателя, который подключен в разъем M1 и D7, D8, D5 для двигателя подключенного к разъему М2 на плате Studuino. Именно к этим выводам разработчики платы Studuino параллельно подключили микросхему-драйвер управления двигателями. Использовать эти выводы, подключив туда свою схему, и при этом одновременно использовать двигатели, не получится.

Рассмотрим как управлять двигателем, который подключен к разъему М1. Устанавливая разное логическое состояние выводов D2 и D4, можно добиться вращения двигателя в нужную сторону. Например: при D2 – 1 и D4 – 0 двигатель вращается по часовой стрелке, а если D2 – 0 и D4 – 1, то двигатель вращается против часовой стрелки. Также еще необходимо задать скорость вращения двигателя, установив значение ШИМ на выводе D3 – оно может быть в диапазоне от 0 до 255. При значении равным 0 двигатель не будет вращаться, а при значении 255 получим максимальную скорость вращения. Драйвер двигателя также допускает мгновенное изменение направления вращения двигателя и поддерживает режим торможения мотора. Для торможения двигателя следует задать на выводах D2 и D4 одинаковое логическое состояние равное 1.

Кстати, выводов D3 и D5 нет в разъемах, которые имеют по 3 контакта (GND, VCC, SIG) на плате Studuino, но они есть на стандартном разъеме Arduino, который разработчики оставили для сохранения совместимости платформ.

Аналогичным образом осуществляется управление двигателем, который подключен к разъему M2. Для него направление вращения задается через состояние выводов D7 и D8, а скорость вращения выводом D5.

Самая простая программа, которую я написал, заставила робота-машинку двигаться вот так:


Сервоприводы можно подключать в разъемы, которые выделены на плате красным цветом: D2, D4, D7, D8, D9, D10, D11, D12.

Подключение сервоприводов
Подключение сервоприводов

Именно на этих выводах у микроконтроллера AtMega168 присутствует ШИМ, который нужен для управления сервоприводами. Если подключить максимальное количество сервоприводов, то можно получить какую-нибудь интересную конструкцию. Я собрал ради демонстрации простой аналог шагающего робота и попытался его чуть-чуть запрограммировать. Результат можно увидеть на видео:


Единственное о чем нужно помнить, что если планируется использовать одновременно и DC моторы и сервоприводы, то использовать одновременно 2 DC мотора и 8 сервоприводов не получится, так как у них есть общие выводы, управляемые микроконтроллером. Можно сделать следующие конфигурации: 2 DC мотора + 4 сервопривода, 1 DC мотор + 6 сервоприводов или использовать только 8 сервоприводов.

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

Датчики в наборе
Датчики в наборе

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

Вот так работает датчик касания:


А так работает датчик освещенности:


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

Расширение возможностей набора
Расширение возможностей набора

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

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

Пиктограммное программирование
Пиктограммное программирование

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

Когда этот уровень будет освоен или его возможностей станет недостаточно для решения поставленных задач, то можно перейти на следующую ступеньку и начать использовать среду программирования Scratch для платформы Studuino.

Программирование на Scratch для Studuino
Программирование на Scratch для Studuino

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

Когда и этих возможностей будет не хватать, то можно перейти к программированию в среде Arduino IDE и получить полный контроль над всеми аппаратными возможностями платформы Studuino. Для использования платы Studuino в среде Arduino IDE необходимо произвести настройку среды по инструкции с сайта производителя (чтобы среда увидела в списке поддерживаемых плат платформу Studuino).

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

А теперь попробуем решить небольшую практическую задачу с использованием этого набора.

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


Перемещение робота вперед и назад можно сделать с помощью DC двигателей или с помощью сервоприводов. Сервоприводы решают вопрос, как остановить робота в нужном месте (можно точно задавать угол поворота сервопривода). У этого решения есть ограничение (будем использовать сервопривод из набора с закрепленным на нем колесом) – сервоприводы не могут поворачиваться на угол более чем 180 градусов и таким образом перемещение нашего робота будет ограничено половиной длины оборота колеса на сервоприводе, а обычно хочется большего.

Будем использовать DC мотор с редуктором из набора для перемещения вперед и назад. У этих моторов нет обратной связи. После запуска двигателя мы не можем сказать какое расстояние проехал робот. Можно засечь время, за которое при работе двигателя робот проходит требуемое расстояние и эти задержки использовать в программе для остановки робота в нужном месте. У этого метода есть один существенный недостаток – скорость вращения двигателя напрямую зависит от напряжения, которое на него подается и от требуемого усилия. Так как в роботе используются батареи, которые через некоторое время немного разрядятся и их напряжение станет меньше, то за то же самое время робот начнет проходить меньшее расстояние, и нужно будет опять подбирать время.

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

Я внес изменения в мою конструкцию, добавив туда датчики касания на роботе, и поставил элементы на пути следования робота так, чтобы робот при движении касался их датчиками касания.


На видео видно, что робот доезжает до конца «склада» и касается датчиком «стены», потом едет назад до противоположной «стены» склада и там тоже касается. После этого процесс повторяется. Таким образом, здесь есть две точки, которые робот точно «знает» – это точки, когда происходит срабатывание датчика касания.

Программа с датчиками касания на языке Scratch для Studuino
Программа с датчиками касания на языке Scratch для Studuino

Иногда этого достаточно, чтобы решить поставленную задачу. Но мы же хотим больше!

Я внес изменения в конструкцию и программу робота и вот что у меня получилось:


Рассмотрим более подробно, что же такого большего я решил потребовать от робота. Я решил, что мне недостаточно двух точек остановки и хочется больше. Для этого я использовал ИК-датчик, который входит в комплект набора. Я закрепил его на движущейся платформе, а снизу расположил полоску бумаги c предварительно нарисованными маркером черными линиями, на которых робот должен будет останавливаться. У робота я также убрал один из датчиков касания (с правой стороны).

Робот с ИК-датчиком
Робот с ИК-датчиком

В результате у меня получилась классическая схема устройства одной из осей 3D-принтера или станка с ЧПУ. При включении принтер не знает, где он находится и едет до упора в одну сторону (до касания концевым выключателем), а после этого считает эту точку нулем и начинает от нее отсчитывать свое положение по этой оси.

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

Программа для робота с ИК-датчиком на языке Scratch для Studuino
Программа для робота с ИК-датчиком на языке Scratch для Studuino

Если внимательно посмотреть на программу, то можно увидеть непонятные значения 40 и 50, с которыми происходит сравнение значения ИК-датчика. Для понимания этих цифр необходимо немного вспомнить о том, какие сигналы можно считать с разных датчиков. Есть датчики цифровые и аналоговые. Цифровые датчики имеют фиксированное число положений и переходят из одного положения в другое резко и без промежуточных значений. В основном используются датчики с двумя положениями. Датчики касания является наиболее ярким примером цифрового датчика: пока датчик не нажат он выдает логическое значение 1, а когда нажат – логическое значение равное 0 (в этом конструкторе и с этими датчиками используется именно такая схема, но бывают схемы, где это сделано наоборот: нажат – 1, не нажат – 0). Аналоговые датчики описывают некоторую неразрывную связь сигнала и напряжения или тока на выходе и не имеют фиксированных значений. ИК-датчик выдает аналоговый сигнал освещенности, который может зависеть от разных факторов, таких как внешняя засветка, напряжение батареи питания и даже температуры окружающей среды.

Аналоговый сигнал линейно преобразуется в число, для Scratch для Studuino – в диапазоне от 0 до 100. Это сделано для упрощения работы с ними конечным пользователям (для обучения детей в первую очередь разработана эта среда программирования). В среде Arduino IDE выдается «честный» диапазон значений от 0 до 1023, что соответствует 10-битному АЦП преобразователю, который присутствует в контроллере Atmega168. Если посмотреть на различия в графиках между цифровым и аналоговым сигналом, то становится понятно, почему аналоговый сигнал это диапазон значений.

Аналоговый (слева) и цифровой (справа) сигнал
Аналоговый (слева) и цифровой (справа) сигнал

ИК-датчик, который используется для определения черной линии-метки, работает по принципу улавливания отраженного сигнала. На рисунке можно четко увидеть два элемента: ИК-светодиод и ИК-фотоприемник.

ИК-датчик
ИК-датчик

Если перед датчиком поставить какое-либо препятствие, от которого будут отражаться ИК лучи, то чем лучше они будут отражаться от препятствия (в зависимости от расстояния до препятствия или типа препятствия), тем больше их уловит ИК-фотоприемник и датчик вернет большее значение пользователю при его опросе.

Цифры 40 и 50 подобраны опытным путем. В моем случае датчик показывал, находясь над белой поверхностью, значения примерно 65-75. Над черной поверхностью датчик возвращал значения в диапазоне 18-25. Цифра 40 – это момент, когда датчик начинает переходить с белой поверхности на черную, а цифра 50 – момент перехода с черной поверхности на белую. Эти цифры взяты с небольшим запасом, чтобы перекрыть погрешность измерения датчика. При разработке реальной конструкции, необходимо учитывать условия, в которых будут сниматься показания с датчика (внешнее освещение, расположение датчика и т.д.), потому что все эти факторы будут оказывать влияние на показания датчика. Возможно придется разработать алгоритм постоянной корректировки данных значений в зависимости от внешних условий.

Подведу небольшой итог. Было рассмотрено два возможных варианта решения задачи для перемещения робота по складу. Оба этих варианта требовали дополнительных внешних «меток», по которым ориентировался робот. А можно ли как-нибудь обойтись без них? Чтобы робот, например, знал, на какой угол повернулся вал двигателя и, в зависимости от значения угла, принял решение об остановке или движении далее. Существует простое решение этой проблемы – использование двигателя с энкодером (датчиком угла поворота).

В наборе есть DC моторы с редуктором, но они не имеют энкодера. В голову пришла мысль: а может попробовать сделать простой энкодер из деталей конструктора, тем более что шестерни из набора имеют достаточно крупный модуль (размер зуба)?

Основные элементы энкодера
Основные элементы энкодера

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

Тестовая модель энкодера
Тестовая модель энкодера

Как видно на изображении выше, ИК-датчик закреплен так, чтобы шестерня при вращении пересекала его рабочую область измерения своими зубьями. Напротив (с другой стороны шестерни) ИК-датчика я дополнительно поставил препятствие, работающее на отражение ИК-лучей, чтобы получить более корректные данные с датчика. Когда шестерня будет вращаться и ИК-датчик измерять отражение сигнала, то будут большие значения, когда перед датчиком будет зуб и меньшие, когда – «дырка» между зубами шестерни.

Программа для работы с энкодером разработана в среде Arduino IDE. Для проверки работоспособности моей идеи я написал простую программу, которая запускала двигатель на вращение с постоянной скоростью и непрерывно выводила значения с ИК-датчика на отладочную консоль.

Текст программы
#define M1_A        2       // Управляющая ножка 1 для двигателя 1
#define M1_B        4       // Управляющая ножка 2 для двигателя 1
#define M1_PWM      3       // Управляющая ножка для задания скорости двигателя 1
#define SENSOR_PIN  A4      // Ножка на которую подключен IR-sensor

void setup() {
  Serial.begin(9600);       // Инициализация консоли для вывода туда данных
  pinMode(M1_A, OUTPUT);    // Установка управляющих пинов контроллера в режим "вывода данных"
  pinMode(M1_B, OUTPUT);    
  analogWrite(M1_PWM, 100); // Задание скорости вращения двигателя
  digitalWrite(M1_A, HIGH); // Запуск двигателя на вращение
  for (int i=0; i < 2000; i++) {    // В цикле 2000 раз опросить значение ИК-датчика
    Serial.println(analogRead(SENSOR_PIN));   // Опросить и вывести значения в консоль
  }
  digitalWrite(M1_A, LOW);  // Остановить вращение двигателя
}

void loop() {
}


По данным, которая программа вывела в консоль, у меня получился вот такой график:

График изменения значений ИК-датчика при вращении шестерни
График изменения значений ИК-датчика при вращении шестерни

Характер графика напоминает форму зубцов у шестерни, что говорит о том, что действительно такие данные можно использовать для контроля вращения двигателя, используя зубчатую шестерню из набора конструктора как диск энкодера. Для исключения «дребезга» используется гистерезис, который реализован следующим образом (обозначения на графике): MIDDLE – это среднее значение между максимальной и минимальной величиной показаний ИК-датчика, WIDTH – это отклонение от MIDDLE к большему или меньшему значению для создания определенной «полосы погрешности» измерений сигнала (общая ширина этой полосы равняется 2*WIDTH). MIDDLE и WIDTH будут использоваться в алгоритме работы контроля вращения двигателя. Алгоритм подсчета зубцов в шестерне во время вращения двигателя можно представить следующим образом:

Алгоритм подсчета зубцов в шестерне во время вращения двигателя
Алгоритм подсчета зубцов в шестерне во время вращения двигателя

В алгоритме используются следующие обозначения:

  • prev_state – предыдущее состояние шестерни;
  • cur_state – текущее состояние шестерни;
  • count – количество подсчитанных зубцов шестерни;
  • tmp – показания ИК-датчика.

Принцип подсчета зубцов шестерни в этом алгоритме основан на постоянном считывания показания с ИК-датчика и изменения значения переменной cur_state при переходе уровня сигнала за верхнюю или нижнюю линию «полосы погрешности». Если значение переходит верхнюю границу, то переменная cur_state становится равна 1, что означает зубец шестерни, а при переходе за нижнюю границу, переменная cur_state становится равна 0, что означает провал между зубьями шестерни. Прибавление в переменной count происходит только при изменении состояния переменной cur_state.

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

Текст программы
#define M1_A        2       // Управляющая ножка 1 для двигателя 1
#define M1_B        4       // Управляющая ножка 2 для двигателя 1
#define M1_PWM      3       // Управляющая ножка для задания скорости двигателя 1
#define SENSOR_PIN  A4      // Ножка на которую подключен IR-sensor

#define MIDDLE      550     // Серединное значение диапазона по которому определяется зубец или 
                            // "дырка"
#define WIDTH       50      // Значение ширины погрешности от середины диапазона, чтобы исключить 
      // ложный подсчет значений из-за погрешности считывания с аналогового 
      // датчика

int enc_tooth = 0;          // Переменная, содержащая количество зубцов шестеренки
int cur_state = 0;          // Текущее состояние шестеренки: зубец (1) или "дырка" (0)
int prev_state = 0;         // Предыдущее состояние шестеренки
int tmp;                    // Переменная для хранения временных значений вычислений

void init_enc() {
  enc_tooth = 0;
  cur_state = 0;
  prev_state = 0;

  // Прочитать текущее состояние шестеренки и инициализировать значение переменной cur_state
  // то есть если зубец, то переменная = 1, а иначе там "дырка" и переменная не меняет свое 
  // значение из 0 как она и была присвоена изначально
  if (analogRead(SENSOR_PIN)>MIDDLE) {
    prev_state = 1;
    cur_state = 1;
    enc_tooth++;
  }  
}

void wait_by_count(int count) {
  // Выполнить цикл пока не будет достигнуто count, в котором производить измерения с датчика IR 
  // и в зависимости от его значения увеличивать количество зубцов или "дырок"
  while (enc_tooth <= count) {
    // Считываем показания с IR-датчика во временную переменную 
    tmp = analogRead(SENSOR_PIN);

    // Если полученное значение лежит выше середины + ширина погрешности, то значит там зубец
    if ((tmp + WIDTH) > MIDDLE) {
      cur_state = 1;
    } else {
    // Если полученное значение лежит ниже середины - ширина погрешности, то значит там "дырка"
      if ((tmp - WIDTH) < MIDDLE) {
        cur_state = 0;
      }
    }

    // Если текущее значение не совпадает с предыдущим, то значит шестеренка повернулась уже 
    // настолько, что вместо зуба появилась "дырка" или наоборот - вместо "дырки" появился зуб
    if (cur_state != prev_state) {
    // Увеличиваем количество зубов, если прибавить 0 к какому-то значению, то ее значение не 
    // поменяется
      enc_tooth += cur_state;
    
    // Изменяем предыдущее состояние для сравнения при новом измерении
      prev_state = cur_state;
    }
  }
}

void setup() {
  // Инициализировать монитор порта для последующего вывода данных туда
  Serial.begin(115200);

  // установить ножки для управления двигателем в состояние "выход"
  pinMode(M1_A, OUTPUT);
  pinMode(M1_B, OUTPUT);

  // обнулить значения переменных
  init_enc();

  // установить скорость вращения двигателя
  analogWrite(M1_PWM, 100);
 
  // запустить двигатель на вращение, переведя значение одной из ножек 1 (по умолчанию они в 
  // состоянии 0)
  digitalWrite(M1_A, 1);

  // ждать, пока не провернуться 30 зубцов шестерни
  wait_by_count(30);
  
  // Остановить вращение двигателя
  digitalWrite(M1_B, 1);

  Serial.print("Количество зубцов = ");
  Serial.println(enc_tooth);

}

void loop() {
}


Программа запускает двигатель и ждет, пока шестерня не провернется на 30 зубцов – как раз такое количество зубцов в шестерне, и после этого останавливает мотор. Ниже представлено видео, которое демонстрирует работу программы:


По белому кусочку бумаги, который приклеен к шестерне, можно отследить ее оборот на 360 градусов.

На этом я хочу закончить данную статью. В заключении могу сказать, что я не остановился просто на модели энкодера. Была собрана полноценная роботоплатформа с двумя моторами и двумя энкодерами (по одному на каждое колесо) и сделана программа для синхронизации вращения колес по данным энкодеров, но это уже тема для другой статьи…

Роботоплатформа с энкодерами на базе ИК-датчиков
Роботоплатформа с энкодерами на базе ИК-датчиков

Как образовательный конструктор по робототехнике это неплохой вариант для тех детей (ну и взрослых), которые хотят приобщиться к таким занятиям, тем более что производитель сделал все, чтобы максимально расширить возрастной диапазон тех, кто может пользоваться этим продуктом (наличие разных по уровню сред программирования). Можно начать с самых простых и элементарных элементов, а после получения базовых знаний перейти на другой уровень освоения робототехники и электроники.
Теги:
Хабы:
+4
Комментарии13

Публикации

Изменить настройки темы

Истории

Ближайшие события

Weekend Offer в AliExpress
Дата20 – 21 апреля
Время10:00 – 20:00
Место
Онлайн
Конференция «Я.Железо»
Дата18 мая
Время14:00 – 23:59
Место
МоскваОнлайн