Привет, Хабр!

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

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

Игрушка на Яндекс Маркете
Игрушка на Яндекс Маркете

Робот-доставщик

Модель выполнена в виде настоящего ровера 3 от Яндекса в масштабе 1/64. Инерционный механизм делает из него небольшую игрушку, работающую по принципу детских машинок, которые были, вероятнее всего, у каждого в прекрасные времена более голубого неба и самой зеленой травы.

Работа инерционного механизма
Работа инерционного механизма

Также внутри робота расположены светодиоды для подсветки его «глаз» и периметра контейнера, который, к слову, открывается. Работает все великолепие на 3 батарейках типа LR44.

Подсветка ровера
Подсветка ровера

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

А может сделать его управляемым?

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

На самом деле, все просто. Примерно так же, как из инерционной машинки сделать радиоуправляемую. Только зачем радиоуправление, если можно написать мобильное приложение и управлять ей с телефона?

В итоге решил делать следующее:

  1. Управление ровером должно быть реализовано с помощью мобильного приложения на iOS, которое я напишу на Flutter (про мой первый опыт работы с этим фреймворком можно прочитать в статье про Smart Connect)

  2. Беспроводная передача данных будет производиться с помощью BLE, потому что ранее я имел с ним дело (можете ознакомиться со статьями про SmartLight и SmartPulse) и я считаю его идеальным под подобные проекты (имхо)

  3. Ровер должен ездить вперед, назад, поворачивать влево и вправо, а также включать подсветку по кнопке в мобильном приложении

Далее я решил разобрать робота, чтобы понять, каким объемом пространства внутри него я могу располагать.

Внутри игрушки
Внутри игрушки

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

Что по начинке?

Для реализации поворота ровера нужны были два небольших мотора. Подобные классы транспортных средств поворачивают путем одновременного вращения колес в разные стороны. По такому принципу работают и настоящие роботы-курьеры, и марсоходы, и многие другие роверы. То есть, для поворота направо, необходимо, чтобы колеса ровера на левой стороне корпуса вращались вперед, а колеса на правой стороне - назад (если говорить про направление движения). По сути, в этот момент колеса крутятся одновременно либо по часовой стрелке, либо против часовой. А чтобы ехать вперед или назад, одно из колес должно крутиться по часовой, а другое - против, но мы отвлеклись.

Схема направления движения колес при повороте ровера
Схема направления движения колес при повороте ровера

Для управления моторчиками нужен драйвер. Естественно, так как мы имеем дело с небольшим корпусом игрушки, то все платы надо было подобрать так же миниатюрные. Сказано - сделано, и драйвер L298N mini уже был у меня. На него сразу можно было припаять два моторчика, чему я был несказанно рад.

Родных батареек ровера на два маленьких мотора с высоким крутящим моментом было бы недостаточно (я проверил это в том числе экспериментально), поэтому решил использовать аккумулятор на 350 mAh и зарядную плату к нему. По моей задумке, это все должно было располагаться на месте батарейного отсека ровера - в его голове. Там было достаточно места для размещения аккумулятора, а также можно было спокойно получить доступ к его зарядной плате через крышку отсека, чтобы в дальнейшем заряжать робота.

А что будет всем этим управлять? Для работы с BLE существует крайне удачная линейка ESP32 контроллеров с различными модификациями. Так как в этом проекте мы ограничиваемся сравнительно небольшим пространством для монтажа плат внутрь устройства, я выбрал ESP32-C3 SuperMini, которая по своим размерам не больше зарядной платы аккумулятора.

Разобранный ровер и все то, что в него предстоит засунуть
Разобранный ровер и все то, что в него предстоит засунуть

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

Схема подключения электронных компонентов
Схема подключения электронных компонентов

Прошивка робота

Перейдем к прошивке ровера, полная версия которой лежит у меня в репозитории на GitHub (всегда рад гостям).

По сути все, что нам нужно для реализации передачи данных на ровер, это поднять BLE-сервер и определить сервис и характеристику, в которую мы будем отправлять значения для включения и выключения моторчиков. Я писал код в Arduino IDE с использованием библиотек BLEDevice.h, BLEUtils.h и BLEServer.h

Инициализируем работу с BLE посредством следующего кода:

