Привет всем хабражителям!
Наверняка, многим в детстве нравились машинки на радиоуправлении. Да и не только в детстве: я уверена, что и в возрастной категории 30+ найдётся масса любителей лихой езды в миниатюрном масштабе. Вот и я с детства мечтала о такой машинке, однако девочкам обычно дарят не машинки, а кукол, и моя мечта оставалась невоплощённой до недавнего времени. Но теперь я выросла, и простая радиоуправляемая машинка показалась мне достаточно скучной. И в один прекрасный день мне в голову пришла идея, как себя развлечь и заодно модернизировать машинку: я решила организовать её управление со смартфона по каналу WiFi.
Я загорелась этой идеей, тем более что целевой смартфон – Nokia Lumia 620, а программированием на C# я увлекалась и раньше. Работа над программной частью обещала быть безоблачной и увлекательной, но вот с аппаратной частью всё обстояло немного сложнее, и я стала гуглить и изучать матчасть.
Так как я не электрик, чтобы организовать работу аппаратной части моей машинки, пришлось достаточно долго шариться по просторам интернета в поисках гайдов по основам роботостроения для блондинок. Как всегда, помог любимый Хабр, а именно этот пост, написанный языком, понятным даже для очень далёкого от электроники человека. Там всё расписано очень хорошо, поэтому на аппаратной части подробно останавливаться не буду, расскажу лишь об отличиях моей машинки от описанного там WiFi-бота:
• Так как машинка предназначена для развлечения, а не для мониторинга помещений, ей ни к чему веб-камера (хотя, может быть, в дальнейшем стоит развить и эту тему…). То же касается и системы автономной зарядки. Полного заряда аккумуляторов (я использовала Sanyo Eneloop AA ёмкостью 2500 мАч) вполне хватает по времени, чтобы вдоволь наиграться.
• Чтобы вся начинка влезла в корпус машинки, а также чтобы не особенно мучиться с пайкой (я же блондинка, всё-таки…) я использовала немного меньший по размеру микроконтроллер Arduino Nano v3.0 с USB-интерфейсом. Наличие USB-интерфейса, помимо подключения МК к роутеру без пайки, ещё и очень облегчает процесс программирования ардуины: подключили к компьютеру -> залили скетч прямо из Arduino IDE -> наслаждаемся. Роутер я тоже брала другой, TP-LINK WR703N, он чуть меньше TL-MR3020, но в остальном они практически идентичны.
• Так как МК к роутеру мы не припаиваем, а подключаем через USB, то и проброс UART’а производится виртуально, с помощью демона ser2net, который по умолчанию присутствует в прошивке OR-WRT нашего роутера. Всего-навсего нужно добавить в файл ser2net.conf на роутере следующую строчку (или заменить, если на 2000 порт уже что-то прописано):
В принципе, не обязательно использовать именно 2000 порт, можно взять любой свободный.
Итак, обобщу. Аппаратная часть машинки в моей версии кухонного роботостроения «готовится» по такому рецепту:
Ингредиенты:
Джипик на радиоуправлении, роутер TP-LINK TL-MR3020 или WR703N, микроконтроллер Arduino Nano, драйвер двигателей L293D, 2 недлинных microUSB кабеля, 4 аккумулятора.
Приготовление:
1. Перепрошить роутер прошивкой OR-WRT и провести первичную настройку.
2. Настроить связь с ардуиной с помощью ser2net.
3. Выдрать из машинки плату радиоуправления.
4. Подвести питание к роутеру. Как вариант, это можно сделать самым простым и очевидным образом: так как запитка роутера производится через его microUSB интерфейс, отрезаем от одного кабеля кусочек нужного размера с тем концом, на котором маленький разъём. Отрезанный конец подпаиваем к проводкам, идущим из батарейного отсека машинки (распиновка проводов: pin1 – V+ (5В), pin5 – V- (GND)), разъём втыкаем в соответствующее гнездо.
5. Подключить МК к роутеру с помощью второго microUSB кабеля и припаять нужные (см. дальше) выходы МК к соответствующим входам микросхемы-драйвера двигателей, а её выходы – непосредственно к двигателям машинки.
Структурная схема
Вуаля, сама машинка готова! Повторюсь, это очень схематическое и упрощённое описание порядка действий, более подробные инструкции – в этом источнике.
А теперь самая интересная и творческая (по крайней мере, для меня) часть работы – создание программы, которая будет всем этим счастьем управлять. Её суть заключается в том, чтобы при нажатии на управляющие кнопки посылать соответствующие сигналы на роутер, который, в свою очередь, передаст их ардуине, а она через драйвер двигателей управляет двигателями машинки. То есть программная часть состоит из двух компонентов: программы для смартфона и программы для МК.
Я долго думала, каким именно образом лучше посылать машинке сигналы, перепробовала кучу вариантов и остановилась на таком: при нажатии на определённую управляющую кнопку машинке будет отправляться сигнал о начале движения (активации соответствующего двигателя), а при отпускании – о прекращении движения (деактивации двигателя). Я закодировала управляющие сигналы следующим образом:
1 – начать движение вперёд, 2 – начать движение влево, 3 – начать движение назад, 4 – начать движение вправо. И, соответственно, 5 – закончить движение вперёд, 6 – закончить движение влево, 7 – закончить движение назад, 8 – закончить движение вправо. Кнопки «вперёд» и «назад» работают с маршевым двигателем машинки, а «влево» и «вправо» — с поворотным.
После этого уже можно спокойно приступать к программированию ардуины, которое заключается в том, чтобы, пока она включена, в цикле проверять наличие входящих данных и при принятии определённой цифры устанавливать HIGH или LOW уровень на соответствующей ножке. Код скетча элементарен, switch/case нам в помощь.
А вот мобильное приложение будет посложнее. Прежде всего, нам нужно связаться с машинкой, именно с нашей машинкой, если вокруг будет несколько доступных WiFi-сетей. Для начала настроим роутер на работу в режиме точки доступа с помощью его нового веб-интерфейса.
Как видно, после перепрошивки IP-адрес роутера в режиме точки доступа – 192.168.218.1 – не меняется. Именно по этому адресу и будем посылать данные на роутер. Придумаем сети нашей машинки SSID и пароль, чтобы избежать подключения к машинке других устройств, кроме нашего. Конечно, нельзя исключить возможность взлома нашей сети, но крайне маловероятно, что кто-то будет страдать такой ерундой, по вполне очевидным причинам.
Теперь наша сеть уникальна и подключиться к ней может только наш смартфон. Поэтому первое, что должно сделать наше приложение – это проверить подключение телефона к машинке. И если проверка окажется успешной – можно стартовать (кнопка старта, неактивная по умолчанию, активируется), а если нет – нужно отправить юзера проверить подключение.
foreach (var network in new NetworkInterfaceList())//Для каждой доступной сети
{
if ((network.InterfaceType == NetworkInterfaceType.Wireless80211) && (network.InterfaceState == ConnectState.Connected) && (network.InterfaceName == "WiFi_Car"))//если тип сети – беспроводная стандарта ІЕЕЕ 80211 (WiFi), а SSID совпадает с SSID машинки
{
CarName.Text = network.InterfaceName; //записать SSID сети в текстовое поле
StartButton.IsEnabled = true; //и сделать кнопку «СТАРТУЕМ» активной
break;
}
else
{
CarName.Text = "Нет доступных машин. Проверьте WiFi-подключение.";
StartButton.IsEnabled = false;
}
}
Вот макет стартовой страницы приложения:
Когда мы успешно подключились к машинке, по нажатию кнопки «СТАРТУЕМ!» происходит переход на основную страницу приложения, с которой мы и будем управлять машинкой:
И вот он — момент истины: передача машинке управляющих сигналов. Процесс этот мы организуем с помощью сокета. При открывании этой страницы устанавливается сокетное соединение с роутером. Для этого нам нужно создать несколько компонентов:
IPEndPoint routerPort = new IPEndPoint(IPAddress.Parse("192.168.218.1"), 2000); //создание удалённого узла для сокета, с указанием IP-адреса роутера и его порта, который связан с МК через ser2net
Socket S = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); //создание и инициализация сокета
SocketAsyncEventArgs socketEventArgs = new SocketAsyncEventArgs();//создание объекта-аргумента методов соединения и передачи данных сокета
byte[] buf = new byte[1]; //буфер – массив байтов для передачи (в нашем случае имеет размер 1 байт)
После этого выполним подключение:
socketEventArgs.RemoteEndPoint = routerPort; //инициализация созданного удалённого узла как конечной точки сокета
S.ConnectAsync(socketEventArgs); //подключение к удалённому узлу
socketEventArgs.SetBuffer(buf, 0, 1); //инициализация буфера данных для передачи
Функции улавливания нажатия и отпускания управляющих кнопок реализуются, соответственно, методами MouseEnter и MouseLeave. Для исключения аварийных ситуаций, связанных, например, с одновременным нажатием кнопок противоположных направлений, введём для каждой кнопки флаг, который будет индикатором движения машинки в соответствующем направлении. Функции нажатия и отпускания у всех кнопок-стрелочек имеют одинаковую структуру.
Нажатие:
private void [название кнопки]_MouseEnter (object sender, System.Windows.Input.MouseEventArgs e)
{
if (![флаг выбранного направления] && ![флаг противоположного направления])
{
buf[0] = [цифра-команда начала движения для выбранного направления];
Socket.SendToAsync(socketEventArgs);
[флаг выбранного направления] = true;
}
}
Отпускание:
private void [название кнопки]_MouseLeave(object sender, System.Windows.Input.MouseEventArgs e)
{
if ([флаг активного направления])
{
buf[0] = [команда завершения движения для выбранного направления];
Socket.SendToAsync(socketEventArgs);
[флаг активного направления] = false;
}
}
Здесь стоит отметить, что пересылку данных удалённому узлу следует производить именно с помощью метода SendToAsync и не спутать его с подобным методом, SendAsync. Разница состоит в том, что SendAsync посылает данные с нашего сокета на другой сокет, а SendToAsync – просто на удалённый узел, что нам и нужно, поэтому если спутать методы – данные не будут пересланы.
Также отмечу, что при тест-драйве машинки с ноутбука я обнаружила, что при одновременной работе обоих двигателей, маршевого и поворотного, время от времени происходят аварийные ситуации вроде потери связи с роутером. Подозреваю, что это происходит из-за проседания напряжения на внутренностях машинки, потому что подобные баги также были замечены при низком заряде аккумуляторов. Как-никак, столько потребителей питаются от несчастных 4 батареек. Поэтому я сняла с поворотных колёс пружинку, которая возвращала их в первоначальное положение, чтобы можно было запускать моторы последовательно, и в мобильном приложении возможность одновременной работы обоих двигателей намеренно не предусматривала. Пока так, а когда найдётся способ усовершенствовать систему питания машинки – тогда можно будет задуматься и о расширении её функциональных врзможностей.
Теперь дело осталось за малым: реализовать закрытие сокетного подключения и возврат на стартовую страницу, когда мы накатаемся. У меня эти функции выполняет кнопка в виде ключа зажигания:
private void key_MouseEnter(object sender, System.Windows.Input.MouseEventArgs e)
{
S.Close(); //отключение и закрытие сокетного подключения
NavigationService.Navigate(new Uri("/MainPage.xaml", UriKind.Relative));
}
Вот, в общем-то, и всё, теперь можно спокойно играться! Для полного погружения можно ещё добавить кнопку включения/отключения музыки, и поставить на проигрывание какую-нибудь подходящую песенку. Я, например, не удержалась, и моя машинка ездит под Lil Jon & the Eastside Boyz — Get Low.
Предлагаю вашему вниманию короткий видеоролик о результатах моего вдохновенного труда:
P.S.: Буду очень рада конструктивной критике и оптимизационным предложениям. Но не судите строго, пожалуйста, это мой первый проект такого масштаба.
P.P.S.: Огромное спасибо svavan за помощь с аппаратной частью! Так как я с электроникой и процессом перепрошивки роутеров была знакома очень смутно, я бы не справилась без этой помощи.
Автор: Yaliny