Год назад я приобрел фитнес-трекер SONY SmartBand SWR10. Как и большинство других фитнес-трекеров, гаджет не играл большой роли в моей жизни, его основным занятием было лежать на моем столе. Тем не менее, это интересное электронное устройство, и когда друг пригласил меня присоединиться к его команде в хакатоне NASA Space Apps — я решил использовать трекер. Мы выбрали секцию «Не разбей мой дрон», где требовалось создать решение для управления беспилотником. Было решено использовать этот трекер для управления дроном.
Как только хакатон начался, я стал искать API своего браслета или инструменты для разработчиков, но ничего не нашлось. Некоторые пользователи форума сообщили, что у SONY есть проприетарный SDK, который дается разработчикам, решивших сотрудничать с компанией, но по условиям хакатона можно было использовать только open-source решения. Поэтому я решил, что пришло время создать собственный интерфейс по управлению дроном. Я запустил приложение для трекера, и включил функцию “Bluetooth HCI snoop log”. Эта функция позволила сниффать трафик и сохранять дамп на SD-карту.
Я нашел лог:
~ adb shell echo $EXTERNAL_STORAGE
/sdcard
~ adb pull /sdcard/btsnoop_hci.log
И открыл его в WireShark:
Похоже, что передача данных велась по протоколу Bluetooth Low Energy, а если точнее, то по протоколу GATT. Это двухсторонняя передача данных, где телефон выступает в роли сервера, «GATT Server», а периферийные устройства получают “GATT characteristics”, которые могут содержать любые двоичные данные.
Я выбрал первый UUID (00000208–37cb-11e3–8682–0002a5d5c51b), и поискал его в Google. Я ожидал обнаружить обычный стандартизированный GATT сервис, но нашел что-то более интересное.
Я решил, что мы обнаружили чей-то Git-репозиторий, созданный человеком, который осуществил реверс-инжиниринг протокола. И это было частично так: проект содержал несколько работающих функций, включая подключение, отклик на движение руки, считывание статуса батареи и сохранение подключения активным. Но вот реализация акселерометра была странной. Я добавил данные по акселерометру в MPAndroidChart, и получил вот это:
И хотя информация по наклонам была верной, значения не отображались. После изучения кода функции оказалось, что в 32-битном числе одинарной точности содержалось три 10-битных значения. Это имеет смысл, поскольку большинство акселерометров работает с 10-битной точностью (как и большинство АЦП, аналогово-цифровых преобразователей). Вот исходные данные, обратите внимание на первых два бита:
acc data: 0b00101110 0b10110101 0b10111100 0b10111000
acc data: 0b00111010 0b11110001 0b00011101 0b11101011
acc data: 0b00111110 0b01110110 0b10100110 0b10011001
acc data: 0b00001011 0b11011101 0b10111101 0b00111001
Но парсинг данных как трех 10-битных значений был довольно странным, каждые несколько градусов наклона меняли значение с 511 до -512, и смещение этого не меняло (причем проблемы целочисленного переполнения здесь не было). Поэтому я решил, что SONY может использовать какой-то собственный метод бинарной упаковки, или же компания использует арифметическое кодирование и сжатие.
Я опробовал Protobuf, MsgPack, Thrift и несколько парсеров, но ничего не сработало. У меня был всего день для подключения своего устройства к Wi-Fi дрона. Уже отчаявшись, я попробовал DEX-декомпиляцию приложения компании, и через полчаса получилось вот что:
// Roughly
1: iconst_0 512 iconst_1 1023
2: r1 xor iconst_0
3: istore_0
4: iload_0
5: ifneq 8
6: r1 xor iconst_1
7: istore_0
if ((data & 0x200) != 0) {
data = -((data ^ 1023) + 1);
}
return (((float) (-data)) * 15.625f) / GRAVITY_FACTOR;
Первая половина [0, 512] была инвертирована, поэтому и возникла ситуация, описанная выше. Я исправил это, использовав обычный XOR, и все получилось. Я добавил фактор гравитации, с результатом [-1,1] для ±1 г, и все, что нужно было сделать дальше — использовать данные акселерометра для управления дроном.
Автор: marks