BLEDevice::init("Yandex Delivery Robot");
pServer = BLEDevice::createServer();
pServer->setCallbacks(new MyServerCallbacks());
BLEService* pMoveService = pServer->createService(BLEUUID((uint16_t)0x170D));
pMoveCharacteristic = pMoveService->createCharacteristic(BLEUUID((uint16_t)0x2A60), BLECharacteristic::PROPERTY_READ | BLECharacteristic::PROPERTY_WRITE);
pMoveCharacteristic->setCallbacks(new MoveCharacteristicCallbacks());
pMoveService->start();
BLEAdvertising* pAdvertising = pServer->getAdvertising();
pAdvertising->addServiceUUID(pMoveService->getUUID());
pAdvertising->setScanResponse(false);
pAdvertising->setMinPreferred(0x06);
pAdvertising->setMinPreferred(0x12);
BLEDevice::startAdvertising();

Здесь мы определяем наименование устройства, а также присваиваем UUID нашему сервису и характеристике pMoveCharacteristic

Запись в характеристики производится с помощью MoveCharacteristicCallbacks, в которой я реализовал switch-case конструкцию. Внизу прикреплен урезанный фрагмент.

class MoveCharacteristicCallbacks : public BLECharacteristicCallbacks {
    void onWrite(BLECharacteristic* pCharacteristic) {
        std::string value = pCharacteristic->getValue();
        if (value.length() > 0) {
            switch (value[0]) {
                case 0x01: // Поворот ровера влево
                    ledcWrite(CHANNEL_IN1, 255); 
                    ledcWrite(CHANNEL_IN2, 0);
                    ledcWrite(CHANNEL_IN3, 255);
                    ledcWrite(CHANNEL_IN4, 0);
                    break;
                case 0x02: // Поворот ровера вправо
                    ledcWrite(CHANNEL_IN1, 0);
                    ledcWrite(CHANNEL_IN2, 255); 
                    ledcWrite(CHANNEL_IN3, 0);
                    ledcWrite(CHANNEL_IN4, 255);
                    break;
            }
        }
    }
}

Мобильное приложение

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

Логика работы кода мобильного приложения также как и в случае с кодом прошивки построена на switch-case конструкции. Я решил расположить на странице управления ровером кнопки в виде стрелок направления (вперед, назад, влево, вправо) и кнопку включения/выключения подсветки в виде горящей лампочки. Когда пользователь нажимает на любую из кнопок направления, в BLE-характеристику подключенного устройства записывается соответствующее значение (к примеру, для поворота ровера влево - 0x01, для правого поворота - 0x02 и тд.).

Positioned(
  bottom: 160,
  left: 80,
  child: controlButton(Icons.arrow_back, 0x01, "Left"),
),
Positioned(
  bottom: 160,
  right: 80,
  child: controlButton(Icons.arrow_forward, 0x02, "Right"),
)

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

onPointerDown: (_) => sendCommand(command),
onPointerUp: (_) => sendCommand(0)

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

Positioned(
  bottom: 40,
  right: 20,
  child: FloatingActionButton(
    onPressed: () {
      setState(() {
        toggleState = !toggleState;
        sendCommand(toggleState ? 3 : 6);
      });
    },
    child: Icon(
      toggleState ? Icons.lightbulb_outline : Icons.lightbulb,
      color: Color.fromRGBO(255, 53, 63, 1),
    ),
    backgroundColor: Colors.white,
  ),
)

Чтобы работать с BLE, я подключил flutter_blue: ^0.8.0 , а сам процесс подключения к устройству построен на определении рассылки от конкретного BLE-сервера по его названию. Запись значений производится по UUID сервиса и характеристики ровера.

List<BluetoothService> services = await widget.device.discoverServices();
var targetService = services.firstWhere((service) =>
    service.uuid == Guid('0000170D-0000-1000-8000-00805f9b34fb'));
var targetCharacteristic = targetService.characteristics.firstWhere(
    (characteristic) =>
        characteristic.uuid ==
        Guid('00002A60-0000-1000-8000-00805f9b34fb'));
await targetCharacteristic.write(value);

После того, как я протестировал работоспособность приложения с микроконтроллером, мне захотелось как-то "довести до ума", чтобы как минимум приложение выглядело чуть более симпатично. На странице управления ровером были только четыре кнопки управления моторами и одна кнопка для подсветки. Это выглядело скучновато, поэтому мне захотелось добавить динамики. Решил изобразить в Adobe Illustrator модельки с направлением ровера.

Изображения направления ровера
Изображения направления ровера

Реализовал обновление картинок по нажатию на соответствующие кнопки управления ровером также через switch по отправляемому с кнопки значению.

