Традиционно роботы, построенные на платформе Lego Mindstorms EV3, программируются с использованием графической среды LabVIEW. В этом случае программы запускаются на контроллере EV3 и робот работает автономно. Здесь я расскажу про альтернативный способ управления роботом — использование платформы .NET, запущенной на компьютере.
Но прежде чем мы перейдем непосредственно к программированию, давайте рассмотрим случаи, когда это может быть полезно:
- Требуется удаленное управление роботом с ноутбука (например, по нажатию кнопок)
- Требуется собирать данные с контроллера EV3 и обрабатывать их на внешней системе (например, для IoT-систем)
- Любые другие ситуации, когда хочется написать алгоритм управления на .NET и запускать его с компьютера, подключенного к контроллеру EV3
LEGO MINDSTORMS EV3 API for .NET
Управление контроллером EV3 из внешней системы осуществляется путем отправки команд в последовательный порт. Сам формат команд описан в Communication Developer Kit.
Но реализация этого протокола вручную — дело скучное. Поэтому можно воспользоваться готовой .NET-оберткой, которую заботливо написал Brian Peek. Исходные коды этой библиотеки размещены на Github, а готовый к использованию пакет можно найти в Nuget.
Подключение к контроллеру EV3
Для связи с контроллером EV3 используется класс Brick
. При создании этого объекта в конструктор требуется передать реализацию интерфейса ICommunication
— объект, описывающий способ подключения к контроллеру EV3. Доступны реализации UsbCommunication
, BluetoothCommunication
и NetworkCommunication
(подключение через WiFi).
Наиболее популярный способ подключения - через Bluetooth. Рассмотрим поподробнее этот способ подключения.
Прежде чем мы сможем программно подключиться к контроллеру через Bluetooth, контроллер необходимо подключить к компьютеру, используя настройки операционной системы.
После того, как контроллер подключен, идём в настройки Bluetooth и выбираем вкладку COM-порты
. Находим наш контроллер, нам нужен исходящий порт. Его и будем указывать при создании объекта BluetoothCommunication
.
Код для подключения к контроллеру будет выглядеть так:
public async Task Connect(ICommunication communication)
{
var communication = new BluetoothCommunication("COM9");
var brick = _brick = new Brick(communication);
await _brick.ConnectAsync();
}
Опционально можно указать таймаут подключения к контроллеру:
await _brick.ConnectAsync(TimeSpan.FromSeconds(5));
Подключение к блоку через USB или WiFi осуществляется аналогично, за тем исключением, что используются объекты UsbCommunication
и NetworkCommunication
.
Все дальнейшие действия, выполняемые с контроллером, осуществляются через объект Brick
.
Покрутим моторами
Для выполнения команд на контроллере EV3 обратимся к свойству DirectCommand
объекта Brick
. Для начала попробуем запустить моторы.
Предположим, что наш мотор подключен к порту A контроллера, тогда запуск этого мотора на мощности 50% будет выглядеть так:
await _brick.DirectCommand.TurnMotorAtPowerAsync(OutputPort.A, 50);
Есть и другие методы для управления мотором. Например, можно повернуть мотор на заданный угол, используя методы StepMotorAtPowerAsync()
и StepMotorAtSpeedAsync()
. Всего доступно несколько методов, которые являются вариациями на режимы включения моторов — по времени, скорости, мощности и т.д.
Принудительная остановка осуществляется методом StopMotorAsync()
:
await _brick.DirectCommand.StopMotorAsync(OutputPort.A, true);
Второй параметр указывает на использование тормоза. Если его установить в false
, то мотор будет останавливаться «накатом».
Чтение значений с датчиков
Контроллер EV3 имеет четыре порта для подключения сенсоров. Дополнительно к этому, моторы также имеют встроеные энкодеры, что позволяет использовать их как сенсоры. В итоге мы имеем 8 портов, с которых можно считывать значения.
Доступ к портам для считывания значений можно получить через свойство Ports
объекта Brick
. Ports
— это коллекция портов, доступных на контроллере. Поэтому для работы с конкретным портом нужно его выбрать. InputPort.One
...InputPort.Four
— это порты для датчиков, а InputPort.A
...InputPort.D
— это энкодеры моторов.
var port1 = _brick.Ports[InputPort.One];
Датчики в EV3 могут работать в разных режимах. Например, датчик цвета EV3 можно использовать для измерения внешнего освещения, измерения отраженного света или для определения цвета. Поэтому, чтобы «сообщить» сенсору о том, как именно мы хотим его использовать, нужно задать его режим:
_brick.Ports[InputPort.One].SetMode(ColorMode.Reflective);
Теперь, когда датчик подключен и режим его работы задан, можно считать из него данные. Получить можно «сырые» данные, обработанное значение и значение в процентах.
float si = _brick.Ports[InputPort.One].SIValue;
int raw = _brick.Ports[InputPort.One].RawValue;
byte percent = _brick.Ports[InputPort.One].PercentValue;
Свойство SIValue
возвращает обработанные данные. Здесь все зависит от того, какой именно датчик используется и в каком режиме. Например, при измерении отраженного света мы получим значения от 0
до 100
в зависимости от интенсивности отраженного света (черный/белый).
Свойство RawValue
возвращает «сырое» значение, полученное с АЦП. Иногда удобнее использовать именно его для последующей обработки и использования. Кстати, в среде разработки EV3 тоже есть возможность получения «сырых» значений — для этого нужно воспользоваться блоком из синей панели.
Если используемый датчик предполагает получение значений в процентах, то можно также воспользоваться свойством PercentValue
.
Выполнение команд «пачкой»
Предположим, в нашем распоряжении есть робот-тележка с двумя колесами и мы хотим развернуть его на месте. В этом случае два колеса должны вращаться в противоположном направлении. Если мы воспользуемся DirectCommand
и последовательно отправим две команды контроллеру, между их выполнением может пройти некоторое время:
await _brick.DirectCommand.TurnMotorAtPowerAsync(OutputPort.A, 50);
await _brick.DirectCommand.TurnMotorAtPowerAsync(OutputPort.B, -50);
В этом примере мы отправляем команду для вращения мотора A на скорости 50, после успешного окончания отправки этой команды, повторяем то же самое с мотором, подключенным к порту B. Проблема в том, что отправка команд происходит не моментально, поэтому моторы могут начать крутиться в разное время — пока передается команда для порта B, мотора A уже начнет крутиться.
Если для нас критически важно заставить крутится моторы одновременно, можно отправлять команды контроллеру «пачкой». В этом случае следует воспользоваться свойством BatchCommand
вместо DirectCommand
:
_brick.BatchCommand.TurnMotorAtPower(OutputPort.A, 50);
_brick.BatchCommand.TurnMotorAtPower(OutputPort.B, -50);
await _brick.BatchCommand.SendCommandAsync();
Теперь подготавливается сразу две команды, после чего они отправляются на контроллер одним пакетом. Контроллер, получив эти команды, начнет вращение моторов одновременно.
Что ещё можно сделать
Кроме вращения моторов и считывания значений сенсоров, можно выполнять ещё ряд действий на контроллере EV3. Не буду подробно останаливаться на кадом из них, перечислю только список того, что можно сделать:
CleanUIAsync()
,DrawTextAsync()
,DrawLineAsync()
и др. — манипуляция встроенным экраном контроллера EV3PlayToneAsync()
иPlaySoundAsync()
— использование встроенного динамика для воспроизведения звуковWriteFileAsync()
,CopyFileAsync()
,DeleteFileAsync()
(изSystemCommand
) — работа с файлами
Заключение
Использование .NET для управления роботами Mindstorms EV3 хорошо демонстрирует как технологии «из разных миров» могут работать совместно. В качестве результата исследования EV3 API для .NET было создано небольшое приложение, которое позволяет управлять роботом EV3 с компьютера. К сожалению, аналогичные приложения существуют для NXT, а EV3 они обошли стороной. В то же время они полезны на сорвнованиях управляемых роботов, например в футболе роботов.

Приложение можно загрузить и установить по этой ссылке: EV3 Remote Control.
Исходные коды доступны на Github.