
Є проєкти, які починаються з чіткого технічного завдання, архітектурної схеми, беклогу, оцінки ризиків і кави без цукру. А є проєкти, які починаються з думки: «А що буде, якщо змусити китайський робот-пилосос ганятися за повітряною кулькою?»
Ця історія саме з другої категорії. Трохи комедійна, трохи хакерська, трохи детективна. У головних ролях: робот-пилосос, Android-додаток, Bluetooth Low Energy, декомпіляція, нативна .so бібліотека, Wireshark-настрій, стара IP-камера на скотчі та одна дуже підозріла повітряна кулька.
Спочатку я просто хотів зрозуміти, як мобільний додаток керує пилососом. Потім захотів повторити це сам. А далі, як це часто буває в програмуванні, проєкт раптово отримав сюжет, побічні квести й бос-файт із Bluetooth-пакетами.
Розділ 1. Підозрюваний: Android-додаток
Усе почалося з оригінального Android-додатка, через який робот-пилосос керувався вручну: вперед, назад, ліворуч, праворуч, стоп. Звучить просто. Але коли ти натискаєш кнопку в додатку, а десь у кімнаті оживає пластиковий диск на колесах — між цими двома подіями ховається маленька магія.
Мені стало цікаво: що саме відбувається всередині? Які команди летять по Bluetooth? Чи це звичайні байти? Чи там є шифрування? Чи є handshake? Чи робот просто вдає з себе розумного, а насправді чекає на магічне число типу 0xA5?
Тому перший крок був очевидний — декомпілювати APK.
Після декомпіляції я почав шукати частину коду, яка відповідає за Bluetooth-з’єднання. У проєкті швидко проявився знайомий слід: додаток використовував BLE-бібліотеку, схожу на Android-BLE SDK від Heaton / Aicare. Це китайська бібліотека для спрощення роботи з Bluetooth Low Energy: сканування пристроїв, підключення, читання та запис характеристик, обробка callback-ів.
На цьому етапі розслідування виглядало приблизно так:
// Умовний приклад логіки, схожої на те,
// що можна зустріти у BLE-керуванні пристроєм
bleManager.connect(device, new BleConnectCallback() {
@Override
public void onConnectSuccess(BleDevice bleDevice) {
System.out.println("Пилосос на зв'язку. Підозрюваний підключений.");
}
@Override
public void onConnectFail(BleDevice bleDevice, String error) {
System.out.println("Пилосос втік у Bluetooth-туман: " + error);
}
});
Звісно, реальний код був складнішим. Там були сервіси, характеристики, callback-и, обробка станів, різні пакети даних. Але головне стало зрозуміло: керування відбувається через BLE, а мобільний додаток просто записує певні байти у потрібну характеристику пристрою.
Розділ 2. Команди є, але вони не хочуть говорити
Наївна версія мене думала так: «Зараз знайду команду “вперед”, скопіюю байти, відправлю їх із власної програми — і пилосос поїде».
Але пилосос, виявляється, теж мав характер.
Просто надіслати один пакет було недостатньо. Перед керуванням відбувався певний обмін даними: пробудження, підключення, підготовка, можливо, внутрішня ініціалізація сесії. Деякі пакети виглядали як службові, деякі — як команди, а деякі — як щось, що спеціально придумали, щоб програміст уночі дивився на hex-дамп і ставив під сумнів свої життєві рішення.
Приклад пакета міг виглядати умовно так:
A5 5A 03 01 10 00 B9А ти сидиш і думаєш:
A5 5A— це заголовок?03— довжина?01— тип команди?10— рух вперед?B9— checksum?- чи це просто пилосос передає мені своє невдоволення?
Розділ 3. Нативна бібліотека: місце, де ховаються дракони
Частина логіки була не в Java/Kotlin-коді, а в нативній .so бібліотеці. Це вже цікавіше. Якщо Java-код після декомпіляції ще можна читати майже як книгу, то .so файл — це більше як стародавній сувій, який хтось порізав, перемішав і скомпілював із оптимізаціями.
Саме там могли ховатися речі на кшталт:
- формування команд;
- додавання службових байтів;
- обчислення контрольної суми;
- шифрування або просте обфусковане перетворення;
- логіка handshake між додатком і пилососом.
Я почав розбирати, які функції викликаються перед відправкою команд, які байти додаються, як змінюється пакет перед записом у BLE-характеристику. Це вже було схоже не на звичайну розробку, а на технічний детектив: є пристрій, є додаток, є підозріла бібліотека, є потік байтів — і десь там правда.
// Умовний приклад того, як може виглядати
// проста підготовка пакета перед відправкою
uint8_t checksum(uint8_t* data, int length) {
uint8_t result = 0;
for (int i = 0; i < length; i++) {
result ^= data[i];
}
return result;
}
Звісно, конкретна реалізація в реальному пристрої може бути зовсім іншою. Але загальна ідея така: пристрій рідко приймає «голу» команду. Часто треба правильно сформувати пакет: заголовок, довжина, команда, параметри, checksum, іноді ще й зашифрований блок.
Розділ 4. Пакети, перехоплення і nRF Connect
Щоб не гадати лише по декомпільованому коду, я почав дивитися на реальний обмін між додатком і пилососом. Для цього використовував кілька підходів.