setState(() {
  switch (command) {
    case 0x01:
      currentImage = 'assets/Влево.png';
      break;
    case 0x02:
      currentImage = 'assets/Вправо.png';
      break;
    case 0x05:
      currentImage = 'assets/Вперед.png';
      break;
    case 0x04:
      currentImage = 'assets/Назад.png';
      break;
    default:
      break;
  }
});

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

Иконка мобильного приложения
Иконка мобильного приложения

Собираем приложение в Xcode, устанавливаем его на мобильное устройство (в моем случае это iPhone 12 Pro Max) и запускаем для тестирования работоспособности. В итоге мобильное приложение получило следующий внешний вид:

Работа и внешний вид мобильного приложения
Работа и внешний вид мобильного приложения

Пайка, флюс, два колеса

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

Колеса ровера до подключения моторов и после
Колеса ровера до подключения моторов и после

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

Разобранный ровер со срезанными креплениями
Разобранный ровер со срезанными креплениями

Чтобы получить возможность зарядки аккумулятора без разбора всего робота (потому что это садизм), я отпилил батарейный блок. В этом месте я расположу зарядную плату аккумулятора так, чтобы порт USB Type-C выходил на крышку бывшего батарейного блока. В таком случае для зарядки ровера нужно будет только снять эту крышку и подключить кабель к зарядной плате. Также я выпилил места под моторы, потому что места оказалось недостаточно, а мне не хотелось, чтобы их работе что-либо мешало.

Модифицированное днище ровера
Модифицированное днище ровера

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

Пайка электронных компонентов ровера
Пайка электронных компонентов ровера

В итоге не без проблем, но у меня получилось разместить все компоненты внутри ровера. Зарядка устройства производится со стороны нижней части корпуса. Это напоминает мне подключение Magic Mouse, но только я это сделал за неимением других вариантов, а не потому что не люблю людей. Можно было бы расположить зарядную плату как-то иначе, но я не хотел портить внешний вид ровера (днище не считается, потому что его не видно).

Процесс зарядки ровера от сети
Процесс зарядки ровера от сети

Поехали!

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

В качестве демонстрации управления ровером я записал небольшое видео:


Большое спасибо всем, кто прочитал эту статью!

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

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

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

Комментарии (12)


  1. datacompboy
    29.04.2024 12:18
    +2

    DFRobot Beetle ESP32 C6 Mini например уже содержит зарядник на борту для литиевой батарейки и ультракомпактен.


    1. MaxiEnergy Автор
      29.04.2024 12:18

      Спасибо за совет) В следующих проектах попробую ее


  1. YoungSecurityManagerSally
    29.04.2024 12:18
    +1

    Еще не прочитал, но рад, что Вы снова выпустили статью)


    1. MaxiEnergy Автор
      29.04.2024 12:18

      Я очень рад, что Вы продолжаете следить за моими статьями) Большое спасибо за комментарий


  1. Nihiroz
    29.04.2024 12:18
    +5

    Разве это инерционный механизм? Вроде, инерционный механизм это когда внутри есть быстро крутящийся маховик. Разгоняешь машинку и она едет дальше. А тут, скорее, заводной механизм


    1. MaxiEnergy Автор
      29.04.2024 12:18
      +2

      Яндекс пишет, что инерционный. Я решил с ними в этом не спорить. С точки зрения механизма я с вами согласен, это заводной 100%


  1. serafims
    29.04.2024 12:18
    +2

    Не очень понял, как 3.7-4.2 В от аккумулятора превращаются на выключателе в 3.3 В, которые вы попадете на пин 3 платы с контроллером. Наверное, все же на первый контакт надо, куда 5В подается? Или контроллер зарядки имеет стабилизатор?


    1. MaxiEnergy Автор
      29.04.2024 12:18
      +4

      Сейчас заметил в схеме. Ничего сверхъестественного, это просто мой косяк. Спасибо, что сказали. Исправил


  1. EvilBeaver
    29.04.2024 12:18
    +2

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

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


    1. MaxiEnergy Автор
      29.04.2024 12:18

      Спасибо, что прочитали статью и поделились своим мнением) Я думал поэкспериментировать с прошивкой. Может все таки по дополнительному функционалу еще подумаю. Если надумаю, будет ещё одна статья)


  1. alpame
    29.04.2024 12:18
    +1

    Глядя на цену на скриншоте я ожидал что он уже на радиоуправлении.


    1. MaxiEnergy Автор
      29.04.2024 12:18
      +1

      Как и приличное количество людей в комментариях к товару