На Android дуже допомагає nRF Connect. Це зручний інструмент для роботи з BLE-пристроями: можна сканувати пристрої, дивитися сервіси, характеристики, читати значення, підписуватись на notifications та пробувати записувати дані вручну.
Типовий BLE-сценарій виглядає приблизно так:
1. Знайти пристрій
2. Підключитися
3. Отримати список сервісів
4. Знайти потрібну характеристику
5. Увімкнути notifications, якщо треба
6. Записати пакет команди
7. Подивитися відповідь пристрою
Окремо я експериментував і в терміналі на Ubuntu. Linux дає багато інструментів для роботи з Bluetooth, і хоча BLE не завжди поводиться так дружньо, як хотілося б, можливість дивитися пристрої, підключення та пробувати низькорівневу взаємодію дуже допомагає.
# Перевірити Bluetooth-контролер
bluetoothctl
# Усередині bluetoothctl
scan on
devices
connect XX:XX:XX:XX:XX:XX
У такі моменти починаєш відчувати себе не веброзробником, а людиною, яка проводить допит пилососа:
— Назвіть ваш UUID характеристики.
— ...
— Я бачу, що ви мовчите. Але ми обидва знаємо, що команда “вперед” у вас десь є.
Розділ 5. Власне ядро керування
Коли структура команд стала трохи зрозумілішою, виникла наступна ідея: зробити окреме ядро керування, яке зможе підключатися до пилососа і відправляти команди без оригінального Android-додатка.
Ідея проста: винести роботу з пристроєм у незалежний код, який потім можна буде використовувати в різних сценаріях — CLI, desktop-застосунок, інтеграція з системою комп’ютерного зору або навіть невеликий локальний сервіс.
Умовно API такого ядра може виглядати так:
class VacuumController {
public:
bool connect(const std::string& deviceAddress);
void moveForward();
void moveBackward();
void turnLeft();
void turnRight();
void stop();
};
А далі вже можна будувати логіку вищого рівня:
VacuumController vacuum;
if (vacuum.connect("XX:XX:XX:XX:XX:XX")) {
vacuum.moveForward();
std::this_thread::sleep_for(std::chrono::seconds(1));
vacuum.stop();
}
Публічний репозиторій ядра керування можна подивитися тут: GitHub: Vacuum BLE Control Core .
Це не історія про «натиснув кнопку — усе запрацювало». Це історія про те, як команда, яка в додатку виглядає як простий tap по кнопці, насправді проходить через кілька рівнів: UI, BLE SDK, нативну бібліотеку, формування пакета, запис у характеристику, відповідь пристрою і лише потім — рух коліс.
Розділ 6. А потім я захотів дати пилососу зір
Коли з керуванням стало більш-менш зрозуміло, з’явилася інша думка: а що, якщо зробити цей пилосос трохи розумнішим?
Не в сенсі «зробити з нього конкурента Boston Dynamics». Поки що скромніше: навчити його бачити об’єкт і рухатися за ним.
Саме в цей період я активно займався комп’ютерним зором: навчав моделі, експериментував із детекцією об’єктів, відеопотоками, трекінгом, FPS, оптимізацією та різними підходами до inference. Тому ідея народилася майже сама:
Беремо стару IP-камеру, кріпимо її на пилосос скотчем, запускаємо детекцію повітряної кульки, а пилосос має їхати за нею. Що може піти не так?
Спойлер: майже все. Але саме тому це цікаво.
Розділ 7. Повітряна кулька як фінальний бос
На перший погляд задача звучить просто:
- отримати відео з IP-камери;
- знайти на кадрі кульку;
- визначити, де вона відносно центру кадру;
- дати команду пилососу повернути або їхати вперед;
- повторювати це багато разів на секунду.
Але в реальному світі все складніше. Камера має затримку. Bluetooth має затримку. Робот має інерцію. Кулька рухається. Освітлення змінюється. Модель може помилятися. А скотч, як виявляється, не завжди є промисловим стандартом монтажу камер.
Базова логіка трекінгу може виглядати приблизно так:
if (!ballDetected) {
vacuum.stop();
return;
}
int frameCenterX = frameWidth / 2;
int ballCenterX = detection.x + detection.width / 2;
int offset = ballCenterX - frameCenterX;
int deadZone = 50;
if (std::abs(offset) < deadZone) {
vacuum.moveForward();
} else if (offset < 0) {
vacuum.turnLeft();
} else {
vacuum.turnRight();
}
Це дуже спрощений приклад. У реальному сценарії треба враховувати:
- стабільність детекції між кадрами;
- фільтрацію випадкових спрацювань;
- затримку відеопотоку;
- обмеження частоти команд;
- безпечну зупинку, якщо об’єкт втрачено;
- відстань до об’єкта, якщо її можна оцінити;
- плавне керування замість різких команд.
Розділ 8. Найбільший ворог — не модель, а затримка
У задачах комп’ютерного зору часто здається, що головна проблема — це модель. Треба краще навчити, зібрати датасет, підібрати архітектуру, підвищити точність. І це справді важливо. Але коли модель починає керувати фізичним пристроєм, у гру вступає інший ворог — latency.
Затримка може накопичуватися на кожному етапі:
- IP-камера передає відео із затримкою;
- програма отримує кадр не миттєво;
- модель витрачає час на inference;
- алгоритм приймає рішення;
- команда йде по Bluetooth;
- пилосос реагує не моментально.
У результаті може вийти ситуація, коли кулька вже зліва, а пилосос усе ще героїчно повертає вправо, бо саме там вона була кілька кадрів тому. Це вже не штучний інтелект, а штучна розгубленість.
Тому для таких задач важлива не лише точність моделі, а й уся система: швидкість отримання кадрів, стабільність FPS, частота команд, логіка згладжування і правильна реакція на втрату об’єкта.
Розділ 9. Що може піти не так
Якщо коротко — усе. Якщо детальніше, то ось кілька типових проблем.
1. BLE-пристрій не завжди хоче дружити
Bluetooth Low Energy — це не просто «підключився і пишеш байти». Там є сервіси, характеристики, permissions, notifications, MTU, таймінги, нестабільні підключення і поведінка, яка іноді залежить від фази місяця, версії Android і настрою пилососа.
2. Команди треба відправляти обережно
Якщо надсилати команди занадто часто, пристрій може їх ігнорувати, зависати або поводитись непередбачувано. Для керування фізичним пристроєм краще мати обмеження частоти команд і стан машини: не дублювати одну й ту саму команду без потреби.
if (nextCommand != currentCommand) {
vacuum.send(nextCommand);
currentCommand = nextCommand;
}
3. Детекція не дорівнює розумінню
Модель може знайти кульку на кадрі, але це ще не означає, що система «розуміє» сцену. Вона не знає, що між пилососом і кулькою може бути ніжка стола, килим, поріг або кабель зарядки, який дуже хоче стати частиною експерименту.
4. Скотч — це MVP, але не production
Камера, прикріплена скотчем, — це швидко, дешево і в дусі стартапу. Але для стабільної роботи потрібне нормальне кріплення, правильний кут огляду, живлення, кабель-менеджмент і бажано не перетворювати пилосос на кіберпанк-їжака.
Розділ 10. Навіщо це все?
Можна чесно сказати: практична користь такого експерименту не в тому, щоб отримати ідеального робота-мисливця на кульки. Практична користь — у процесі.
У цьому проєкті перетинаються одразу кілька цікавих напрямків:
- reverse engineering Android-додатків;
- аналіз BLE-протоколів;
- робота з нативними бібліотеками;
- низькорівневе формування пакетів;
- керування фізичним пристроєм;
- комп’ютерний зір;
- детекція об’єктів;
- трекінг;
- інтеграція AI-моделі з реальним світом.
І саме такі експерименти дуже добре прокачують інженерне мислення. Бо тут недостатньо просто написати красивий код. Треба зрозуміти пристрій, протокол, обмеження заліза, поведінку мережі, затримки, помилки моделі і те, що реальний світ не має методу retry() без побічних ефектів.
Висновок: пилосос ще не Skynet, але вже підозрілий
Цей експеримент почався з цікавості: як мобільний додаток керує пилососом через Bluetooth? Потім перетворився на дослідження BLE-пакетів, декомпіляцію Android-додатка, аналіз нативної бібліотеки, спроби повторити протокол керування і побудову власного ядра для відправки команд.
А потім до історії додалася комп’ютерна зорова частина: IP-камера, детекція кульки, трекінг і бажання зробити простий китайський пилосос трохи розумнішим, ніж він планував бути із заводу.
Чи став він повноцінним автономним роботом? Ще ні. Чи навчився він дивитися на світ через камеру і потенційно ганятися за кулькою? Це вже набагато ближче до правди (Наземний роботизований комплекс :-)).
Найцікавіше в таких проєктах — не фінальний результат, а шлях. Ти починаєш із кнопки «вперед» в Android-додатку, а закінчуєш аналізом байтів, BLE-характеристик, inference-затримок і питанням: «А чи не занадто багато свободи я дав цьому пилососу?»
Але поки він ганяється лише за повітряною кулькою — людство, здається, у безпеці.
