В последнее время коллеги по "цеху" независимо друг от друга стали спрашивать меня: как получить c одного SDR-приемника одновременно все каналы Bluetooth? Полоса ведь позволяет, есть SDR с выходной полосой 80 МГц и более. Можно, конечно, сделать это на ПЛИС, но время такой разработки будет довольно большим. Мне давно было известно, что сделать такое на GPU довольно просто, но чтобы так!
Стандарт Bluetooth определяет физический уровень в двух версиях: Classic и Low Energy. Спецификация есть здесь. Документ ужасно большой, читать его целиком опасно для мозга. К счастью, большие компании, производящие измерительную технику, имеют средства для создания наглядных документов по теме. Tektronix и National Instruments, например. У меня совершенно нет шансов в конкуренции с ними по качеству представления материала. Интересующихся прошу изучать по ссылкам.
Все, что мне нужно знать о физическом уровне для создания многоканального фильтра, это шаг сетки частот и скорость модуляции. Они сведены в таблицу в одном из указанных документов:
Таким образом, нам нужно нарезать полосу 80 МГц на 79 фильтров с шагом настройки 1 МГц и, одновременно с этим, на 40 фильтров с шагом настройки 2 МГц. Частоты следования отсчетов с выходов фильтров должны быть 1 МГц и 2 МГц, соответственно.
Таким образом, нам необходимо две гребенки фильтров.
Для начала выберем параметры этих фильтров исходя из полос сигналов Bluetooth Classic и Bluetooth Low Energy. Нам нужны их импульсные характеристики, чтобы рассчитать нагрузку на вычислительное устройство фильтра. Здесь сразу стоит оговориться, что длины импульсных характеристик мы выбрали исходя из требований "быстрого" алгоритма фильтрации. Суть от этого не меняется. И число коэффициентов импульсной характеристики не должно быть слишком большим, чтобы фильтр был реализуем на вменяемой вычислительной аппаратуре.
Для фильтров с шагом 1 МГц выберем полосу пропускания ФНЧ (половина полосы пропускания полосового фильтра) 500 кГц, длину импульсной характеристики подгоним к 480 отводам. Для фильтров с шагом 2 МГц эти параметры выберем 1 МГц и 240 отводов, соответственно. Тип окна выбираем Кайзера. Рассчитаем импульсные характеристики в filterDesigner и выгрузим их в формате С-header:
Можно решать задачу лобовым способом: строить соответствующий по числу фильтров массив DDC (Digital Down Converter). Такой подход хорош для ПЛИС, где возможна экономия за счет уменьшения разрядности вычислителей первых ступеней. Также ПЛИС это наиболее энергетически-эффективный способ реализации. Но трудозатраты при этом способе наиболее высоки.
При выполнении гребенки фильтров на популярных нынче GPU появляется возможность реализации более хитрого алгоритма: полифазной гребенки фильтров на основе БПФ, который на CUDA доступен из библиотеки. В заграничной литературе алгоритм называется Polyphase or WOLA (Weight, Overlap and Add) FFT Filterbank. Лень к рисованию не дает мне возможности выполнить наглядное объяснение самостоятельно. В Сети есть много материалов по теме, особенно наглядный график выполнен здесь на странице 11 (огромное спасибо уважаемым авторам), вот он:
Я попробую пояснить схему обработки своими словами в рамках моих
методических возможностей. БПФ есть свертка входного сигнала со
всем спектром комплексных ортогональных гармоник, укладывающихся на
интервале импульсной характеристики. Импульсная характеристика
фильтра, на которую умножается сигнал перед входом БПФ,
модулируется этим спектров гармоник. Другими словами, огибающая
импульсных характеристик результирующих фильтров гребенки выносится
за скобки. Далее, спектр гармоник прореживается в некоторое число
раз, ввиду расширения полосы пропускания фильтра относительно
фильтра в прямоугольным окном. На рисунке мы видим прореживание на
четыре. Другими словами, после расширения полосы окном Кайзера (с
одновременным увеличением затухания в полосе задержания), нам уже
нужны не все фильтры а только четвертая их часть. Остальные
избыточны, их частотные характеристики перекрываются. Из четырех
точек БПФ, идущих подряд, выбираем только нулевую, вычисление
которой есть суммирование четырех
входных точек, взятых через время, равное четверти длительности
исходного БПФ.
Железо выберем то, которое есть под рукой. Это плата ввода компании "Инструментальные Системы" FMC126P. О ней я уже писал в одной предыдущей статье. В разъем FMC платы вставлен субмодуль той же компании с трансивером AD9371 с полосой 100 МГц. Весь поток с трансивера может быть непрерывно передан в компьютер для обработки.
Выберем видео-карту с GPU GTX 1050. (Соврал, это она нас выбрала: это все что было под рукой, была выдрана из вычислителя для расчета антенн, но тем удивительней было увидеть рабочую гребенку). Перейдем к программной части.
К сожалению, из-за лицензий мы не можем опубликовать полный код. Можем показать только ядра GPU. Впрочем, остальной код и не особо интересен.
Вот ядро, которое выполняет перемножение сигнала на окно и сложение, и обертка для его вызова:
__global__ void cuComplexMultiplyWindowKernel(const cuComplex *data, const float *window, size_t windowSize, cuComplex *result) { __shared__ cuComplex multiplicationResult[480]; multiplicationResult[threadIdx.x] = cuComplexMultiplyFloat(data[threadIdx.x + windowSize / 4 * blockIdx.x], window[threadIdx.x]); __syncthreads(); cuComplex sum; sum.x = sum.y = 0; if (threadIdx.x < windowSize / 4) { for(int i = 0; i < 4; i++) { sum = cuComplexAdd(sum, multiplicationResult[threadIdx.x + i * windowSize / 4]); } result[threadIdx.x + windowSize / 4 * blockIdx.x] = sum; }}cudaError_t cuComplexMultiplyWindow(const cuComplex *data, const float *window, size_t windowSize, cuComplex *result, size_t dataSize, cudaStream_t stream) { size_t windowStep = windowSize / 4; cuComplexMultiplyWindowKernel<<<dataSize / windowStep - 3, windowSize, 1024, stream>>>(data, window, windowSize, result); return cudaGetLastError();}
Код обработки сигнала, который вызывает это ядро, в точности повторяет схему алгоритма, изображенную на рисунке выше, поэтому не вижу смысла приводить его здесь.
Гребенки были проверены на выходном спектре каналов в реальном времени. На вход AD9371 подавался сигнал генератора сигналов 2450 МГц, селективность фильтров соответствовала расчетной.
В планах: адаптация софта на плату XRTX и реализация поиска пакетов, если это кому-нибудь окажется нужно или будет свободное время.
Всю работу по софту выполнил gaudima, слава ему!
Скейтборд это не только средство передвижения, но и спортивный снаряд, с которым можно выполнять различные трюки. Пожалуй, наиболее известный трюк это kickflip, но существует и множество и других.
Целью нашего девайса будет определение качества и количества сделанных трюков. Потом эти данные можно собрать в статистику и инфографику и отобразить в мобильном приложении, где можно будет отслеживать свой прогресс и делиться им с друзьями. Устройство должно крепиться на сам скейтборд. Так проще определять, что с ним происходит.
Грубо говоря, большинство трюков это некое изменение пространственного положения доски с ходом времени и по определённому сценарию. Для отслеживания движений можно использовать MEMS-датчики и компас. Для скейт-трюков это необходимое, но не достаточное условие, но чисто теоретически, самые базовые элементы типа ollie и kickflip с их помощью можно определять.
Что-то подобное уже существует?Мне удалось нагуглить только два похожих проекта, но статус обоих превращает эту часть статьи в небольшой некролог.
RideBlock (c) IGG1) RideBlock. Проект скейтборд-трекера с мобильным приложением. Использует IMU (9DoF) для сбора данных. Был запущен в инкубаторе в Лос-Анджелесе 4-5 лет назад, в 2017 году не собрал нужную сумму на Indiegogo, с тех пор признаков жизни не подаёт.
Syrmo (c) DigitalTrends2) Syrmo. Аналогичный проект из Долины, с более удачным местом установки на подвеске. Размещение сбоку под осью уберегает устройство от возможных ударов и не препятствует выполнению ряда трюков. Для измерения высоты прыжков использует высокоточный барометр, для остального - 6DoF IMU. Последние новости о проекте датируются 2015 годом.
Провалились ли эти проекты? Я считаю, что да.
Почему? Трудно сказать, ведь никто из их создателей не делился публично тем, что же пошло не так.
Можно предположить, что:
Целевая аудитория (скейтеры) не захотели тратить деньги на устройство к скейту, когда на эту же сумму можно купить новую доску или кеды, ведь у скейтеров это дорогой расходный материал, который нужен всегда
Возникли какие-то непреодолимые трудности в реализации идеи. Вдруг в принципе невозможно при текущем развитии технологий добиться желаемого результата в точности определения трюков или в качестве полученных данных?
Не хватило капитала на реализацию идеи: денежного либо человеческого
Оба проекта были мошенничеством в том или ином виде и служили лишь целью быстрого получения денег
Лично я настроен оптимистично и не имею никакого давления со стороны (инвесторы, зарплаты сотрудников), так как проект некоммерческий и делается в свободное время, быть может это позволит ему остаться на плаву?
Дальше по тексту на глаза могут попасться названия трюков, которые могут быть не знакомы тем из читателей, кто не катается или не играл в THPS или Skate 3. Под спойлером небольшой раздел с илюстрацией трюков.
Скейтборд-ликбезХотя катание на скейте и несёт в себе креативный и развлекательный аспекты, некая система всё же присутствует. Известные трюки имеют устоявшиеся названия, вот самые базовые из них (в исполнении вашего покорного слуги):
KickflipБеря во внимание основную идею, можем сформулировать базовые требования к устройству.
Компактность. На скейтборде не так много места, куда можно было бы закрепить устройство так, чтобы оно не пострадало
Питание от батареи. Батарея должна быть перезаряжаемая и компактная, но достаточной ёмкости, чтобы протянуть минимум одну типичную скейт-сессию (у меня это 2-3 часа)
Необходимые датчики: акселерометр, гироскоп, магнитометр и др.
Наличие беспроводной связи. Так как устройство призвано генерировать много данных, нужен удобный способ эти данные получить
Минимальный пользовательский интерфейс помимо беспроводного. Например, парочка светодиодов и кнопка. Нам не нужен черный ящик
(Желательно) Водонепроницаемость, вибро- и ударостойкость. Трюки получаются не всегда с первой попытки и доска может упасть, сильно и много раз удариться о землю. На асфальте так же могут быть лужи, грязь и влага, которые могут навредить электронике устройства
(Желательно) Отделяемый блок батареи, чтобы не заряжать доску
Сформулируем некий минимум того, что мы хотим от прошивки устройства:
Опрос датчиков и сбор данных. Собираем и обрабатываем данные с гироскопа, акселерометра, и т.д.
Обеспечение работы BLE, формирование пакетов данных
Отслеживание состояния и уровня заряда батареи. Общаемся с контроллером заряда, оцениваем когда батарея заряжена и сколько осталось заряда
Обеспечение низкого энергопотребления. Что не нужно в данный момент выключаем, что нужно, но не часто регулируем частоту срабатываний
Индикация и взаимодействие с пользователем. Реагируем на кнопку, сообщаем о текущем состоянии и нештатных ситуациях
В этом разделе рассмотрим самые ключевые компоненты, не беря во внимание мелочь типа пассивных компонентов, кнопок или разъемов.
Сердцем устройства должен стать шустрый, но малоопотребляющий микроконтроллер. Из-за требований к компактности, хочется, чтобы этот чип был системой на кристалле со встроенным радио.
Выбирал между:
Nordic Semi nRF52x зарекомендовавшие себя на рынке системы на кристалле с Cortex-M4F и отличной поддержкой BLE/Bluetooth 5. Преимущества этих чипов известны: очень продуманная и простая в использовании периферия, возможность назначать её на любые выводы, что освобождает руки при разводке платы. Проверенная временем экосистема драйверов, документации и примеров. В том числе и на языке Rust.
ST STM32WBx5 относительная новинка на рынке беспроводных SoC.
Отличительные особенности:
1) Два ядра: Cortex-M4F как основное и Cortex-M0+ как ядро для
радио-стека
2) Периферия унаследована от STM32L4, можно переиспользовать код и
опыт
3) Высокочастотный кварц можно подключать без нагрузочных
конденсаторов, они встроены в чип
4) Есть поддержка USB, у Nordic она только в самом продвинутом
nRF52840 и супер-новом
двухъядерном (2 x Cortex-M33) nRF5340
Изначально выбрал Nordic, но как только в широкой продаже появился STM32WB, решил попробовать его. В отличие от нордика, под STM32 чип не было экосистемы драйверов и библиотек и пришлось написать свои1,2 и получить тонну опыта. Собственно, ради нового опыта я этот чип и выбрал, хотя и nRF52 всем устраивает.
Помимо этих двух чипов, довелось попробовать в работе чип NXP QN908x. Это довольно интересный чип с уникальными фишками типа встроенного сопроцессора (FSP) для sensor fusion и машинного обучения, USB, встроенных конденсаторов для кварцев, подключения внешней FLASH-памяти по (Q)SPI. Было дело, что я даже развёл и спаял плату под этот чип. Но столкнулся с сырой документацией и примерами, странными багами с отладочной платой и, видимо, отсутствие опыта с чипами NXP дало о себе знать.
Решил ставить STM32WB55. Сперва в исполнении с 1 МБ FLASH и 512 КБ RAM. Затем можно будет оптимизировать размер памяти.
Здесь я решил пойти простым путем и взять что-то компактное и дающее нужные данные из коробки.
Изначально выбор пал на Bosch Sensortec BNO055. Это SiP из акселерометра, гироскопа и магнитометра, а также Cortex-M0+ микроконтроллера. Чип идёт с проприетарной прошивкой, которая обеспечивает опрос датчиков и sensor fusion, что позволяет сильно разгрузить основной микроконтроллер. При этом размеры корпуса 5.2 x 3.8 мм. В ходе работы над драйвером обнаружился ряд несовершенств прошивки этого чипа (например, медленная скорость обновления данных либо кривые углы Эйлера после fusion-а).
Чип BNO08x на типичной breakout-платеЗатем вышел более новый Hillcrest/CEVA BNO08x на базе того же железа, что и BNO055, но с более совершенной прошивкой и я решил использовать уже его. Разница между BNO080 и BNO085 лишь в версии прошивки, идущей в чипе.
Отдельно стоит упомянуть InvenSense/TDK ICM-20948, преемника очень популярного MPU9250 (который ныне NRND). Чип с хорошими характеристиками и тоже имеет встроенный sensor fusion (Motion Engine), но документация на него очень непрозрачная либо под NDA, что для хобби-проекта неприемлемо.
Поэтому ставим BNO08x, даже если это может быть немного оверкилл.
Девайс будет работать от батареи. При этом хотелось бы, чтобы устройство:
Заряжало литиевую батарею
Мгновенно переключалось между источниками питания: от шнура при зарядке и от батареи, когда шнур отключен (power path selection)
Отслеживало, сколько в батарее осталось заряда (gas gauge)
Включало-выключало элементы схемы по команде главного контроллера
Обладало режимами вкл/выкл
Имело один или несколько преобразователей напряжения для питания остальных частей
Все желаемые функции так или иначе можно сделать из дискретных компонентов: микросхем заряда, gas gauge чипов, регуляторов напряжения, watchdog-чипов с кнопкой, транзисторов. Но из-за требований к компактности, идеальным вариантом было бы получить все перечисленные возможности, добавив лишь одну микросхему. И такие варианты есть, они входят в категорию микросхем под названием PMIC.
Что предлагает рынок?Перечислим то, что удалось найти под наши требования. В скобках число реализуемых конкретным чипом желаемых возможностей.
AMS AS3701 PP, 1 DC/DC, 2 LDO, WLCSP-20, charger, GPIO и кнопка (5/6)
Maxim MAX20353 PP, 3 DC/DC, 2 LDO, корпус WLP-56, charger, gas gauge, GPIO, кнопка (6/6)
Dialog DA9070 PP, 1 DC/DC, 3 LDO, WLCSP-42, charger, gas gauge (ADC), watchdog и кнопка (6/6)
TI TPS6572x PP, 1 DC/DC, 1 LDO, WQFN-32, DSBGA-25, charger, GPIO и кнопка (4/6)
Qorvo (Active Semi) ACT81460 PP, 4 DC/DC, 3 LDO, 1 HVB, WLCSP-49, charger, 4 GPIO, кнопка (5/6)
Microchip MCP73871 PP, QFN-20, charger. Просто маленький зарядник + power path контроллер (2/6)
X-Powers AXP173 PP, 2 DC/DC, 4 LDO, QFN-32, charger, кнопка, gas gauge (6/6)
Из массы имеющихся вариантов, я сделал довольно странный, но по-итогу вполне логичный выбор это китайский чип X-Powers AXP173. Это оказался единственный чип, который включает всё что нужно и при этом представлен в QFN-корпусе. Наличие такого корпуса важно по причине требований к ударостойкости самого устройства, корпусы типа BGA/WLCSP известны низкой стойкостью к ударам и подверженностью возникновения трещин в шариках.
И это только если не задумываться о том, что WLCSP-корпуса с шагом 0.4/0.5мм сильно повышают требования к печатной плате, а также требуют инспекции во время производства (например, просвет рентгеном).
После гугл-перевода китайского даташита, оказалось вполне понятно как с AXP173 работать по I2C шине, был написан драйвер (Rust). Микросхему легко достать и в Китае она стоит мало, проблем с ней не возникало. Кстати, чипы этой фирмы используются почти во всех устройствах на базе решений Allwinner (планшеты, одноплатник Banana Pi и т.п.).
Хоть чип и предлагает несколько DC/DC, для экономии пространства на плате я ограничился использованием лишь трёх LDO: два на главный микроконтроллер и один на IMU.
Все LDO и DC/DC преобразователи позволяют регулировать их выходное напряжение через соответствующие регистры. Чип имеет несколько встроенных АЦП для измерения напряжения батареи, протекающего в/из батареи тока, интегратор тока (coulomb counter), который помогает отслеживать уровень заряда. Чип так же можно использовать с кнопкой, и настраивать реакцию на короткое и длинное нажатие. Например, выключать или включать устройство по длинному нажатию. На каждое нажатие кнопки чип выдаёт прерывание и указывает в одном из регистров, какое было нажатие. Выдаются прерывания и по началу/окончанию зарядки, подключению/отключению зарядного устройства и много чему ещё.
Ставим AXP173.
За батареями можно ходить на Aliexpress, я заказывал максимально приближенный к размерам будущей платы литиевый элемент 301430 на 120 mAh.
Фото батареиСледуя рекомендациям для STM32WB55 мы берём чип-фильтр гармоник ST MLPF-WB55-01E3:
2.4GHz фильтр MLPF-WB55-01E3Простоты ради я решил так же использовать маленькую и проверенную чип-антенну Johanson 2450AT18B100:
Чип-антенна на 2.4GHz 2450AT18B100SoC имеет встроенный балун, поэтому он нам не нужен.
В текущей ревизии устройства два светодиода:
Светодиод-чип желтого цвета American Bright BL-HKC37A-AV-TRB размера 0402 для индикации процесса зарядки. Он подключается напрямую к PMIC AXP173 и управляется ею же
Маленький чип RGB-светодиода 1x1mm Foshan NationStar FC-B1010RGBT-HG. Подключается к микроконтроллеру и используется для отладки и индикации процесса работы
Теперь мы можем сформировать целостную картину основных блоков устройства:
Система-на-кристалле STM32WB5x отвечает за BLE связь и управляет по двум разным IC шинам контроллером питания (PMIC) и инерциальными датчиками (IMU). Светодиоды и кнопка образуют минимальный интерфейс для взаимодействия с устройством, а USB порт позволяет заряжать батарею и, например, обновлять прошивку, загружать лог данныхПройдёмся по основным блокам схемы, рассмотрев интересные на мой взгляд детали. На момент написания статьи была актуальна ревизия C (третья).
Схема рисовалась в онлайн-EDA Upverter (by Altium), ссылка на дизайн.
На схеме видно, что MicroUSB-порт (J3
) и батарея
(J2
) подключены к PMIC AXP173 (U3
).
Выходы регуляторов напряжения LDO1 и LDO2 через сдвоенный диод Шоттки питают основной микроконтроллер. Диоды нужны, чтобы оба LDO не перетягивали друг на друга одеяло и регулировали только свой выход.
Почему вообще два LDO на один чип? Чтобы обеспечить достаточный ток, так как LDO1 очень маломощный и рекомендуется производителем только для питания домена RTC. При этом LDO1 включен вообще всегда, а LDO2 включается программно, но почти сразу же.
LDO3 питает IMU и по-умолчанию выключен. Его будем включать только когда нужны данные с датчиков.
Кнопка не требует подтягивающего резистора, потому что подтяжка встроена в чип. Остальные компоненты подключены по даташиту и общим рекомендациям.
BNO08x (U2
) питается от VCC_IMU
и
сидит на I2C шине (IMU_SCL
,
IMU_SDA
). Также стоит внешний кварц на 32.768 Гц
(Y3
) с нагрузочными конденсаторами (C17
,
C18
) на 22 пФ.
В основном всё подключено по даташиту, но стоит
резистор-перемычка R26
для тестирования работы чипа с
внешним кварцем и без. На случай, если внешний кварц по каким-то
причинам не заведётся, можно будет выбрать режим без него.
Спойлер: кварц завёлся без проблем. DNP означает, что этот резистор не
должен быть запаян по-умолчанию.
От IMU микроконтроллер через I2C шину получает
данные, а кроме этого есть линия запроса на прерывание
IMU_INT
. По отдельным линиям микроконтроллер также
управляет сбросом чипа и режимом входа в загрузчик, если вдруг
когда-нибудь можно будет обновлять прошивку чипа IMU (такая
возможность есть, но она под NDA).
BNO08x поддерживает подключение дополнительных сенсоров типа барометра или датчика освещённости по собственной шине I2C, на которой он будет выступать ведущим. Эти сенсоры нам не нужны, поэтому просто подтягиваем линии внешнего I2C согласно даташиту.
Отдельно на схеме стоят также подтяжки (R21
,
R22
) для I2C шины, на которой висит IMU.
Эти подтяжки подключены к питанию IMU и неактивны (не потребляют
ток) когда IMU обесточен путём отключения LDO3 PMIC, который питает
VCC_IMU
. Отключая подтяжки вместе с чипом, мы экономим
некоторую часть общего бюджета энергопотребления.
Почему для обмена данными с IMU выбрана шина I2C вместо гораздо более быстрой SPI? Всё просто: сэкономил ножки микроконтроллера. Две (SCL, SDA) против четырёх (MOSI, MISO, nCS, CLK). Микроконтроллер, чип IMU и подтяжки на 4.7 кОм позволяют шине I2C разгоняться до 400 кГц без особых проблем.
Теперь перейдём к мозгу устройства.
Обвязка микроконтроллера STM32WB55 (U1
) согласно
даташиту. Благодаря встроенным нагрузочным конденсаторам, кварцевый
резонатор на 32 МГц Y2
подключается напрямую к чипу. В
то время как низкочастотный кварц Y1
на 32.768 Гц
подключается с нагрузочными конденсаторами на 12 пФ.
В плане подключения 2.4 GHz антенны для BLE всё тривиально,
никаких диаграмм Смита пока не
потребовалось. Антенна ANT1
подключена через фильтр
FL1
. Выход SoC RF1
, фильтр и антенна
рассчитаны на импеданс 50 Ом, поэтому согласующая цепь не
применяется, достаточно соединить всё напрямую.
L1
, L2
, C1
внешние
элементы для встроенного DC/DC преобразователя SMPS. Его использование позволяет
снизить энергопотребление SoC при работающем радио и получить более
чистое питание, уменьшив шум.
Подтяжки для шины I2C (R2
,
R3
), к которой подключен PMIC, не подключаются сразу к
линии питания. Вместо этого они подключены к выходу SoC
I2C_PULLUPS
. Подтяжки включаются только при подаче на
этот выход высокого лог. уровня и выключаются при переводе выхода в
High-Z (высокоимпедансное, третье) состояние. Таким образом
подтягивающие резисторы не потребляют энергию когда не нужны.
После того как схема более-менее готова, можно переходить к разводке платы. Как правило, два этих процесса взаимосвязаны и влияют друг на друга. Плата разводится по схеме, а схема изменяется под возможности разводки платы.
Разводка платы точно так же выполнялась в EDA Upverter. В основном я использовал готовые футпринты компонентов из встроенной в Upverter библиотеки на базе Octopart, но для парочки деталей приходилось редактировать футпринт либо рисовать свой.
Обычно я развожу платы по примерно такому плану:
Рисуем желаемую форму и задаём габариты платы
Задаём нужные слои, у нас их точно будет 4: верхний, земля, питание, нижний. Такой четырёхслойный стек является рекомендуемым1,2 для плат с RF, при этом слой земли должен быть следующим после слоя, на котором будет разведена RF часть. Кроме этого, работать с 4 слоями удобнее, особенно когда это касается подключения питания компонентов. Когда два внутренних слоя выделены для питания и земли, для запитывания чипов часто достаточно просто поставить переходное отверстие на соответствующий слой
Задаём автоматические правила проверки (DRC) согласно возможностям выбранного производителя плат (у меня это PCBWay): 5/5 mil (0.127 мм) проводник/зазор, 10 mil (0.254 мм) ширина отверстия, 4 mil внешнее кольцо переходного отверстия. При этом это минимально допустимые правила. Для большей надёжности я ставлю по-умолчанию минимальные размер дорожки и зазор в 6 mil (0.1524 мм)
Расставляем монтажные отверстия для крепления платы в корпусе. Лучше сделать это в самом начале, чем потом, когда места под них уже не останется
Расставляем те компоненты, которые точно должны быть с краю платы: разъёмы и антенну
Ставим на плату все микросхемы (U1
U3
)
и их обвязку и двигаем-вращаем их, пока:
Не станет как можно меньше пересечений воздушных линий (ratsnest) между компонентами. Это уменьшает число переходов на другие слои и в целом даёт идею об оптимальности взаимного расположения
Длина воздушных линий не станет минимальной (это уменьшает длину печатных проводников и помогает сэкономить площадь платы)
Расставляем/двигаем обвязочные компоненты по ходу разводки
Соединяем все цепи проводниками, заливаем полигон на слое земли. Сеть питания можно разводить на выделенном слое, желательно для линий питания использовать топологию звезда
Cтараемся держать все SMD компоненты на одном слое платы. Это позволит удешевить автоматический монтаж, потому что pick-and-place автомат сможет расставить все компоненты за один подход
Разводку удалось уместить в 35 х 15 мм, что вполне удовлетворяет нашим требованиям к компактности.
Скриншот платы (без полигонов, вид сверху)Для наглядности, разметил топологию основных блоков после разводки:
Как обычно, тратим минимум несколько дней на сверку с даташитом/референсами и поиск ошибок, после чего можно переходить к генерированию файлов для отправки на производство.
В этот раз получается не совсем своими руками я решил заказать производство плат вместе с монтажом компонентов. Чтобы сэкономить время и нервы, а заодно и попробовать что-то новое.
Для производства плат и поверхностного монтажа я выбрал сервис PCBWay по рекомендации знакомых.
Для производства устройств нужен следующий набор файлов:
Gerber-файлы, содержащие рисунок платы + drill файл с координатами отверстий
BOM, т.е. список компонентов с точным артикулом (part number) детали, количеством
XYRS-файл (centroid), содержащий координаты центра и ориентацию всех монтируемых компонентов
В Upverter есть специальный раздел для экспорта этих файлов, заходим туда и скачиваем. Все файлы, кроме BOM, не требуют никакой обработки и готовы к отправке. А вот BOM нужно будет подогнать под стандартный формат (.xlsx), предлагаемый PCBWay.
Пример BOM в формате PCBWayВ каждой строке количество деталей идёт в расчёте на одно устройство. При закупке компонентов это количество сотрудники PCBWay умножат на число собираемых устройств.
Заходим на сайт PCBWay и переходим в раздел Quote & Order. Там вводим основные параметры платы:
Режим Всё Включено (Turnkey): платы, трафареты, закупка, монтаж, всё делают они
Размер: 35 х 15 мм
Сторона монтажа SMD-компонентов: только верх
Количество плат: 5 (это минимум плат в заказе, сам монтаж можно сделать и на меньшем количестве)
Число слоёв: 4
Цвет паяльной маски: чёрный
Финиш поверхностей: HASL со свинцом (хороший вариант когда не надо самому паять)
Остальные параметры я оставил по-умолчанию.
Загружаем герберы, указываем, какой файл отвечает за какой слой, загружаем сверловку и XYRS, загружаем BOM. Отправляем и ждём ответа.
Через некоторое время в личном кабинете статус заказа меняется на In Audit и если всё хорошо, то заказ переходит в Audit Passed и начинается производство плат. Одновременно с этим на почту пишет сотрудник компании и уточняет наличие позиций в BOM, примерное время ожидания доставки компонента. Таблица дополняется столбцом стоимости каждой позиции умноженной на число устройств, а так же итоговой стоимостью компонентов. На этом этапе можно заменить отсутствующие на складе детали на аналоги, чтобы сократить время ожидания. Так же можно попросить специалиста подобрать компонент запросами типа поставьте самый дешевый светодиод 0402 желтого цвета или самый доступный кварц в этом корпусе с точностью 50 ppm.
По моему опыту, проходит несколько рабочих дней от отправки файлов до первого email с уточнениями по BOM.
Хорошим признаком того, что всё идёт хорошо является тишина. Если производитель больше ничего не пишет, значит процесс идёт и проблем не возникло. Сам процесс можно отслеживать поэтапно в личном кабинете:
Каждый шаг производства плат показан либо зелёным, когда он завершен, либо серым, если он в процессе или к нему ещё не приступили. Такая же картина наблюдается и при монтаже компонентовКогда производитель смонтировал самую первую плату (я так понял, что они делают это поштучно, а не панелями), наступает час Икс, когда на почту приходит письмо с фотографиями готовой платы и просьбой внимательно посмотреть и указать на недочёты:
Предварительная плата выглядела вполне себе ничего:
Точно лучше, чем если бы я паял вручнуюФото платы в более высоком разрешении: клик.
У производителя возник вопрос по поводу полярности футпринта RGB-светодиода, ведь у них не было схемы, чтобы сверить, только герберы:
Фото светодиода с предположением о полярностиСравнив со схемой я подтвердил, что всё верно и дал зелёный свет. Возможно, что данный вопрос бы не возник, если бы я удосужился разместить на шелкографии ключ компонента либо оставил полярность где-то в заметках, которые можно приложить к файлам.
Доставляют по заранее указанному при заказе адресу выбранной службой. Можно изменить адрес доставки в любое время. Например, если успел переехать на другой адрес пока шло производство (у меня так было). Если цена доставки при этом становится выше, то просят доплатить.
До адреса в России фирмой EMS посылка шла где-то неделю.
Фото посылки я не делал, но сделал фото того, что оказалось внутри:
Одно из пяти готовых устройств я уже подключил для тестов. К устройствам также приложили подарок: 5 таких же плат, но без компонентовУдовольствие не из дешёвых, но затраты компенсируются огромной экономией времени и нервов. Вот, что получилось у меня (курс доллара на 2021 год):
Платы: 5 штук за $97 (7,200 ) в сумме
Монтаж: 5 устройств за $186 (13,800 ) в сумме. При стоимости монтажа в $30 (2,238 ), львиная доля стоимости это компоненты:
SoC: $11.5 x 5 = $55.75 (4,160 ) за 5 штук
IMU: $11.9 x 5 = $59.85 (4,466 ) за 5 штук
USB разъем molex: $1.2 x 5 = $6.3 (470 )
Цены остальных компонентов меркнут по сравнению с первой тройкой
Доставка EMS: $28 (2,000 )
Очевидно, что есть куда удешевлять. Вот некоторые идеи и low-hanging fruit по снижению стоимости:
Взять менее тонкий техпроцесс плат, наприме 6/6 mil вместо 5/5 mil, взять стандартный цвет маски (зелёный)
Снижение оверкиллов в дизайне: использовать младшие модели SoC с меньшим объемом памяти и периферией. Взять более дешевый IMU BNO080 вместо BNO085
Использовать USB разъем подешевле
Что-ж, вот железо лежит на столе, подключенное к программатору и ждёт, когда в него вдохнут жизнь.
Приступив к программной части, я сформулировал следующий план действий:
Язык прошивки будет Rust. Исключительно на нём я пишу последние годы и уже порядком подзабыл Си, да и возвращаться к нему после раста совсем не хочется
Надо собрать в кучу или дописать драйверы для периферии, BLE-стека, IMU и PMIC
Помигать светодиодом
Написать простую прошивку, которая будет включать IMU и слать рассчитанные им кватернионы текущего положения устройства через последовательный порт
Написать программу для визуализации кватернионов положения
Написать прошивку посложнее, которая будет слать все данные с IMU через BLE
Написать программу на ПК для опроса по BLE и адаптировать визуализацию, добавив графики
Хотелось как можно скорее прицепить девайс к доске хоть на скотч и приступить к сбору данных. Для этого сперва надо сориентировать оси IMU относительно скейтборда и выбрать, где расположить устройство:
Ориентация осей относительно скейтборда и желаемое положение устройства под доскойКак только минимально рабочий софт был готов, я примотал устройство к нужному месту на подвеске доски и отправился в скейт-парк.
Мне удалось добиться стабильной передачи данных по BLE с частотой 15 Гц через характеристику с оповещением (Notify), несущую пакет данных из 20 байт. Если пытаться выставить частоту обновления выше, то перестаёт выдаваться прерывание от радио-сопроцессора. Особо глубоко пока не копал, но предполагаю, что боттлнеком является процедура обновления характеристики где-то в BLE стеке, работающем на сопроцессоре. Есть идея, например, в одно обновление характеристики упаковывать несколько пакетов, два или три за раз. Это нужно только на этапе сбора данных. В дальнейшем, все основные расчёты планируется вести на самом устройстве, поэтому канал BLE будет существенно разгружен.
Больше всего боялся, что устройство сразу же вырубится после первого же прыжка из-за тряски или удара, но обошлось:
Делаю Ollie, данные регистрируются с частотой где-то 15 ГцНа графике акселерометра (слева снизу) видно, когда доска отрывается от земли и когда она приземляется. Значительно возрастает ускорение по вертикальной оси Z (зелёная). Используя эти данные и время, можно придумать алгоритм расчета высоты прыжка. На графике справа показаны данные с гироскопа. Т.к. при ollie значительных вращений вокруг осей не происходит, график гироскопа здесь выглядит скучновато.
Heelflip. Из-за вращения доски график гироскопа выглядит интереснее. Из-за низкого FPS визуализации, кажется, что доска не сделала полный оборот вдоль оси Y, но на видео он был, а также на гироскопе видно, что вращение точно было.Ещё примерДля определения трюков с вращениями очень полезны данные с гироскопа. Прекрасно видно, вокруг каких осей, как быстро и на сколько градусов вращается доска.
Например, kickflip это +360 вдоль оси Y, а heelflip это -360. Меняем знаки местами если стойка скейтера меняется с regular на goofy. А pop shove-it будет выглядеть как вращение на 180 вдоль оси Z.
За кадром этого поста остались детали разработки прошивки, поскольку она пока далека от завершения и может ещё много раз измениться кардинально. Более подробно я могу рассказать про код прошивки и ПО для ПК в следующем посте. Сейчас же ссылки на весь код можно найти в разделе ссылок в конце поста.
Полдела сделано и идея проверку проходит, осталось самое сложное довести проект до конца. Вот некоторые планы на будущее (так сказать, roadmap):
Корпус! Пожалуй, это самая сложная часть проекта и источник прокрастинации для меня. Но корпус придётся делать, не приматывать же устройство скотчем каждый раз
Собрать ещё больше примеров данных с более высокой частотой, дабы иметь представление о тех вещах, которых не видно при 15 Гц дискретизации
Обратиться к литературе по алгоритмам определения высоты по акселерометру, машинному обучению и классификации по данным с IMU, например: 1, 2, 3, 4
Выполнять расчёты на самом устройстве, по BLE передавать лишь результаты
Оптимизация энергопотребления устройства
Мобильное приложение
Подумать над влаго- и ударо- защитой устройства. Это скорее относится к корпусу, но и с точки зрения электроники тоже можно попытаться на что-то повлиять. На ум приходит conformal coating платы, убрать, где возможно кварцевые, резонаторы, водонепроницаемый разъем для зарядки
Спасибо за внимание, с нетерпением жду ваших вопросов и комментариев!
Репозиторий с кодом прошивки, программ и документация
Схема и разводка платы на Upverter
Прошло 4 месяца с написания предыдущей статьи, за это время произошло довольно много нового как по технической части, так и по позиционированию девайса.
Напомню какие требования к девайсу я поставил, когда начинал разработку:
Устройство должно имитировать гитару с 6-ю струнами и 12-ю ладами на грифе
Должно быть компактным, в идеале складным, чтобы можно было брать его с собой куда угодно
Должно подключаться ко всем популярным осям Android, IOS, Windows, Linux, MacOS и определяться там как MIDI устройство без каких-либо драйверов
Работа от аккумулятора
Подключение должно производиться без проводов по Bluetooth Low Energy (но раз уж там будет USB разъем для зарядки, то и по проводу пусть тоже подключается)
Возможность сразу начать играть, без необходимости в долгих тренировках по адаптации кистевых связок
На каждой струне и каждом элементе грифа должно быть по светодиоду, чтобы можно было запустить табулатуру мелодии, и гитара сама показывала куда нужно прикладывать руки
Возможность использования основных техник игры на гитаре: hummer on, pull off, slide, vibrato
Задержка передачи midi команд не более 10мс
Все должно собираться из подручных материалов без сложных техпроцессов и дорогой электроники
Реализовать мне все это удалось, и даже больше. Был дополнительно интегрирован акселерометр для управления параметрами фильтрации звука наклоном гитары и вибромотор (правда, я до сих пор не придумал зачем).
На момент написания предыдущей статьи выглядело оно так:
Было принято решение пытаться делать стартап и выходить на кикстартер.
Следующим шагом стал стандартный этап поиска pre-seed раунда инвестирования. Деньги нужны были на доработку и изготовление нового прототипа, проведения пиар кампании и оплату юридических манипуляций (для участия на кикстартере необходимо юр. лицо в США со всеми вытекающими организационными тратами). Эту задачу нам с моим партнером, отвечающим за бизнес процессы, удалось решить за 2 месяца.
Для подтверждения полезности продукта и его функционала мы решили провести встречи со всеми Питерскими и Московскими музыкантами, с которыми смогли связаться, и заодно поснимать видеороликов для соц сетей.
Профессиональные гитаристы отнеслись к девайсу ожидаемо скептически. И я их полностью понимаю, это не замена настоящей полноценной гитаре. Это девайс для применений, в которых важны портативность, универсальность, простота, возможность играть в наушниках и т.д. Сидишь, например, в самолете, вокруг шум, суета, плачущие дети, кислородные маски выпадают. Спать невозможно. И тут внезапно наступает вдохновение, достаешь девайс, надеваешь наушники и записываешь новый трек.
Широкой публикой гитара была оценена очень позитивно не смотря на пока что ограниченный функционал и большие конструктивные неудобства. Девайс оказался настолько интересным, что даже популярный в рунете блогер Ваганыч снял на него обзор.
По результатам общения мы открыли для себя новую целевую аудиторию - саунд дизайнеры, мьюзик мейкеры - люди, которые пишут цифровую музыку. Оказалось, у них есть большие сложности с записью гитарных и других струнных партий на миди клавиатурах. Это очень сложно, долго, муторно настолько, что зачастую им приходится отказываться от струнных инструментов, органичивая свое творчество. Наш девайс они восприняли с максимальным энтузиазмом, пророча ему большую популярность.
Есть на этом рынке и конкуренты. Мы купили по экземпляру каждого для оценки.
1. Artiphon - панель, чувствительная к нажатию, по форме напоминает гитару, но позиционируется скорее как настольная клавиатура.
Интересная, но дорогая штука. В целом, работает неплохо, можно извлекать разнообразные звуки. Имеет встроенные динамики, но лучше их не включать. Подключается только по USB.
2. Jammy - гитарный форм-фактор, состоит из разъединяющихся элементов с реальными струнами. Датчики независимо отслеживают удары по струнам на деке и прикосновение струны к ладу на грифе. Знакомым гитаристам не удалось сыграть на ней что-то внятное - ноты то не извлекались, то извлекались по нескольку раз. Возможно, требуется длительное привыкание. На реальных роликах в интернете также не удалось найти полноценной гитарной игры, в основном это игра медленным перебором по отдельным струнам.
3. Jamtik - игрушка с 7-ю ладами на батарейках. Сыграть на ней не удалось даже В траве сидел кузнечик.
Анализ конкурентов добавил нам оптимизма. Ни один из этих девайсов не позволяет играть реальные быстрые гитарные партии, не имеет обучающей подсветки.
На основе испытаний старого прототипа, конкурентов и личных предпочтений, я решил расширить функционал девайса и добавить несколько пунктов к требованиям:
Конечно, RGB подсветка
Подвижные струны на деке с детектированием как касания, так и величины отклонения при извлечении ноты. Это позволит избавиться от проблемы задевания пальцами соседних струн и расширит возможности игры за счет поддержки velocity (разные параметры нарастания звука и громкости ноты) и глушения струн прикосновениями как в настоящей гитаре
Детектирование силы нажатия на сенсоры грифа для реализации стандартных гитарных техник игры
Встроенный синтезатор со встроенной библиотекой инструментов и разъем Jack 3.5мм для подключения наушников или внешних колонок. Встроенные динамики делать не стал добиться хорошего звучания было бы слишком сложно и дорого
Мобильное приложение со встроенным качественным синтезатором и функционалом обучения
Пады с подсветкой для записи лупов
Упоры на деке и удобного удержания сидя и стоя, крепления для ремешка
Корпус получил множество изменений, особенно в узлах со струнами на деке и в механизме складывания. Сенсоры на грифе теперь покрыты матовыми рассеивателями и светятся всей поверхностью.
Основная задача - сделать девайс таким, чтобы был применим мануальный опыт игры на настоящей гитаре. Были проработаны расстояния между ладами, механика струн, геометрия деки и равесовка. Пока что это только рендеры корпуса, но изготовление прототипов уже идет полным ходом.
Электронику пришлось разделить на 4 платы:
Гриф
Адресные RGB светодиоды подключены последовательно к одной ноге STM-ки. Пришлось повозиться с двойной буферизацией и выводом данных через DMA 100 раз в секунду. Зато, теперь оно работает очень быстро и можно запускать цветные визуальные анимации на поверхности грифа.
Плата с падами и подпружиненными контактами для соединения с грифом в разложенном состоянии гитары
Основная плата со струнами, мозгами, силовой частью, радио частью, синтезатором и датчиками
Здесь происходит детектирование как прикосновения к струнам, так и величины их отклонения. Это решает проблему со случайными задеваниями соседней струны при игре и дает возможность глушить струны как на настоящей гитаре.
Плата с разъемами USB type-C, Jack 3.5мм и тремя индикаторными светодиодами
После выкладывания предыдущей статьи, мне написал мобильный разработчик Юрий Дубовой с предложением помочь в разработке приложения под iOS.
Мы сформировали протокол общения гитары с приложением и разбили его на несколько интерфейсов:
Midi команды, разумеется, по умолчанию передаются по стандартному BLE-Midi интерфейсу. Таким образом, к приложению при желании можно будет подключить и другие midi устройства, например, клавиатуру
Опционально поддерживается прием midi команд и по проводному USB-Midi интерфейсу. Это будет полезно для старых телефонов без поддержки BLE, а также в случае необходимости сокращения задержки до минимума (порядка 5мс)
Отдельный кастомный BLE сервис для передачи уникальных команд, связанных с управлением светодиодами, режимами работы девайса, синхронизацией состояния и т.д.
Стандартный BLE battery service для передачи уровня заряда аккумулятора. Он поддерживается на уровне операционной системы и, в случае в виндой, даже отображается соответствующая иконка в панели устройств
Приложение разбито на несколько экранов, соответствующих разным режимам работы:
В этом режиме пользователь выбирает один из инструментов (акустика, электрогитара, пианино, укулеле, барабаны, и т.д.) и просто играет как ему хочется. Есть возможность загружать свои инструменты в виде саундфонтов в формате .sf2.
В этом режиме нужно выбрать мелодию из списка табулатур и запустить интерактивное воспроизведение, при котором на гитара светодиодами подсвечивается нужный аккорд и приложение ждет пока юзер его не сыграет и зажигает следующий. При этом происходит оценка правильности и скорости игры.
Пока у нас нет даже прототипа этого режима, но предполагается интерактивное обучение нотной грамоте и гитарным основам в игровой форме с использованием светодиодов на девайсе.
Есть и другие интересные режимы, о которых я расскажу в следующей статье. Впереди еще очень много работы.
Теперь играть на ней можно тремя разными способами:
Подключение через BLE MIDI протокол к телефону или компу, где девайс распознается как миди устройство, и игра через внешние виртуальные синтезаторы (Ableton, FL studio, Garage Band и т.д. или наше приложение)
То же самое, но с подключением через USB MIDI (работает со всеми хостами, которые я проверял Android, IOS, Windows, MacOS, Debian)
Игра внутренним синтезатором, с подключением наушников или внешней колонки напрямую в гитару. В этом случае звук будет не самым Hi-Fi, но вполне приемлемым для игры для себя
Интересно, что можно играть всеми тремя способами одновременно, может кому-то пригодится.
После завершения изготовления корпуса и тестирования нового прототипа я планирую написать следующую статью, в которой будет больше технических подробностей. Если среди читателей Хабра есть люди, желающие поучаствовать в создании контента, поделиться предложениями или помочь с продвижением пожалуйста, пишите мне. А также будем рады помощи с изготовлением корпуса и разработкой мобильного приложения.
Кому интересно следить за новостями проекта или оформить предзаказ оставляйте почту в форме на сайте и подписывайтесь на соцсети.
Спасибо за внимание! Буду рад обратной связи в комментариях.
В этом посте я расскажу как можно собирать данные BLE и передавать через MQTT в системы умного дома, например в HomeAssistant.
Эта история началась в прошлом году: у меня появился несколько таких шлюзов. В то время было несколько статей по получению root доступа, интеграции miio в HA и по прошивке чистого openwrt на шлюз. Толчком к развитию стал сезон распродаж в разных магазинах, где стоимость шлюза стремилась к нулю, и многие энтузиасты получили интересную железку.
Первым большим делом для меня было заставить работать zigbee2mqtt с чипом и прошивкой находящимся в шлюзе. И пока я допиливал интеграцию в zigbee-herdsman, ребята в чатике @xiaomi_gw_hack занимались добавлением поддержки в openwrt периферии, которая была в шлюзе (светодиоды RGB, динамик, датчик света, wi-fi модуль).
Отдельное спасибо @lenz1986, @Alx2000y, @belokobylskiy!
Было обнаружено, что в wifi модуле rtl8723bs европейской версии шлюза есть встроенный bluetooth с поддержкой BLE.
Но в стоковой системе на шлюзе нет никаких следов bluetooth. И лишних uart, по которому можно было бы с ним работать тоже. @lenz1986 провел раскопки
Несколько плат очень помогли разобраться в внутреннем мире шлюзаВот как плата выглядит без процессора )Он вызвонил контакты, и обнаружил что на плате разведены все 4 UART от процессора. Один из которых вел на uart от bluetooth части модуля wifi rtl8723bs. Потом он добавил поддержку этого uart в DTB, где описываются вся периферия устройства для openwrt и нашел подходящие драйвера. За что @lenz1986 огромное спасибо!
модуля
Внимание! Все действия я описываю и делаю на базе openwrt прошивки для шлюза. Установить ее можно по воздуху просто подключившись по uart к шлюзу. (спасибо @divanikus)
Подробнее описано на https://openlumi.github.io/
Bluetooth инициализируется через rtk_hciattach при запуске
шлюза. После загрузки мы получаем такую картину
hciconfig
Я знаю 2 пути, как можно включить bluetooth адаптер.
Руками hciconfig hci0 up
изменив параметр AutoEnable
конфиге
/etc/bluetooth/main.conf
на true
Я выбираю второй. Интерфейс запущен. Для проверки можно
запустить скан hcitool lescan
Мои знания по BLE были на нуле, и чтобы было проще разобраться я искал что-то готовое по типу zigbee2mqtt. Перепробовал несколько решений на Node.Js, в том числе пакеты для node-red. Остановился на проекте EspruinoHub. (хоть и код там не супер современен и технологичен, но зато работает)
После запуска с отсылкой данных в локальный mqtt сервер, в CLI и web интерфейсе уже показались распарсенные данные с части датчиков LYWSDCGQ (круглые гигротермографы) .
Раньше я их слушал на esp32 через esphome. Небольшое сравнение получаемых данных с одного термометра.Это меня порадовало, но у меня было еще несколько устройств, данные от которых я увидел почти в raw виде. И я решил использовать эту программу и немного улучшить ее.
пример cli интерфейса с статусом доступных устройствМногие устройства Xiaomi с bluetooth шлет BLE Advertising Packet, в большинстве случаев в нем содержится полезная нагрузка в виде измерений, которые производит устройство. Часто данные отправляются открыто, но используется шифрование с ключом.
Например для браслета MiBand данные выглядят вот так. Если есть данные о пульсе то они добавляются в конец
В устройствах xiaomi, часто используется BLE сервис fe95. В интернете есть небольшая документация по нему .На github есть множество проектов которые умеют парсить эти данные. На основе этих данных и существующей реализации espruino я немного улучшил парсинг открытых данных, но потом я нашел более красивое решение из hannseman/homebridge-mi-hygrothermograph. Мне особенно понравилась стандартизация разных событий и расшифровка исходя из данных заголовка.
Этот парсер закрыл вопрос с большинством устройств Xiaomi, отправляющих данные в fe95. Можно еще попробовать добавить некоторые типы событий (движение, дым, нажатие на кнопку), но у меня нет таких устройств под рукой.
Я добавил в EspruinoHub данный парсер, и реализовал возможность указать настройки для разных устройств. Это необходимо для устройств, которые шифруют с помощью bindKey свои пакеты. Получить bindKey можно из miHome.
Данных стало больше, но хотелось чтобы они автоматически появлялись в HomeAssistant. EspruinoHub отправляет данные которые и слышит в эфире, и не имеет на данный момент привязки к конкретным устройствам. Поэтому в момент появления данных, если они из списка поддерживаемых отправляется config устройства в топик homeassistant в mqtt и устройства появляются в системе умного дома
В результате изучения разных решений и raw сообщений от устройств удалось добавить или улучшить интеграцию следующих устройств в пассивном режиме (только слушаем эфир не подключаемся к устройствам и не тратим батарейку).
LYWSDCGQ - работал "из коробки". Добавил только mqtt discovery в HA
показания переLYWSD02 - температура, влажность и батарейка
Самый бюджетный датчик температуры и влажности с экраном LYWSD03MMC - температура, влажность и батарейка (нужен bindKey). Существует 2 альтернативные прошивки, они очень крутые и продвинутые. Особенно от Виктора pvvx. Рекомендую использовать именно ее. Помимо лучшего потребления она шлет данные в одном пакете, а не в трёх и имеет множество настроек.
MI SCALE - 181d v1 По крупицам из разных источников допилена реализация в которой показываются данные о - стабилизации веса (весы моргают) - убрали вес (встали с весов) - дата и время измерения. 181b v2 Работает, но не тестировал лично. Возможно нужно что-то допилить
Mi band 3 fee0 Шаги и Пульс в режиме тренировки. Чтобы браслет отправлял данные необходимо включить обнаружение в MiFit.
Как оказалось шлюз очень хорошо и далеко слышит, а эта функция включена у многих людей. Работает это и для других моделей и даже на некоторых часах например Amazfit GTS
HHCCJCY01 MiFlora, Huahuacaocao - temperature, moisture, illuminance, conductivity, battery_level
Другие устройства тоже можно попробовать подключить. Если они шлют в кодированном виде, то в mqtt об этом будет ошибка с просьбой указать bindKey в конфиг.
YEERC - я обнаружил что прошивка для esp32 tasmota сообщает, что поддерживает данный пульт. Он идет в комплекте с многими люстрами YEELIGHT, но к сожалению у меня не получилось нигде найти как получить 32 символьный bindKey для него. Сообщения нажатий я вижу, но не могу расшифровать. (Значение event закодировано и зависит от counter который увеличивается с каждым нажатием) Возможно кто-то из читателей подскажет как добыть данный ключик. Пульт можно привязать к нескольким люстрам в разное время и они будут вместе расшифровывать и отрабатывать нажатия. Скорей всего ключ там не изменяется со временем или привязкой.
Можно установить и на другие устройства с помощью git / npm, инструкция на странице проекта EspruinoHub
Мои последние наработки собраны в пакет и ставятся с помощью opkg
Необходимо подключить фид по инструкции https://openlumi.github.io/openwrt-packages/
Дальше установить собранный пакет.
opkg updateopkg install node-espruinohub
По-умолчанию он будет пытаться подключиться к локальному mqtt
без авторизации. Если вы хотите подключить к внешнему брокеру mqtt,
то нужно изменить конфиг в
/etc/espurinohub/config.json
Внимание! у некоторых настроек в начале стоят слеши чтобы они не применялись. (конфиг в этом проекте частично сделан как пример и я не стал ничего менять)
Пример конфига можно посмотреть тут. Если вы знаете мак
адреса своих устройств то рекомендую их прописать и
установить"only_known_devices": true
, чтобы не
отправлялись данные с чужих устройств из эфира.
Для работы с HomeAssistant достаточно отправлять данные в json формате, а остальное можно выключить. По умолчанию будет отправлять в топики с группировкой по типу данных.
"mqtt_format_json": true,"homeassistant": true,"mqtt_cache_state": true
Отслеживание носимых устройств по rssi между комнатами. Для этого в конфиг я добавил возможность указать минимальный rssi в разрезе устройства и таймаут присутствия.
данные с устройств летят достаточно часто если они в зоне прямой видимости.Активные подключения: хочу попробовать управлять некоторыми устройствами по BLE, но для одновременного подключения и сканирования Bluetooth модуль и ПО могут работать нестабильно. Поэтому я пока только изучаю этот вопрос.
devbis/ble2mqtt - своя реализация на python через bleak, умеет подключаться к чайникам, но сильно грузит процессор.
Beetle-II/lumi - тот же парсер из hannseman/homebridge-mi-hygrothermograph, но без возможности задать индивидуальный ключ bindKey для устройства. Нет raw данных и управление через mqtt. + Умеет работать не только с BLE.
Спасибо, что дочитали до конца!
Если у Вас есть вопросы, то можете задавать их в комментариях.
Создание кастомного сервиса и тем более клиента Bluetooth Low Energy прогулка по граблям с завязанными глазами. По крайне мере так было для меня 4 года назад, когда я только начинал работать с BLE-устройствами. Сейчас почти каждый мой проект предусматривает использование этого протокола, поэтому в свое время пришлось в нем долго и мучительно разбираться.
Разложить все по полкам помогла книга Мохаммада Афане "Intro to Bluetooth Low Energy" и серия постов на Novel Bits. Лично для меня эта книга стала настоящим открытием. Изначально я делал ее перевод на русский для своих коллег, не имеющим опыт работы с BLE. С согласия автора (огромное ему спасибо) решил опубликовать свою работу здесь. Надеюсь, перевод окажется полезным.
Это первая часть перевода (всего их будет 5), которая рассказывает, что такое BLE, ее возможности и отличия от Bluetooth Classic и описывает архитектуру протокола.
Мохаммад Афане занимается разработкой встроенного программного обеспечения и прошивок с 2006 года. Он работал и консультировал множество крупных компаний, включая такие как Allegion (Schlage locks), Motorola, Technicolor, Audiovox, и Denon & Marantz Group. На протяжении всей своей карьеры он работал над множеством проектов Интернета Вещей, включая: беспроводные электронные дверные замки, спутниковые приемники, беспроводные дверные замки и т.д.
В июле 2015 года он принял решение прекратить работу на полную ставку для того, чтобы основать собственную компанию Novel Bits, LLC, где он делится своими знаниями и опытом на своем web-сайте, локальных тренингах и в электронных книгах, посвященных разработке приложений с поддержкой Bluetooth Low Energy.
Вы можете связаться с Мохаммадом по его электронной почте: mohammad@novelbits.io или через профиль на LinkedIn.
Bluetooth был задуман как технология связи ближнего диапазона, призванная заменить провода в таких устройствах, как компьютерные мыши, клавиатуры или персональные компьютеры. Если у вас есть современный автомобиль или смартфон, то скорее всего вы использовали Bluetooth хотя бы раз в своей жизни. Он повсюду: в громкоговорителях и колонках, беспроводных наушниках, автомобилях, носимых устройствах и даже в шлёпанцах!
Первая официальная версия стандарта была выпущена компанией Ericsson в 1994 году. Разработчики назвали свое изобретение в честь короля Дании Харальда Гормссона по прозвищу Синезубый, объединившего в 10 веке враждовавшие датские племена в единое королевство.
В настоящее время существует два типа устройств с поддержкой Bluetooth:
Bluetooth Classic (BR/EDR), используется в беспроводных громкоговорителях, автомобильных информационно-развлекательных системах и наушниках;
Bluetooth Low Energy (BLE), т.е. Bluetooth с низким энергопотреблением, который появился в версии стандарта Bluetooth 4.0. Он чаще всего применяется в приложениях, чувствительных к энергопотреблению (например в устройствах с батарейным питанием) или в устройствах, передающих небольшие объемы данных с большими перерывами между передачами (например, разнообразные сенсоры параметров окружающей среды или управляющие устройства, такие как беспроводные выключатели).
Эти два типа устройств несовместимы друг с другом, даже если они выпущены под одним брендом или спецификацией. Устройства с поддержкой Bluetooth Classic не могут напрямую связываться с устройствами, использующими BLE. Это причина, по которой некоторые устройства, такие как смартфоны, выполняются с поддержкой обоих типов соединения (так называемые Dual mode Bluetooth devices), что позволяет им обмениваться информацией с обоими типами устройств.
Рис.1: Типы Bluetooth-устройствНесколько важных замечаний о BLE:
Официальная спецификация Bluetooth сочетает оба типа Bluetooth (Classic и BLE), что иногда затрудняет поиск документации, специфичной для BLE;
BLE был введен в версии 4.0 спецификации стандарта Bluetooth, выпущенной в 2010 году;
BLE иногда называют Bluetooth Smart, BTLE или Bluetooth 4.0, что является ошибкой, так как эта версия в действительности включает оба типа Bluetooth;
Bluetooth Classic и BLE работают в одном и том же частотном диапазоне 2.4 ГГц, ISM-диапазон.
Поскольку во многих устройствах Интернета Вещей (IoT) используются небольшие устройства и датчики, BLE стал наиболее часто используемым протоколом связи (в сравнении с Bluetooth Classic) в приложениях Интернета Вещей. В декабре 2016 года группа компаний Bluetooth Special Interest Group (SIG), регулирующая развитие стандарта, выпустила Bluetooth версии 5.0 (для простоты маркетинга была убрана точка из названия, так что официально он называется Bluetooth 5). Большинство улучшений и новых функций, представленных в этой версии, были ориентированы на BLE, а не на Bluetooth Classic.
Вы также могли слышать о другом термине, связанном с Bluetooth Bluetooth Mesh. Bluetooth Mesh был выпущен в июле 2017 года и основан на BLE. Для работы ему требуется полный стек BLE (ПО, которое действует как интерфейс для другого программного или аппаратного обеспечения), но он не является частью основной спецификации Bluetooth. Мы рассмотрим более подробно эту технологию в отдельной главе.
Подводя итог, посмотрим на диаграмму, показывающую прогресс BLE за прошедшие годы с начала его появления:
Рис.2: История BLEНекоторые из наиболее важных технических фактов о BLE включают в себя:
Используемый частотный диапазон 2.400 - 2.4835 ГГц.
Весь частотный диапазон поделен на 40 каналов по 2 МГц каждый.
Максимальная скорость передачи данных по радиоканалу (начиная с Bluetooth версии 5) 2Мбит/с.
Дальность передачи сильно зависит от физического окружения, а также используемого режима передачи. Например, в режиме большой дальности передачи дальность связи будет выше, а скорость передачи ниже, чем в высокоскоростном режиме. Типичная дальность передачи: 10-30 метров.
Потребление электроэнергии также может изменяться в широких пределах. Оно зависит от реализации устройства, различных параметров протокола и используемого чипсета. Типичное потребление BLE-трансивера во время передачи данных как правило не превышает 15 мА.
Обеспечение безопасности не обязательно при обмене данными через BLE и зависит от устройства и реализации приложения разработчиком. Другими словами, существует несколько возможных для реализации уровней обеспечения безопасности.
Для всех операций, связанных с шифрованием, BLE использует алгоритм AES-CCM с длиной ключа 128 бит.
BLE предназначен для передачи данных по каналу с низкой пропускной способностью. Использование BLE для приложений с большим объемом часто передаваемых данных существенно увеличивает потребление электроэнергии и сводит на нет основное преимущество BLE. То есть минимизация использования радиосвязи, насколько это возможно, позволяет достичь минимального уровня потребления энергии.
Версии Bluetooth (в части BLE) являются обратно совместимыми. Тем не менее возможности связи будут ограничены функциями более старой версии. Например, устройство с поддержкой Bluetooth 5 LE может установить связь с устройством с поддержкой Bluetooth 4.1 LE, но возможности, появившиеся в версии 4.2 и более новых, будут недоступны. В то же время они смогут использовать возможности подключения, рассылки и приема широковещательных пакетов, обнаруживать сервисы и характеристики, а также читать и записывать их независимо от поддерживаемой ими версии стандарта, так как эти возможности доступны во всех версиях Bluetooth.
Важно помнить, что существует большая разница между классическим Bluetooth и Bluetooth с низким энергопотреблением с точки зрения технических спецификаций, реализации и типов приложений, для которых они предназначены. Это в дополнение к тому факту, что они несовместимы друг с другом.
Некоторые из упомянутых различий представлены в этой таблице:
Таблица 1. Сравнение Bluetooth Classic и BLE
Bluetooth Classic |
BLE |
Используется для потоковых приложений, таких как трансляция аудио и передача файлов |
Используется в сенсорах, управлении устройствами и приложениях, не требующих передачи больших объемов данных |
Не оптимизирован для низкого энергопотребления, но поддерживает большую скорость передачи (максимум 3 МБит/с, в то время как BLE 5 имеет максимум 2 МБит/с) |
Предназначен для применения в малопотребляющих устройствах с большими интервалами между передачей данных |
Использует 79 радиоканалов |
Использует 40 радиоканалов |
Обнаружение происходит на 32 каналах |
Обнаружение происходит на 3 каналах, что приводит к более быстрому обнаружению и установке соединения по сравнению с Bluetooth Classic |
С момента официального выпуска в 2010 году BLE прошел череду ревизий и изменений. Наиболее важное изменение произошло в декабре 2016 года с внедрением Bluetooth 5, который привнес множество важных улучшений в спецификацию стандарта, большинство из которых касалось BLE. Эти улучшения позволили удвоить скорость передачи, в 4 раза увеличить дальность передачи и в 8 раз увеличить размер широковещательного пакета.
Каждая технология имеет свои ограничения, и BLE не является исключением. Как мы упомянули ранее, BLE наилучшим образом подходит для приложений с небольшим радиусом передачи и редко передаваемыми небольшими объемами данных.
Пропускная способность BLE ограничена физической пропускной способностью радиоканала, т.е. скоростью, с которой данные передаются по радиоканалу. Пропускная способность зависит от используемой версии Bluetooth. Для Bluetooth 4.2 и более ранних, доступна только пропускная способность в 1 Мбит/с. В Bluetooth 5 и более поздних версиях пропускная способность зависит от выбранного режима PHY (Physical Layer, рассматривается в разделе физического уровня). Она может составлять 1 Мбит/с как в более ранних версиях или 2 Мбит/с при использовании высокоскоростной передачи. При использовании функции дальней связи пропускная способность ограничена значениями 500 или 125 Мбит/с. Мы обсудим это более подробно в главе, посвященной Bluetooth 5.
Скорость передачи с точки зрения конечного пользователя всегда будет ниже скорости передачи по радиоканалу в силу следующих факторов:
Промежутки между пакетами данных: спецификация Bluetooth определяет зазор в 150 микросекунд между передаваемыми пакетами как требование для соблюдения спецификации. В этот промежуток времени невозможна передача данных между устройствами.
Служебная информация внутри пакета: каждый пакет содержит помимо полезной нагрузки заголовок и служебные данные, обрабатываемые на уровнях ниже уровня приложения. Они учитываются при передаче данных, но не используются вашим приложением.
Требование на передачу служебной информации периферийным устройством: спецификация требует обязательного ответа ведомого устройства на каждый пакет, переданный ведущим. В случае, когда необходимая для передачи информация отсутствует, передается пустой пакет.
Переотправка пакетов данных: в случае потери пакета или перекрестных помех от находящихся поблизости устройств, потерянные или поврежденные данные отправляются заново.
BLE был разработан для применения на коротких расстояниях, и, следовательно, его диапазон действия ограничен. Вот некоторые факторы, ограничивающие дальность передачи при помощи BLE:
На передачу в ISM-диапазоне 2.4 ГГц сильно влияют окружающие нас препятствия, такие как металлические предметы, бетонные стены, вода и человеческие тела.
Диаграмма направленности и коэффициент усиления антенны.
Корпус устройства, в котором находится антенна, также ухудшает характеристики антенны.
Ориентация устройства в пространстве, от которого зависит ориентация антенны, например в смартфонах.
Для передачи данных с устройства, поддерживающего только BLE-соединение, необходимо другое устройство с поддержкой как BLE, так и IP-соединения. Именно оно будет получать данные и отправлять их в интернет.
Даже с учетом представленных выше ограничений BLE имеет некоторые существенные преимущества перед другими аналогичными технологиями передачи данных для IoT.
Вот некоторые из них:
Меньшее энергопотребление;
По сравнению с другими низкопотребляющими технологиями передачи данных, BLE потребляет гораздо меньше электроэнергии. Это достигается благодаря глубокой оптимизации протокола, выключению передатчика при первой возможности и пересылке малых объемов данных на низкой скорости.
Бесплатный доступ к официальным спецификациям;
Чтобы получить доступ к спецификациям большинства других протоколов вы должны стать членом официальной группы или консорциума по этому стандарту. Стать членом можно за внушительную сумму (от 7500 до 35000 долларов в год). В случае с BLE, спецификации для основных версий (4.0, 4.1, 4.2, 5) доступны для загрузки с сайта Bluetooth абсолютно бесплатно.
Низкая цена модулей и чипсетов по сравнению с другими технологиями;
Наконец, не менее важный фактор наличие в большинстве смартфонов на рынке. Возможно, это наибольшее преимущество BLE перед такими технологиями как ZigBee, Z-Wave и Thread.
Исходя из ограничений и преимуществ, указанных выше, существуют варианты использования, где BLE раскрывается наиболее полно:
Малый объем передаваемых данных;
BLE подходит для случаев, когда устройство передает небольшие объемы данных, например, данные датчиков или команды исполнительных устройств.
Настройка устройств;
В случаях, когда BLE не удовлетворяет основным требованиям системы, он может использоваться для настройки устройства до того, как оно окажется подключенным к основной сети передачи данных.
Например, некоторые устройства с поддержкой WiFi добавляют BLE как вспомогательный протокол вместо использования таких технологий как WiFi Direct. Это технология, которая позволяет двум устройствам с поддержкой WiFi соединяться напрямую, минуя роутер. Вы можете узнать подробнее о ней на Википедии или здесь.
Использование смартфона в качестве интерфейса;
Компактные малопотребляющие устройства обычно не имеют больших экранов и зачастую отображают ограниченное количество данных конечному пользователю,например, путем светодиодной индикации. В настоящее время, благодаря широкому распространению смартфонов, BLE может предложить альтернативный, гораздо более информативный и удобный интерфейс для этих устройств. Еще одним преимуществом смартфона является возможность загрузки данных в облако.
Персональные и носимые устройства;
Для случаев, когда устройство является носимым и находится вне зоны покрытия беспроводных сетей (таких как WiFi или сотовая связь), BLE может оказаться единственным доступным способом подключения.
Устройства без возможности установления соединения.
Вероятно вы слышали или видели ранее такие устройства как маячки. У этих устройств одна простая задача выдавать через определенные промежутки времени в эфир данные так, чтобы другие устройства могли их обнаружить и принять передаваемые данные. Существуют и другие технологии, которые могут быть использованы для этих целей. Тем не менее BLE становится все более популярным, так как большинство людей имеют смартфоны, которые поддерживают BLE из коробки.
Все вышеперечисленные сценарии только выигрывают от использования BLE. С другой стороны, есть условия, при которых, как правило, использование BLE невозможно или не дает ощутимых преимуществ, такие как:
Потоковая передача видео;
Трансляция высококачественного звука (прим.: стала возможна в BLE 5.2);
Передача больших объемов данных в течении длительного времени в тех случаях, когда важно сокращение энергопотребления.
Рисунок ниже иллюстрирует различные уровни, присущие архитектуре BLE. Три главных блока в этой архитектуре приложение, хост и контроллер.
Рис.3: Архитектура BLEВ этой книге мы сфокусируемся на верхних уровнях архитектуры, кратко ознакомившись с нижними уровнями в этой главе. Подробное описание верхних уровней GAP (Generic Access Profile), GATT (Generic Attribute Profile) и Security Manager вынесем в отдельные главы.
Прикладной уровень зависит от варианта использования девайса/приложения и относится к реализации на основе общего профиля доступа (GAP) и общего профиля атрибутов (GATT) он отвечает за то, как ваше приложение обрабатывает данные, полученные от других устройств и отправленные на них, а также управляющую логику.
Эта часть является кодом, который вы написали для своего приложения и, как правило, не является частью BLE-стека для платформы, под которую вы разрабатываете. Эта часть не рассматривается в книге, поскольку она зависит от специфики вашего приложения и способа использования.
Хост включает следующие уровни:
Общий профиль доступа (GAP, Generic Access Profile);
Общий профиль атрибутов (GATT, Generic Attribute Profile);
Протокол атрибутов (ATT, Attribute Protocol);
Менеджер безопасности (SM, Security Manager);
Протокол управления и адаптации логических связей (L2CAP, Logical Link Control and Adaptation Protocol);
Интерфейс хост-контроллера (HCI, Host Controller Interface), зона ответственности хоста.
Контроллер включает следующие уровни:
Физический уровень (PHY, Physical Layer);
Слой связи (Link Layer);
Режим прямого тестирования (DTM, Direct Test Mode);
Интерфейс хост-контроллера (HCI, Host Controller Interface), зона ответственности контроллера.
PHY относится к части оборудования, ответственного за прием, передачу, модуляцию и демодуляцию сигнала. BLE работает в ISM-диапазоне (2.4 ГГЦ), который разделен на 40 каналов по 2 Мгц, как показано на рисунке ниже:
Рис.4: Частотный спектр и радиоканалы в BLEТри выделенных канала носят название Первичных Широковещательных Каналов, в то время, как оставшиеся 37 используются в роли Вторичных Широковещательных и для передачи данных во время соединения. Мы подробно рассмотрим принципы их использования в разделе Адвертайзинг и сканирование, но для начала кратко ознакомимся с ними в этой главе.
Адвертайзинг заключается в рассылке широковещательных пакетов по трем Первичным Каналам Адвертайзинга (или части из них). Это дает возможность обнаружить широковещающее устройство и прочитать его данные сканирующим устройствам. После этого сканирующее устройство может инициировать соединение, если широковещающее разрешает подключение. Также сканирующее устройство может послать запрос на сканирование, и, если широковещающее устройство поддерживает эту функцию, то оно пошлет ответ на сканирование. Запросы на сканирование и ответы на него позволяют передавать дополнительные данные без подключения к устройству.
Вот некоторые другие важные технические детали, касающиеся физического уровня передачи BLE:
Он использует скачкообразную перестройку несущей частоты (FHSS, Frequency Hopping Spread Spectrum), что позволяет двум взаимодействующим устройствам переключаться на случайные предварительно согласованные частоты для обмена данными. Это значительно повышает надежность и позволяет устройствам избегать перегруженных каналов.
Мощность передачи может быть:
Не более: 100 мВт (+20 дБм) для версии 5 и более новых, 10 мВт (+10 дБм) для версии 4.2 и более старых;
Не менее: 0.01 мВт (-20 дБм).
В старых версиях Bluetooth (4.0, 4.1 и 4.2) была доступна только одна скорость передачи 1 Мбит/с. Физический уровень радио (PHY) в этом случае называется 1M PHY и является обязательным во всех версиях, включая Bluetooth 5. В Bluetooth 5 были также введены два новых дополнительных PHY:
2 Мбит/с PHY, используемый для удвоения скорости передачи по сравнению с более ранними версиями Bluetooth.
Зашифрованный PHY, используемый для связи на дальних расстояниях.
Мы рассмотрим эти два новых PHY и концепцию кодирования в главе, посвященной Bluetooth 5.
Канальный уровень отвечает за взаимодействие с физическим уровнем радио и предоставление другим уровням абстракции для взаимодействия с радио (через промежуточный уровень интерфейса хост-контроллера, который мы вскоре обсудим). Он отвечает за управление состоянием радио и соблюдение требований к временным задержкам, необходимых для удовлетворения спецификации BLE. Также он отвечает за управление аппаратно-ускоренными операциями, такими как вычисление контрольных сумм, генерацию случайных чисел и шифрование.
Существует три основных состояния, в которых может находиться устройство с BLE:
Широковещательное состояние (Advertising);
Состояние сканирования (Scanning);
Подключенное состояние.
Когда устройство посылает широковещательные пакеты, оно позволяет сканирующим устройствам обнаружить себя и подключиться. Если широковещающее устройство допускает подключения, сканирующее устройство нашло его и послало запрос на подключение, оба они переходят в подключенное состояние.
Канальный уровень управляет различными состояниями радио, показанными на рисунке:
Рис.5: Состояния канального уровняStandby: состояние по умолчанию, когда радио не передает и не принимает никаких данных.
Advertising: состояние, в котором устройство посылает широковещательные пакеты для обнаружения и чтения другими устройствами.
Scanning: состояние, в котором устройство ищет устройства, посылающие широковещательные пакеты.
Initiating: состояние, в котором начинается процесс установки соединения с устройством, находящимся в состоянии advertising.
Connected: Состояние, в котором одно устройство установило соединение с другим и регулярно обменивается с ним информацией. В подключенном состоянии устройство, которое находилось в состоянии scanning и инициировало соединение, называется ведущим. Устройство, которое рассылало широковещательные пакеты, называется ведомым.
Мы рассмотрим эти состояния более подробно в последующих главах.
Bluetooth адрес:
Bluetooth-устройства идентифицируются посредством 48-битного адреса, похожего на MAC-адрес. Существуют два основных типа адресов: публичный и случайный.
Публичный адрес:
Это фиксированный адрес, запрограммированный на фабрике. Он не может быть изменен и должен быть зарегистрирован в IEEE (также, как и MAC-адреса устройств с поддержкой WiFi или Ethernet).
Случайный адрес:
Так как у производителей есть возможность выбирать, какой тип адреса использовать (публичный или случайный), случайные адреса встречаются более часто, так как они не требуют регистрации в Институте инженеров электротехники и электроники. Случайный адрес программируется на устройстве или генерируется в ходе выполнения программы. Он может относиться к одному из следующих подтипов:
Статический адрес
Используется в качестве замены публичного адреса;
Может быть заново сгенерирован при загрузке кода или оставаться постоянным в течение всего срока службы;
Не может изменяться при включении или выключении.
Частный адрес включает в себя следующие подтипы:
Неразрешимый частный адрес:
Случайный, генерируется на определенный промежуток времени;
Широко не используется.
Разрешимый частный адрес:
Используется для обеспечения безопасности;
Генерируется с использованием ключа (IRK, Identity Resolving Key) и случайного числа;
Периодически меняется (даже во время соединения);
Используется для защиты от отслеживания злоумышленниками;
Доверенные устройства (связанные, описанные в главе, посвященной безопасности) могут расшифровать адрес, используя предварительно сохраненный ключ.
Режим прямого тестирования (DTM, Direct Test Mode) используется исключительно для проведения испытаний радиочасти во время производства или сертификационных испытаний. Он не относится напрямую к теме нашей книги, поэтому мы оставим его без подробного рассмотрения.
Интерфейс хост-контроллера это стандартный протокол, определенный спецификацией Bluetooth, который позволяет уровню хоста коммуницировать с уровнем контроллера. Эти уровни могут быть реализованы на двух раздельных микросхемах или существовать на одной. В этом смысле он также обеспечивает взаимодействие между микросхемами, поэтому разработчик устройства может выбрать два сертифицированных Bluetooth-устройства, контроллер и хост, и быть на 100% уверенным в том, что они совместимы друг с другом в плане связи между уровнями хоста и контроллера.
В случае, когда хост и контроллер находятся на разных микросхемах, связь между ними может быть реализована посредством трех официально поддерживаемых физических интерфейсов: UART, USB или SDIO (Secure Digital Input Output). В случае, когда хост и контроллер находятся на одной и той же микросхеме, интерфейс хост-контроллера будет логическим интерфейсом.
Задача интерфейса хост-контроллера состоит в передаче команд от хоста контроллеру и передаче информации и событий от контроллера к хосту. На рисунке ниже приведен пример обмена командами и событиями между уровнями хоста и контроллера.
Рис.6: Пример пакетов интерфейса хост-контроллераПримеры сообщений включают в себя: пакеты команд, настройку контроллера, запрос действий, управление параметрами соединения, пакеты событий, завершение команд и события состояния.
Протокол L2CAP предоставляет услуги по работе с данными, как ориентированные на соединения, так и без ориентации на них, протоколам более высокого уровня с возможностями мультиплексирования и обеспечения операций по сегментации и обратной сборке. Он заимствован из стандарта Bluetooth Classic и в случае BLE выполняет следующие задачи:
Принимает несколько протоколов с верхних уровней и помещает их в стандартные пакеты BLE, которые передаются на нижние уровни под ним.
Управляет фрагментацией и рекомбинацией пакетов. Он берет большие пакеты с верхних уровней и разбивает их на порции, которые соответствуют максимальному размеру полезной нагрузки BLE, поддерживаемому для передачи. На стороне получателя он принимает несколько пакетов и объединяет их в один пакет, который может быть обработан верхними уровнями.
В случае BLE уровень L2CAP управляет двумя основными протоколами: протоколом атрибутов (ATT, рассмотрен в главе, посвященной GATT) и протоколу управления безопасностью (SMP, рассмотрен в главе, посвященной безопасности).
Протокол атрибутов (АТТ), общий профиль атрибутов (GATT), менеджер безопасности (SM) и общий профиль доступа (GAP) будут подробно рассмотрены в следующих главах.
На этом заканчивается первая глава книги. Большое спасибо дочитавшим. Следующая часть будет посвящена классам устройств и адвертайзингу методу, с помощью которого устройства сообщают о своем присутствии окружающему миру.
Это вторая часть перевода книги Мохаммада Афане Intro to Bluetooth Low Energy. В представленных главах мы поговорим о типах устройств и об адвертайзинге, методе, с помощью которого периферийные устройства сообщают о своем присутствии.Первая часть здесь.
Хочу сразу отметить, что адвертайзинг может использоваться не только для обнаружения устройств, но и для отправки кастомных данных. Например, в портативном мониторе качества воздуха Atmotube, пакеты адвертайзинга и ответа на сканирование используются для передачи сведений о текущих показаниях сенсоров. Это удобно для контроля показаний сенсоров на этапе производства и при сборе данных несколькими устройствами.
Существуют несколько важных определений, с которыми вы будете постоянно сталкиваться при изучении BLE. Два наиболее важных касаются ролей устройства: BLE central и BLE peripheral.
Рассмотрим их более детально.
Периферийное устройство устройство, которое объявляет о своем присутствии путем адвертайзинга, т.е.рассылки широковещательных пакетов, и принимает запросы на соединение от центральных устройств.
Другой связанный термин BLE-передатчик, устройство, которое также рассылает широковещательные пакеты, но имеет одно отличие от периферийного устройства:оно не разрешает другим устройствам устанавливать с ним соединение. С другой стороны, устройство-наблюдатель только обнаруживает устройства, производящие процедуру адвертайзинга, но не имеет возможности инициировать соединение с ними.
Типичный пример устройства, реализующего роль передатчика маячок (beacon). Маячки это устройства, которые передают информацию без возможности установить с ними соединение. Они очень популярны в двух сферах: розничная торговля и определение местоположения внутри помещений.
Например, некоторая сеть магазинов использует мобильное приложение, которое может обнаруживать маячки на территории магазина. Если покупатель, имеющий на своем смартфоне это же приложение, приблизится к маячку, то в приложении отобразится специальная скидка на группу товаров, связанную с маячком.
Отличить маячок от периферийного устройства можно по типу широковещательных пакетов, которые он передает. Существуют различные типы пакетов: некоторые указывают на возможность установить соединение, а другие просто указывают на наличие маячка в этом месте. Когда центральное BLE-устройство обнаруживает широковещательные пакеты другого устройства с BLE (будь оно маячком или периферийным), оно знает, может ли оно начинать процедуру установки соединения или нет в зависимости от типа принятых широковещательных пакетов.
Как только периферийное устройство подключается к центральному, оно принимает на себя роль ведомого. Центральное устройство в таком случае называется ведущим. Это роли, определенные на канальном уровне, тогда как роли периферийного и центрального устройства определены на уровне GAP.
Центральное устройство устройство, которое обнаруживает периферийные устройства и считывает передаваемую ими информацию. Оно также может устанавливать соединение с одним или несколькими устройствами одновременно.
Наблюдатель устройство, схожее по функционалу с центральным, но не имеющее возможности устанавливать соединение с другим устройством.
Рассмотрим возможности и ограничения четырех типов устройств: передатчик, наблюдатель, периферийное и центральное устройство.
Передатчик |
Периферийное устройство |
Наблюдатель |
Центральное устройство |
Не требует наличия приемника |
Требует наличия как приемника, так и передатчика |
Не требует наличия передатчика |
Требует наличия как приемника, так и передатчика |
Не поддерживает двунаправленный обмен данными |
Поддерживает двунаправленный обмен данными |
Не поддерживает двунаправленный обмен данными |
Поддерживает двунаправленный обмен данными |
Упрощенная схема, уменьшенный размер программного стека BLE |
Требует полного программного стека BLE |
Упрощенная схема, уменьшенный размер программного стека BLE |
Требует полного программного стека BLE |
Табл. 1: Сравнение типов устройств
Протокол BLE асимметричен. Большая часть тяжелой работы, связанной с управлением соединениями, управлением временем и обработкой информации, лежит на центральном устройстве. Это помогает снизить энергопотребление и требования к вычислительной мощности периферийного устройства, что позволяет интегрировать BLE в компактные устройства с ограниченными ресурсами, например, устройства с батарейным или аккумуляторным питанием.
Центральное устройство BLE также может иметь батарейное питание, но обычно имеет перезаряжаемый аккумулятор большой емкости. Как правило, роль центрального устройства на себя берет смартфон, компьютер или планшет.
Центральное устройство может быть подключено к нескольким периферийным одновременно. Характерный пример смартфон, подключенный к умным часам, термостату умного дома и фитнес-трекеру одновременно.
В некоторых случаях поддержка BLE-устройством центральной и периферийной роли одновременно приносит заметную пользу. Например, устройство может контролировать несколько датчиков (периферийных устройств) и в то же время иметь возможность передавать данные с этих датчиков на смартфон, обеспечивая доступ к ним из интерфейса мобильного приложения.
Рис. 1: Смартфон в качестве многоролевого устройстваОдним из наиболее значимых преимуществ BLE перед другими похожими малопотребляющими технологиями, такими как ZigBee, Z-Wave, Thread и др.,) является его наличие в большинстве смартфонов, представленных на рынке. Практически все смартфоны уже имели на борту Bluetooth Classic с самых ранних дней, и большинство производителей чипсетов Bluetooth теперь внедряют в свои чипы поддержку и BLE, и Bluetooth Classic. В результате в настоящее время подавляющее большинство смартфонов поддерживает BLE.
Для смартфона возможность взаимодействовать с устройствами BLE дает пару существенных преимуществ:
Смартфоны предоставляют пользователям привычный интерфейс. Использование мобильного приложения для взаимодействия с BLE-устройством зачастую оказывается удобнее непосредственного взаимодействия с этим устройством.
Смартфоны, как правило, постоянно подключены к Интернету. Это означает, что данные, полученные с BLE-устройства, могут быть переданы в облако и сохранены для последующего анализа и обработки.
В настоящий момент существуют две основные мобильные операционные системы: Android и iOS. Android представил встроенную поддержку BLE API в версии Android 4.3 (выпущена в июле 2012 года), в то время как iOS сделал то же самое немного раньше в октябре 2011 года.
Важно отметить, что многое зависит от возможностей аппаратного обеспечения операционной системы. В случае с iOS, поддержку BLE имеют все устройства, начиная с iPhone 4s. Ситуация с Android гораздо сложнее. Эта операционная система работает на устройствах разных производителей с разной аппаратной конфигурацией, поэтому нет простого способа определить, какие устройства первыми начали поддерживать BLE. Эта проблема фрагментации Android представляет большую проблему при разработке приложений, использующих BLE, которые должны работать одинаково на всех существующих Android-устройствах.
Общий профиль доступа предоставляет фреймворк, который определяет способы взаимодействия BLE-устройств друг с другом. Он включает в себя следующие аспекты:
Режимы и роли устройств;
Обнаружение устройств: рассылка пакетов адвертайзинга, сканирование, параметры рассылки и сканирования, содержимое пакетов;
Установка соединения: инициация, подтверждение, параметры соединения;
Обеспечение безопасности
Реализация этого фреймворка является обязательной согласно официальной спецификации, и это то, что позволяет устройствам BLE обмениваться данными и взаимодействовать друг с другом.
Мы кратко осветили состояния сканирования и рассылки пакетов адвертайзинга BLE-устройств и упомянули, что периферийное устройство всегда запускается в состоянии рассылки пакетов адвертайзинга, даже если оно предназначено для работы в подключенном состоянии большую часть времени. Чтобы два устройства могли обнаружить друг друга, одно из них должно рассылать пакеты адвертайзинга, а другое сканировать первичные широковещательные каналы (радиоканалы 37, 38, 39) в поисках пакетов адвертайзинга, отправленных периферийным устройством.
Если периферийное устройство поддерживает возможность подключения и центральное устройство обнаружило его, они могут установить соединение. В этой главе мы сфокусируемся на начальных состояниях периферийного и центрального устройства: адвертайзинг и сканирование.
В состоянии адвертайзинга устройство рассылает пакеты, содержащие полезную информацию для других устройств, чтобы они приняли и обработали её. Пакеты посылаются через определенные интервалы времени, которые называются интервалы адвертайзинга.
В BLE существуют 40 радиоканалов, разнесенных на 2 МГц (от центра до центра), как показано на рисунке ниже. Три канала называются каналами первичного адвертайзинга, в то время как оставшиеся 37 каналов используются для вторичного адвертайзинга, а также для передачи пакетов данных во время соединения.
Рис. 8: Радиоканалы в BLEЗамечание: Так как устройство посылает пакеты адвертайзинга на одном из этих каналов и, как правило, постоянно переключается между ними, они (каналы) разнесены далеко по спектру друг от друга для того, чтобы избежать перекрестных помех между устройствами, вещающими на разных каналах. Также, расположение этих каналов на спектре выбрано таким, чтобы избежать помех от наиболее часто используемых Wi-Fi каналов.
Процесс адвертайзинга всегда начинается с посылки широковещательного пакета по трем первичным каналам адвертайзинга или части из них. Это позволяет центральным устройствам найти периферийные и прочитать и пакеты адвертайзинга. Затем центральное устройство может запустить процесс подключения, если периферийное устройство поддерживает такую возможность.
Также центральное устройство может послать запрос на сканирование и, если периферийное поддерживает такую возможность, оно пошлет пакет, содержащий ответ на сканирование. Запросы на сканирование и ответы позволяют периферийному устройству отправить дополнительные данные, которые не поместились в основной пакет адвертайзинга без установки соединения.
Примечание: длина первичного пакета адвертайзинга ограничена 31 байтами. Длина вторичного пакета адвертайзинга может составлять до 254 байт.
Как мы упоминали ранее, некоторые устройства (маячки) всегда остаются в состоянии адвертайзинга и не принимают запросы на подключение, в то время как другие (периферийные устройства) позволяют переход в подключенное состояние, если центральное устройство инициирует соединение.
Главное преимущество нахождения в состоянии адвертайзинга состоит в том, что множество центральных устройств могут получать данные с одного периферийного без необходимости в подключении. В то же время есть существенные недостатки, такие как отсутствие защиты данных и невозможность для периферийного устройства получать данные от центрального устройства (передача данных является однонаправленной).
Рис. 9: Устройства, имеющие и не имеющие возможность подключенияЦентральные устройства, находясь в поиске пакетов адвертайзинга от периферийных устройств, перестраиваются между тремя первичными каналами адвертайзинга, прослушивая каждый из них в определенный момент времени. Для того, чтобы центральное устройство обнаружило периферийное, оно должно быть настроено на тот же канал, на котором в данный момент вещает периферийное. Для того, чтобы увеличить вероятность этого события и ускорить его наступление, можно изменять некоторые параметры адвертайзинга и сканирования.
Устройство, которое прослушивает каналы адвертайзинга в поисках пакетов адвертайзинга а затем посылает запросы сканирования, находится в режиме активного сканирования, а устройство, которое только принимает пакеты адвертайзинга, и не посылает запросов на сканирование, соответственно находится в режиме пассивного сканирования.
Рис. 10: Пассивное и активное сканированиеСобытие адвертайзинга состоит из нескольких пакетов, отправленных по всем или нескольким из трех каналов первичного адвертайзинга (37, 38 и 39). Существует семь типов событий адвертайзинга (их можно рассматривать как различные типы пакетов):
Подключаемое и сканируемое ненаправленное событие.
Этот тип позволяет другим устройствам принимать пакеты, посылать запросы сканирования отправителю и устанавливать с ним соединение.
Подключаемое ненаправленное событие.
Позволяет другим устройствам принимать пакеты и устанавливать соединение с их отправителем.
Подключаемое направленное событие.
Позволяет определенному устройству принимать пакеты и устанавливать соединение с отправителем
Неподключаемое и несканируемое ненаправленное событие.
Позволяет всем устройствам принимать пакеты. В то же время отклоняет все запросы сканирования и попытки установить соединение.
Неподключаемое и несканируемое направленное событие.
Позволяет определенному устройству принимать пакеты без возможности установить соединение или послать запрос сканирования.
Сканируемое ненаправленное событие.
Дает возможность другим устройствам посылать запросы сканирования отправителю для получения дополнительного пакета данных.
Сканируемое направленное событие.
Позволяет определенному устройству посылать запросы сканирования отправителю пакета адвертайзинга для получения дополнительного пакета данных.
Под параметрами адвертайзинга понимают:
Интервал адвертайзинга.
Наиболее важный параметр из относящихся к адвертайзингу это интервал адвертайзинга. Значение этого параметра может дискретно изменяться в пределах от 20 миллисекунд до 10.24 секунд, с шагом в 625 микросекунд. Интервал адвертайзинга оказывает большое влияние на продолжительность работы от батареи, поэтому выбору его значения следует уделить самое пристальное внимание. Рекомендуется выбирать наибольший интервал адвертайзинга, позволяющий соблюсти баланс между скоростью обнаружения и энергопотреблением.
Данные адвертайзинга и ответа на сканирование.
Давайте посмотрим на формат пакета адвертайзинга и составляющие его поля. Стоит отметить, что пакет ответа на сканирование использует такой же формат.
Рис. 11: Формат пакета адвертайзинга (из спецификации стандарта Bluetooth 5)Данные адвертайзинга используют формат, аналогичный формату TLV (Type-Length-Value, Тип-Длина-Значение), используемому для передачи данных. Отличие состоит в том, что в пакетах адвертайзинга длина данных следует перед их типом. Данные адвертайзинга входят в состав протокольных данных (PDU, Protocol Data Unit) BLE-пакета и включает в себя:
Длину: длину данных, которые следуют за самим значением длины, включая тип данных и непосредственно данные.
Тип данных адвертайзинга: тип данных адвертайзинга, содержащихся в этой структуре TLV.
Данные адвертайзинга: непосредственно данные.
Типы данных адвертайзинга определены в дополнении спецификации Bluetooth (не в основном документе).
Ниже приведены одни из наиболее часто встречающихся типов данных:
Local Name: имя устройства, считываемое при его обнаружении другими устройствами, производящими процедуру сканирования.
Tx Power Level: Мощность передачи, измеряемая в дБм.
Flags: множество однобитных логических флагов (переменные, которые могут принимать одно из двух значений, истина [1] или ложь [0], включающее в себя:
Limited Discoverable Mode (ограниченный режим обнаружения);
General Discoverable Mode (общий режим обнаружения);
BR/EDR Not Supported (возможность поддержки классического протокола Bluetooth);
Возможность одновременной поддержки классического и Low Energy Bluetooth на одном устройстве со стороны контроллера;
Возможность одновременной поддержки классического и Low Energy Bluetooth на одном устройстве со стороны хоста.
Примечание: понятия BR (Basic Rate, базовая пропускная способность) и EDR (Enhanced Data Rate, расширенная пропускная способность) относятся к Bluetooth Classic.
Service Solicitation: список из одного или нескольких UUID, показывающий, какие сервисы поддерживаются и представлены GATT-сервером устройства. Это помогает центральному устройству узнать о поддерживаемых периферийным устройством сервисах до установления соединения.
Appearance: вид, определяет тип устройства в соответствии со спецификацией стандарта. Включает в себя такие виды как телефон, измеритель сердечного ритма, брелок для ключей и множество других.
Если вы не можете найти вид, к которому можно было бы отнести ваше устройство, вы всегда можете оставить ему значение по умолчанию Неопределенный.
Существует три основных параметра сканирования:
Scan Type (тип сканирования): пассивное или активное.
Scan Window (окно сканирования): определяет, длительность сканирования.
Scan Interval (интервал сканирования): определяет частоту повторения сканирования.
Центральное устройство прослушивает один из первичных каналов адвертайзинга в течении всего окна сканирования с периодом, равным интервалу сканирования, причем каждое последующее сканирование проходит на новом канале.
Рис. 12: Параметры сканирования__________________________________
В следующей статье мы рассмотрим вопросы, связанные с соединениями, а также разберемся с сервисами, характеристиками и способами работы с ними.
Это третья часть перевода книги Мохаммада Афане Intro to Bluetooth Low Energy. Сегодня мы подробнее рассмотрим процесс подключения устройств и поговорим о сервисах.
Предыдущие части:
Про архитектуру BLE
Про типы устройств,
адвертайзинг и сканирование
Благодаря сервисам происходит обмен как стандартными данными (уровень заряда батареи через Battery Service, текущее время устройства через Current Time Service и т.д.), так и кастомными, при помощи сервисов, созданных разработчиком устройства для удовлетворения специфических нужд. Например, для Atmotube Pro мы сделали два сервиса, в которые сгруппировали несколько характеристик для синхронизации истории, передачи данных о концентрации пыли и летучих органических соединений.
Эта часть получилась очень большой, извините. Я старался сократить ее, или разбить на несколько, однако это нарушило бы целостность восприятия, пожалуй, самой важной темы книги. В следующей части мы поговорим о нововведениях Bluetooth 5 и рассмотрим методы обеспечения безопасности соединения и пользовательских данных в BLE.
Для того, чтобы два BLE устройства установили соединение, необходимо выполнить следующие шаги:
Периферийное устройство должно начать процесс адвертайзинга и не останавливать его до момента установки соединения.
Центральное устройство должно сканировать радиоэфир в поисках пакетов адвертайзинга.
Если центральное устройство прослушивает канал адвертайзинга в момент, когда периферийное устройство посылает по нему данные, то оно обнаруживает периферийное устройство.
Затем центральное устройство посылает пакет CONNECT_IND (также известный как запрос на соединение).
Периферийное устройство всегда прослушивает текущий канал адвертайзинга после отправки пакета. Это позволяет ему получить запрос на соединение от центрального устройства, что запускает процесс установки соединения между двумя устройствами.
После того как будут выполнены эти шаги, соединение будет считаться созданным, но еще не установленным. Соединение будет считаться установленным, после того как устройство получит пакет данных от своего партнера по соединению. После установления соединения центральное устройство возьмет на себя роль ведущего, а периферийное, в свою очередь, ведомого. Ведущее устройство будет отвечать за управление соединением, контроль параметров соединения и контроль временных интервалов между различными событиями.
Во время событий подключения ведущий и ведомый отправляют друг другу данные до тех пор, пока не останется данных, требующих передачи. Есть несколько фактов о соединениях, которые очень важно знать:
События подключения происходят периодически до тех пор, пока соединение не будет закрыто или потеряно.
Событие подключения состоит как минимум из одного пакета посланного ведущим устройством.
Ведомое устройство всегда посылает отвечает отправкой пакета, если оно получает пакет от ведущего.
Если ведущий не получает ответный пакет от ведомого, он закрывает подключение - он возобновит посылку пакетов во время следующего события подключения.
Подключение может быть закрыто как ведущим, так и ведомым.
События подключения разнесены во времени на период интервала соединения.
Параметры, определяющие соединение:
Интервал соединения
Интервал соединения может принимать любое из значений между 7.5 мс и 4.0 секундами с шагом в 1.25 мс. Он задается центральным устройством в пакете запроса соединения. Центральное устройство может принять во внимание Предпочитаемые Параметры Соединения Периферийного Устройства (PPCP). Центральное устройство вправе принять их, модифицировать или отклонить.
Задержка ведомого (Slave Latency)
Параметр задержки ведомого позволяет периферийному устройству оставлять без ответа определенное количество событий соединения и не слушать центральное устройство во время этих событий без инвалидации соединения. Это позволяет периферийному устройству переходить в режим энергосбережения на более длительный срок. Задержка ведомого отображает количество событий соединения, которое периферийное устройство может пропустить.
Например, если установлена задержка три, то периферийное устройство может пропустить три последовательных события соединения, но затем оно должно будет выйти из режима энергосбережения, прослушать центральное устройство и ответить на следующее событие соединения.
Таймаут наблюдения (Supervision Timeout)
Таймаут наблюдения используется для определения потери соединения. Он определяется как максимальное время между двумя полученными пакетами данных, прежде чем соединение считается потерянным. Его значение может задаваться в диапазоне между 100 мс и 32 секундами с шагом 10 мс. Другое условие выглядит следующим образом:
Таймаут наблюдения > (1 + задержка ведомого) * интервал соединения * 2
Существует исключение, для которого таймаут наблюдения не применяется - в момент, когда соединение создано, но еще не установлено. В этом случае ведущее устройство примет решение о потере соединения, если не получит пакета от ведомого в течении 6 интервалов соединения.
Расширение длины данных (DLE)
Это опция, которая позволяет пакетам данных содержать большее количество полезной нагрузки (до 251 байта, в случае, когда эта настройка выключена, полезная нагрузка составляет 27 байт). Эта возможность введена в версии 4.2 спецификации Bluetooth.
Максимальная единица передачи (MTU)
Понятие максимальной единицы передачи используется в компьютерных сетях и определяет максимальный размер данных, которые можно передать по определенному протоколу. Максимальная единица передачи атрибута (в спецификации определена как ATT_MTU) это наибольший размер полезной нагрузки атрибута, который можно передать между клиентом и сервером.
Эффективный размер ATT_MTU определяется наименьшим значением максимальных ATT_MTU поддерживаемых ведущим и ведомым. Например, если ведущее устройство поддерживает ATT_MTU 100 байт и ведомое устройство сообщает, что оно поддерживает ATT_MTU 150 байт, то ведущий решает, что для этого соединения будет использоваться ATT_MTU 100 байт.
Примечание: Для достижения максимальной пропускной способности убедитесь, что включено расширение длины передаваемых данных (в случае, если вы используете Bluetooth 4.2 или новее). Это поможет снизить количество избыточно передаваемых служебных данных, таких как заголовки пакетов, за счет уменьшения числа пакетов.
Как мы упоминали ранее в начале этой главы, существуют 37 радиоканалов, используемых для передачи пакетов данных в течении соединения. В то же время, не все 37 каналов необходимы для соединения. Используемые каналы определяются картой каналов, которая включена в пакет запроса соединения, отправляемый центральным устройством периферийному для установки соединения. Для каждого события соединения пакеты данных будут переданы по другому каналу в карте каналов.
Последовательность каналов, используемая для каждого события соединения определяется картой каналов и шагом прыжка (hop increment). Шаг прыжка, также как и карта каналов, включен в пакет запроса соединения. Комбинация карты каналов и шага прыжка определяет, какой канал будет использован для каждого интервала соединения.
Рис. 14: Карта каналов и шаг прыжкаСуществует два алгоритма выбора каналов в BLE. Рассмотрение подробностей их работы лежит вне поля зрения этой книги. Для того, чтобы узнать больше об этих алгоритмах и принципах их работы, обратитесь к спецификации Bluetooth (version 5.0 | Vol 6, Part B, Section 4.5.8.2).
BLE поддерживает фильтрацию устройств для процедур, связанных с состояниями адвертайзинга, сканирования и стадией инициации (при установлении соединения).
Белый список это список адресов и типов адресов определенных устройств. Он используется для определения того, в каких устройствах заинтересовано конкретное устройство. Запись для анонимного типа адреса устройства позволяет сопоставить все широковещательные пакеты, отправленные без адреса.
Фильтрация устройств происходит на канальном уровне контроллера (нижний уровень протокола Bluetooth), что позволяет сохранить время и не производить лишнюю работу на уровне хоста (на верхних уровнях протокола). Однако именно хост отвечает на конфигурацию белого списка.
Ниже приведен список различных правил фильтрации для каждого из состояний:
Правило фильтрации для состоянии адвертайзинга (периферийное устройство)
Эта политика фильтрации определяет, как периферийное устройство будет обрабатывать запросы сканирования и запросы на подключение. Она может быть сконфигурирована следующим образом:
Обрабатываются запросы сканирования и запросы на подключение только от устройств из белого списка.
Обрабатываются запросы от всех устройств (фильтрация не используется).
Обрабатываются запросы сканирования только от устройств из белого списка, обрабатываются запросы на подключение от всех устройств.
Обрабатываются запросы на подключение только от устройств из белого списка, обрабатываются запросы сканирования от всех устройств.
Правило фильтрации для состояния сканирования (центральное устройство)
Это правило фильтрации определяет, как сканирующее устройство будет обрабатывать пакеты адвертайзинга. Может быть сконфигурировано следующим образом:
Обрабатываются пакеты адвертайзинга от всех устройств (белый список не используется).
Обрабатываются пакеты адвертайзинга только от устройств, включенных в белый список.
Правило фильтрации для состояния инициации (центральное устройство)
Это правило определяет, как устройство, инициирующее соединение, будет обрабатывать пакеты адвертайзинга. Может быть сконфигурировано следующим образом:
Обрабатывать пакеты адвертайзинга и инициировать соединение только с устройствами, включенными в белый список.
Обрабатывать пакеты адвертайзинга и инициировать соединение только с устройствами, определенными хостом.
Обратите внимание, что это не опция для обработки пакетов и подключения к подключаемому периферийному устройству, которого нет в белом списке.
Прежде чем начать рассказ о сервисах и характеристиках, мы должны рассмотреть два очень важных понятия: Общий профиль атрибутов (GATT) и Протокол атрибутов (ATT).
Для того, чтобы понять, что из себя представляет общий профиль атрибутов, необходимо сперва разобраться, что из себя представляет его нижний слой - протокол атрибутов. GATT вступает в игру только после того, как будет установлено соединение между двумя устройствами.
Примечание автораесли вы считаете GAP, GATT и ATT набором слишком похожих акронимов не проклинайте меня Я просто рассказчик! Тем не менее важно разделять их!
АТТ определяет, в каком виде сервер представит свои данные клиенту и как эти данные будут структурированы. Существует две роли, связанные с АТТ:
Сервер:
Это устройство, которое предоставляет данные, которые оно содержит, или которыми управляет и, в некоторых случаях, другие аспекты поведения сервера, которые могут контролировать другие устройства. Это устройство, которое получает входящие команды от связанного устройства и отправляет ответы, уведомления и индикации.
Например, беспроводной термометр будет вести себя как сервер, когда он будет предоставлять температуру окружающей среды, единицу измерений, свой уровень заряда и, возможно, временные интервалы, с которыми термометр производит измерения и сохраняет их результаты. Он также может уведомлять клиент (объясним позже) об изменениях температуры для того, чтобы клиент не опрашивал его непрерывно в ожидании готовых к отправке данных.
Клиент:
Это устройство, которое взаимодействует с сервером с целью считать предоставляемые сервером данные и/или для того, чтобы контролировать его поведение. Это устройство, которое посылает команды и запросы и получает входящие уведомления и индикации. В предыдущем примере, мобильный телефон, который подключался к беспроводному термометру и считывал его показания температуры, действовал в роли клиента.
Данные, предоставляемые сервером, сгруппированы в атрибуты. Атрибут это общий термин для любых типов данных, предоставляемых сервером, он определяет структуру этих данных. Например, сервисы и характеристики (будут описаны позднее) являются атрибутами. Ниже состав атрибута:
Тип атрибута (универсальный уникальный идентификатор, UUID)
Это 16-битное (в случае стандартных атрибутов Bluetooth SIG) или 128-битное число (в случае атрибутов, определенных разработчиком устройства, vendor-specific UUID).
Например, UUID для одобренного консорциумом атрибута значения температуры 0x2A1C. Одобренные консорциумом типы атрибутов имеют один общий (за исключением 16 бит) специальный 128-битный UUID:
0000XXXX-0000-1000-8000-00805F9B34FB
16-битный UUID будет подставлен вместо символов ХХХХ в базовом UUID.
Собственный UUID может быть любым 128-битным числом, не совпадающим ни с одним из одобренных Bluetooth-SIG базовых UUID. Например, разработчик может создать свой собственный UUID для показаний температуры, такой как:
F5A1287E-227D-4C9E-AD2C-11D0FD6ED640
Одно из преимуществ использования стандартных UUID состоит в уменьшении размера пакета, так как UUID может быть передан в виде 16-битного числа, вместо передачи полного 128-битного числа.
Дескриптор атрибута
Это 16-битное число, которое сервер присваивает каждому из своих атрибутов. Это число используется клиентом как ссылка на конкретный атрибут, и сервер гарантирует, что эта ссылка будет уникальной для атрибута, которому она присвоена, в течении всего времени существования соединения между устройствами. Дескриптор может иметь любое значение в диапазоне 0x0001-0xFFFF, значение 0х0000 зарезервировано.
Права атрибута
Права определяют, может ли атрибут быть прочитан или записан, может ли он посылать уведомления или индикации, и какие уровни доступа требуются для каждой из этих операций. Эти права не определяются протоколом атрибутов (АТТ) и не могут быть прочитаны через него, они определяются на верхнем уровне (GATT или уровне приложения).
Теперь, когда мы рассмотрели концепцию атрибутов, познакомимся с другими тремя важными понятиями в BLE, с которыми вам придется постоянно сталкиваться.
Сервисы
Характеристики
Профили
Эти понятия введены для того, чтобы обеспечить возможность иерархического представления данных, предоставляемых сервером. Сервисы и характеристики это атрибуты, служащие для определенной цели. Характеристики это низкоуровневые атрибуты в базе данных атрибутов. Сервисы служат для группировки характеристик по функциональному признаку. Профили немного отличаются и не обнаруживаются на сервере - мы расскажем о них позже в этой главе.
GATT определяет формат сервисов и их характеристик, а также процедуры, используемые для взаимодействия с этими атрибутами, такие как обнаружение сервисов, чтение и запись характеристик, уведомления и индикации.
GATT выполняет ту же роль, что и протокол атрибутов (АТТ). Роли устанавливаются не для устройств, они определяются во время транзакций (такие как запрос ответ, индикация подтверждение, уведомление). Таким образом, устройство может действовать как сервер, предоставляющий данные клиентам, и в то же время быть в роли клиента, получая данные, подготовленные другими серверами одновременно.
Сервисы это группа из одного или большего числа атрибутов, некоторые из которых являются характеристиками. Он предназначен для группировки связанных атрибутов, удовлетворяющих специфической функциональности сервера. Например, одобренный SIG сервис батареи содержит одну характеристику под названием уровень заряда.
Сервисы также содержат другие атрибуты, которые помогают структурировать данные сервиса, такие как объявления сервера, объявления характеристик и другие.
Вот что из себя представляют сервисы:
Рис. 16: Профили, сервисы и характеристики (источник: спецификация Bluetooth)На рисунке мы видим различные атрибуты, составляющие сервис:
Один или несколько включенных сервисов
Одна или несколько характеристик
Свойства характеристики
Ее значение
Дескрипторы характеристики
Включение сервисов позволяет сервису ссылаться на другие сервисы как на включенные в него. Существует два типа сервисов:
Первичный сервис: предоставляет основную функцию устройства или одну из них.
Вторичный сервис: предоставляет дополнительную функциональность устройства и включен в как минимум один первичный сервис на устройстве (вторичные сервисы очень редко применяются, и еще реже возникает необходимость в них они не будут более подробно рассматриваться в этой книге).
Характеристика всегда является частью сервиса и предоставляет часть тех данных, которые сервер хочет предоставить клиенту. Например, характеристика уровня заряда батареи показывает оставшийся уровень заряда батареи в устройстве, который может быть прочитан клиентом. Характеристика содержит набор атрибутов, которые облегчают работу с содержащимися в характеристике данными:
Свойства: представлены набором бит, определяющих то, каким образом значение характеристики может использоваться. Пример свойств: чтение, запись, запись без ответа, уведомление, индикация.
Дескрипторы: используются для хранения информации, связанной со значением характеристики. Примеры использования: расширенные свойства, пользовательское описание, поля, используемые для подписки на уведомления и индикации, поля, описывающие представление характеристики, такие как формат или единица измерения.
Понимание этих концепций важно, однако вы, как разработчик приложения, будете в основном взаимодействовать с API, предоставленным операционной системой или SDK чипсета, которые абстрагируют вас от многих этих понятий.
Например, вы можете иметь API для включения уведомлений для некоторых характеристик, который вы просто вызываете и вам нет необходимости знать, что результатом этого вызова будет запись значения 0x0001 в дескриптор конфигурации характеристик клиента (CCCD) на сервере.
Важно помнить, что, хотя нет никаких запретов или ограничений на количество характеристик, содержащихся в сервисе, сервисы предназначены для группировки связанных характеристик, которые определяют конкретные функции в устройстве.
Например, технически возможно создать сервис под названием сервис влажности, который будет включать в себя характеристику влажности и характеристику температуры. Но гораздо более логичным решением будет создать два отдельных сервиса для каждой несвязанной функции (отображение текущей температуры и отображение текущей влажности).
Стоит отметить, что Bluetooth SIG приняла довольно-таки большое количество сервисов и характеристик, которые удовлетворяют большей части распространенных сценариев использования. Для этих одобренных сервисов существуют спецификации, помогающие разработчикам реализовать их вместе с обеспечением соответствия и взаимодействия с каждым сервисом.
Если устройство заявляет о совместимости с сервисом, оно должно полностью соответствовать спецификации сервиса, опубликованной Bluetooth SIG. Это важно, если вы хотите разработать устройство, которое будет гарантированно подключаться к устройствам, изготовленными другим производителем. Сервисы Bluetooth, принятые SIG, делают соединение предварительно согласованным между устройствами различных производителей.
Вы можете найти список одобренных сервисов здесь, и их спецификации здесь. Одобренные характеристики находятся по этому адресу.
Профили по своему определению гораздо шире сервисов. Они занимаются определением поведения как клиента, так и сервиса, когда дело касается сервисов, характеристик и даже соединений и требований безопасности. Сервисы и их спецификации, с другой стороны, касаются реализации этих сервисов и характеристик только на стороне сервера.
Также как и в случае с сервисами, существуют одобренные SIG профили с опубликованными спецификациями к ним. В спецификации профиля вы можете найти следующее:
Определение ролей и взаимоотношений между GATT сервера и клиента.
Требуемые сервисы.
Требования сервисов.
Как используются требуемые сервисы и характеристики.
Детали требований к процессу установления соединения, включая параметры адвертайзинга и соединения.
Соображения безопасности.
Ниже в качестве примера приведена диаграмма, взятая из спецификации на профиль кровяного давления. Она показывает отношения между ролями (сервер и клиент), сервисами и характеристиками внутри профиля.
Рис. 17: Профиль кровяного давления (Источник: Спецификация профиля кровяного давления)Роли представлены в виде желтых прямоугольников, сервисы в виде оранжевых прямоугольников. Вы можете найти список профилей, одобренных SIG, здесь.
Давайте посмотрим на пример использования GATT. В этом примере мы будем использовать файл GATT.xml, используемый в Silicon Labs Bluetooth Low Energy development framework (BGLib).
Рис. 18: Файл GATT.xml из примера приложения от фирмы Silicon LabsВ этом XML-файле вы можете заметить следующее:
Определены два сервиса:
Сервис общего профиля доступа (GAP) с UUID: 0x1800 (одобрен SIG).
Сервис замены кабеля с UUID: 0bd51666-e7cb-469b-8e4d-2742f1ba77cc (Собственный сервис, определенный разработчиком приложения или производителем чипсета. Обычно так называют реализацию UART средствами Bluetooth. Например, у Nordic Semi эту роль выполняет Nordic UART Service, а у Dialog Semiconductor - Dialog Debug Service).
Сервис общего профиля доступа обязателен по спецификации и включает следующие обязательные характеристики:
Имя с UUID 0x2A00 и значением: Bluegiga CR Demo.
Окружение с UUID 0x2A01 и значением 0x4142.
Описание значений окружения приведены здесь.
Примечание: обычно создание и включение этого сервиса является задачей вендора чипсета и, обычно, разработчику предоставляется API для простой установки значений имени и окружения.
Сервис замены кабеля имеет одну характеристику под названием данные.
Характеристика данных имеет UUID: e7add780-b042-4876-aae1-112855353cc1
Включены свойства, разрешающие запись в характеристику и индикацию.
Существует шесть различных типов операций, которые могут производиться над атрибутами:
Команды: посылаются клиентом серверу и не требуют ответов.
Запросы: посылаются клиентом серверу и требуют ответов. Существуют два типа запросов:
Запросы на поиск информации
Запросы на чтение
Ответы: посылаются сервером клиенту в ответ на запрос.
Уведомления: Посылаются сервером клиенту с целью оповестить об изменении значения характеристики. Для получения уведомлений клиент должен включить их для интересующих характеристик. Помните, что уведомления не требуют подтверждения о получении от клиента.
Индикации: посылаются сервером клиенту. Во многом похожи на уведомления, но требуют подтверждения успешного приема клиентом.
Примечание: настройки уведомлений и индикаций представлены в атрибуте дескриптора конфигурации характеристик клиента (CCCD). Запись 1 в этот атрибут включит уведомления, запись 2 включит индикации. Запись 0 отключит как уведомления, так и индикации.
Подтверждения: посылаются клиентом серверу. Это пакеты, посылаемые с целью уведомить сервер об успешном приеме индикации клиентом.
Запросы последовательны по своей природе и требуют ответа от сервера перед отправкой нового запроса. Индикации имеют такое же требование: новая индикация не может быть отправлена до тех пор, пока не получено подтверждение о приеме предыдущей индикации.
Запросы и индикации являются взаимоисключающими с точки зрения требований к последовательности. То есть индикация может быть отправлена сервером до того как он ответит на запрос, который получил ранее.
Команды и уведомления отличаются, и не требуют управления потоком сообщений - они могут быть посланы в любое время. По этой причине, а также потому что сервер или клиент могут быть не в состоянии обработать эти пакеты, они считаются ненадежными. Запросы и индикации должны использоваться в случаях, когда надежность стоит во главе угла.
Чтение это запрос по своей природе, так как оно требует ответа. Существует несколько типов запросов на чтение, но наиболее важны следующие два:
Запрос на чтение: простой запрос, ссылающийся на атрибут, который будет прочитан его дескриптором.
Запрос на чтение части данных: похож на запрос на чтение, но содержит смещение, указывающее, с какого участка чтение должно начаться, возвращая часть значения. Этот тип чтения используется для чтения только части значения характеристики.
Запись может быть командой или запросом. Ниже представлены наиболее популярные типы записи:
Запрос на запись: как следует из названия, требует ответа от сервера, подтверждающего успешное получение данных.
Команда на запись: не требует ответа от сервера.
Упорядоченная запись (атомарное поведение операции): классифицируется как запрос и требует подтверждения от сервера. Они используются в случаях, когда требуется записать большое количество данных, не умещающееся в одно сообщение. Вместо того, чтобы записывать части сообщения и давать возможность кому-то прочитать некорректное неполное значение, применяются два типа запросов на запись, для того, чтобы иметь уверенность в безопасном выполнении операции:
Один или несколько запросов на подготовку к записи: каждый включает в себя смещение, с которым посылаемое значение должно быть записано в значение атрибута. Посылаемые значения также часто называют подготовленными значениями, и они хранятся в буфере на стороне сервера, еще не записанные в атрибут. Эта операция требует ответа со стороны сервера.
Один запрос на запись: используется для запроса сервера о выполнении или отмены записи принятых значений в атрибут. Требует ответа от сервера для того, чтобы клиент был уверен в том, что атрибут содержит целое и верное значение, посланное серверу.
Сервер и клиент согласуют общее значение MTU, использующееся для передачи данных в обоих направлениях. Запрос инициируется клиентом и может быть послан только один раз во время жизни соединения (согласно спецификации Bluetooth, version 5.0, Vol 3, Part F, Section 3.4.2.1).
Сервер отвечает особым пакетом, содержащим поддерживаемый им размер ATT_MTU. Итоговое значение будет выбрано равным меньшему из переданных между сервером и клиентом ATT_MTU.
Важно помнить, что различные версии BLE имеют разные поддерживаемые максимальные значения ATT_MTU.
Несмотря на то, что GATT это удивительно гибкий фреймворк, существуют несколько общих рекомендаций, которым нужно следовать при создании сервисов и характеристик для них:
Убедитесь, что внедрены обязательные сервисы и их характеристики:
Сервис общего профиля доступа (GAP)
Заданы характеристики имени и окружения.
Используйте одобренные Bluetooth SIG профили, сервисы и характеристики там, где это возможно. Это дает следующие преимущества:
Вы уменьшаете размер передаваемых пакетов, содержащих UUID сервисов и характеристик (включая пакеты адвертайзинга) ускоряете процедуру обнаружения и другие процедуры за счет использования 16-битных UUID вместо 128-битных.
Производители модулей и чипсетов обычно предоставляют примеры реализации этих сервисов в своих примерах и SDK - уменьшая нужное вам время на разработку.
Большее число сторонних устройств и приложений сможет взаимодействовать с вашим, тем самым расширяя число возможных пользовательских сценариев.
Группируйте характеристики, отвечающие за смежный функционал, в один сервис.
Избегайте создания сервисов, имеющий слишком большое количество характеристик. Хорошее разделение сервисов ускоряет процесс обнаружения характеристик и ведет к дружественному к пользователю модульному дизайну GATT.
В следующей главе мы рассмотрим практический пример, показывающий как разработать GATT для системы домашней автоматизации.
Разработка GATT для вашего BLE устройства может быть очень сложной задачей. Для того, чтобы упростить ее, давайте пройдем полный урок создания GATT для простой системы домашней автоматизации.
Система домашней автоматизации это абстрактное нечто, но она поможет вам лучше понять шаги создания GATT для реального приложения, а не для какой-то обобщенной системы. Ниже показана схема с элементами системы умного дома и связями между ними.
Рис. 19: Пример проекта системы умного дома с BLEСистема состоит из множества устройств. Некоторые из них куплены в магазине, и мы не можем как-то изменить их прошивку, остальные подлежат модификации в соответствии с нашими нуждами.
Давайте опишем основные пользовательские сценарии для системы:
Владелец дома может использовать пульт дистанционного управления для включения и выключения светильника.
Владелец дома может наблюдать за изменениями показаний температуры и влажности на сенсоре окружающей среды.
Владелец дома получает уведомления об уровне заряда батареи всех элементов системы.
Теперь давайте рассмотрим составляющие нашей системы.
Шлюз
Шлюз будет выполнять роль центрального BLE устройства во время обмена данными со всеми устройствами кроме смартфона, где он будет выполнять роль периферийного устройства. Мы можем контролировать это устройство и спроектируем GATT для него.
Команды для управления светильником будут поступать с пульта управления на шлюз, и с него на сам светильник.
Пульт управления
Пульт управления это устройство, которое выполняет только
периферийную роль, для него мы также спроектируем GATT.
Сенсор окружающей среды
Это стандартное устройство, GATT которого мы не можем изменить, так
что область нашего интереса будет ограничена чтением показаний с
него (данные о текущей температуре и влажности).
Светильник
Это еще одно стандартное устройство, которое мы не можем каким-либо образом изменить.
Смартфон
Очередное стандартное устройство, мы будем использовать его для контроля системы.
Теперь мы пройдем пошагово весь процесс создания GATT, удовлетворяющего нашим требованиям.
Несмотря на то, что GATT обычно больше сфокусирован на периферийной роли (так как обычно в роли сервера, предоставляющего данные, выступает периферийное устройство), центральное устройство также может выполнять роль сервера в особых случаях. Также, так как мы разрабатываем устройство, выполняющее обе роли (центральную и периферийную), это поможет нам понять, что должно произойти с каждой стороны, так как это повлияет на некоторые аспекты разработки системы и GATT.
6.3.1.1 Шлюз
Шлюз выполняет роль как центрального, так и периферийного устройства. Каждая из этих ролей используется для обеспечения связи с различными устройствами, входящими в состав системы. Основная задача шлюза как центрального устройства - считывать показания с множества периферийных устройств. Затем он, действуя в роли периферийного устройства, предоставляет собранные и обработанные данные другому центральному устройству (смартфону), который может передать эти данные на облачный сервер.
Рассмотрим пользовательские сценарии с точки зрения шлюза, для центральной и периферийной роли.
Периферийная роль
Пульт управления уведомляет шлюз, когда кнопки на нем нажимаются, для включения и выключения светильника.
Данные должны передаваться на облачный сервер через шлюз. Они представлены центральному устройству (например смартфону, имеющему доступ в интернет) в виде GATT-сервера в периферийной роли. Ниже перечень этих данных:
Показания температуры датчика окружающей среды
Показания влажности датчика окружающей среды
Статус светильника (включен или выключен)
Уровни заряда батареи пульта, светильника и датчика окружающей среды.
Центральная роль
Шлюз должен считывать некоторые данные, представленные устройствами, составляющими систему, и получать уведомления о других данных с этих устройств.
6.3.1.2 Пульт управления
Пульт управления выполняет одну функцию: управляет светильником. Он выполняет исключительно периферийную роль и должен предоставлять следующую информацию:
При нажатии кнопки ВКЛ: уведомить шлюз о том, что была нажата эта кнопка.
При нажатии кнопки ВКЛ: также уведомить шлюз о нажатии этой кнопки.
Уровень заряда батареи: шлюз должен иметь возможность читать данные о заряде батареи пульта и получать уведомления о его изменении.
На этом шаге мы сгруппируем характеристики в группы (сервисы), объединяющие характеристики со схожим функционалом и зададим разрешения для каждой из них.
6.3.2.1 Шлюз
У нас есть один GATT-сервер для шлюза в периферийной роли. Посмотрев еще раз на данные, которые нам нужно передавать, мы можем назначить им характеристики и сгруппировать их в следующие сервисы:
Сервис датчика окружающей среды:
Характеристика показаний температуры датчика окружающей среды: Температура
Права: Чтение, уведомление.
Характеристика показаний влажности датчика окружающей среды: Влажность
Права: Чтение, уведомление.
Характеристика остаточного заряда аккумулятора: Уровень заряда
Права: Чтение, уведомление.
Сервис светильника:
Текущее состояние светильника Статус
Права: Чтение, уведомление.
Характеристика остаточного заряда аккумулятора: Уровень заряда
Права: Чтение, уведомление.
Сервис пульта дистанционного управления:
Характеристика остаточного заряда аккумулятора: Уровень заряда
Права: Чтение, уведомление.
Помимо этих сервисов необходимо реализовать обязательный (согласно спецификации Bluetooth) сервис:
GAP сервис:
Характеристика имя: имя устройства.
Права: чтение.
Характеристика местоположения: описание места, где размещено устройство.
Права: чтение.
6.3.2.2 Пульт управления
У нас есть один GATT-сервер для пульта управления. Мы можем назначить ему следующие сервисы и характеристики:
GAP сервис (обязательный):
Характеристика имя: имя устройства.
Права: чтение.
Характеристика местоположения: описание места, где размещено устройство.
Права: чтение.
Сервис аккумулятора:
Характеристика остаточного заряда аккумулятора: Уровень заряда
Права: Чтение, уведомление.
Сервис кнопок:
Характеристика кнопки ВКЛ
Права: уведомление.
Характеристика кнопки ВКЛ
Права: уведомление.
6.3.3.1. Шлюз
Сервисы датчика окружающей среды, светильника и пульта являются нашими собственными, так как не существует ни одного стандартного сервиса, который мы бы могли использовать для них. Однако у нас есть три устройства с аккумуляторами, заряд которых нам необходимо отображать и мы можем использовать стандартную характеристику уровня заряда. Мы будем использовать ее для каждого устройства в его сервисе в GATT шлюза. Также мы будем использовать обязательный GAP сервис.
6.3.3.2. Пульт управления
Для пульта управления мы можем использовать стандартный сервис уровня заряда батареи и обязательный GAP сервис.
Для каждого нестандартного сервиса и характеристики мы можем использовать онлайн-инструмент для генерации UUID, например этот.
Распространенной практикой является выбор базового UUID для сервиса и увеличение значения третьего и четвертого старшего байта UUID для каждой последующей характеристики.
Для примера, возьмем следующий UUID для какого-либо сервиса:
00000001-1000-2000-3000-111122223333
И затем заменим выделенные байты на
0000000[N]-1000-2000-3000-111122223333, где N>1 - порядковый номер характеристики.
Единственное условие, которое накладывает ограничение на выбор UUID для наших сервисов и характеристик это условие несовпадения нашего UUID с базовым UUID Bluetooth SIG: XXXXXXXX-0000-1000-8000-00805F9B34FB.
Следование вышепредставленной методике выбора UUID немного упрощает понимание связей между сервисами и их характеристиками.
На таблицах ниже представлены наши сервисы, характеристики и их UUID для шлюза и пульта управления.
Таблица 3: GATT шлюзаТаблица 4: GATT пульта управленияКаждая платформа, встраиваемая или мобильная, имеет собственный интерфейс прикладного программирования (API, application programming interface) для реализации сервисов и характеристик. Задача читателя самостоятельно реализовать их для выбранного им устройства или приложения.
Новый хаб от Xiaomi с поддержкой технологий Zigbee 3, Bluetooth Mesh, HomeKit и его подключение к достаточно популярной системе умного дома Home Assistant, интересует?
Устройства умного дома можно встретить на разных беспроводных протоколах.
Важно понимать, что поверх каждого протокола производители устройств накладывают что-то своё. А это значит, что нельзя выбрать какой-то один протокол и все устройства всех фирм будут автоматически поддерживаться.
Чаще всего новички выбирают устройства на технологии Wi-Fi. Ведь Wi-Fi роутер сегодня есть у всех. Умным устройством можно пользоваться сразу после покупки. Но тут есть нюанс: в количестве устройств слабость Wi-Fi. Роутеры от провайдеров в большинстве своём тот ещё хлам, способный справиться с 1-2 десятками устройств. И пять новых умных лампочек могут быть проблемой для всей сети.
Здесь выходом будет хороший двухдиапазонный роутер. Весь умный дом можно повесить на диапазон 2.4 ГГц, а мультимедиа-устройства (смартфоны, ноутбуки, телевизоры, колонки) на 5 ГГц.
Устройства на Bluetooth новички выбирают так же охотно, ведь сегодня смартфоны есть почти у всех. Проблема в том, что дальность Bluetooth весьма ограничена. Уже из соседней комнаты вы не сможете посмотреть температуру на датчике или включить чайник или лампочку.
В таких случаях производители рекомендуют покупать BLE Gateway. Это устройство, которое будет посредником между Bluetooth и серверами производителя. Здесь уже далеко не все пользователи соглашаются на дополнительные траты и лишнюю железку в доме.
Некоторые производители встраивают возможности BLE Gateway в камеры и лампы, закрывая проблему лишних затрат и лишнего устройства в доме.
От этой технологии новички отказываются чаще всего из-за необходимости приобретать дополнительное устройство-посредник, ведь ничего работающего с Zigbee в их доме скорее всего нет. Некоторые производители встраивают поддержку этой технологии в умные колонки и, на мой взгляд, это очень интересный ход.
Дополнительную проблему составляет, что каждый такой Gateway поддерживает только дочерние устройства своего производителя. Купив устройства Philips Hue, IKEA, Sonoff, Xiaomi и Tuya, вы, скорее всего, должны будете докупить пять Gateway соответственно.
Эта технология заслуживает внимания по следующим причинам:
Фирма Xiaomi сделала многое для продвижения технологии Zigbee в альтернативных системах умного дома. Их старенький Xiaomi Gateway 2 (DGNWG02LM, lumi.gateway.v3) имел на борту "режим разработчика", который открывал локальный протокол доступа к управлению Zigbee устройствами этого шлюза. Интеграции этого протокола есть в множестве open source систем.
В евро-версии этого шлюза Xiaomi Gateway EU (DGNWG05LM, lumi.gateway.mieu01), а также в обновлённой версии Xiaomi Gateway 3 (ZNDMWG03LM, lumi.gateway.mgl03) этого протокола нет.
Обновлённая версия шлюза получила новый чип на Zigbee 3 (EFR32MG1B), а также поддержку технологии Bluetooth Mesh и HomeKit. В HomeKit поддерживаются не все устройства, будьте внимательны.
В отличие от всех остальных шлюзов, обновлённая версия имеет уникальную особенность: на ней программно можно открыть Telnet-доступ. Доступ открывается только при наличии Mi Home токена, так что всё вполне секьюрно.
В этом шлюзе стоит чип серии EFR32 от фирмы Silicon Labs. Те в свою очередь поставляют вместе с чипом набор SDK. В составе SDK есть MQTT-транспорт, обеспечивающий доступ к Zigbee проколу из любого ПО, установленного как на шлюзе, так и за его пределами.
По умолчанию MQTT-брокер не доступен извне, но у нас ведь теперь есть Telnet!
В брокере есть два корневых топика: это "сырые" данные Zigbee и обработанные данные от Xiaomi. Я решил взять за основу обработанные данные. Там атрибуты устройств хоть и описаны псевдокодами, но в них всё же проще разобраться человеку, ничего не понимающему в Zigbee.
В итоге получился такой вот компонент для Home Assistant XiaomiGateway3.
Он автоматически включает Telnet и публичный MQTT, используя токен Mi Home.
Сейчас токен нужно получать нехитрым образом (инструкция в readme). Но в будущем я планирую добавить получение токена с серверов Xiaomi, используя аккаунт Mi Home. Ведь недавно в сети появилась рабочая реализация авторизации в их облаке.
Сейчас компонент получает список устройств и последние значения их атрибутов с Хаба. Но в дальнейшем я планирую добавить получение списка устройств из облака. Там есть пользовательские названия всех устройств.
С этим пришлось повозиться. Работа с Bluetooth-устройствами не отражается в MQTT. Зато все данные отражаются в консоли. Поэтому компонент подключается к хабу через Telnet отдельным потоком, перезапускает утилиту работы с Bluetooth и читает её вывод в реальном времени. Это самый стабильный способ, что я нашёл. В syslog данные от этой утилиты попадают с перебоями. Моих знаний Linux не хватает, чтоб понять, почему так происходит.
Первым делом компонент научился поддерживать стандартный набор Bluetooth-устройств Xiaomi: датчики температуры, анализаторы почвы и освещенности, фумигатор.
А пару дней назад в нём появилась поддержка умных Bluetooth-замков. На сегодняшний день это единственный из известных мне способов подключить BLE-умный замок Xiaomi в альтернативную систему умного дома.
Чайника у меня нет, но присылайте логи добавлю и его. Правда чайники Xiaomi не поддерживают функцию удалённого включения, это большой минус.
BLE-устройства и их атрибуты отображаются по мере поступления данных. Когда появится поддержка облака полный список BLE устройств с их моделями, именами и последними посланными данными можно будет получить оттуда. Такая информация на хабе не хранится.
Поддержка Bluetooth Mesh ламп пока в разработке. Работа с ними сильно отличается от BLE-устройств.
Грандиозные.
Нужно отладить работу хаба со всем списком официально поддерживаемых Zigbee-устройств. Добавить возможность настройки "тонких" параметров:
Нужно добавить поддержку облака для получения токена хаба и полного списка Zigbee и Bluetooth-устройств.
Нужно добавить поддержку Bluetooth Mesh ламп.
И самое главное добавить поддержку устройств других производителей. Да, это возможно. Мне удалось подключить все сторонние устройства, что у меня были, и управлять ими. Такие устройства не отображаются в Mi Home и HomeKit. Но управлять ими можно с помощью "сырых" Zigbee-команд.
Для понимания полного масштаба проблемы такой поддержки загляните в исходники замечательного проекта zigbee2mqtt: devices, fromZigbee, toZigbee.
Почти каждое устройство требует свой собственный обработчик. В случае с Xiaomi Gateway 3 и официально поддерживаемыми устройствами роль такого обработчика выполняет софт хаба.
Другие мои разработки можно найти на GitHub. Среди русскоговорящей аудитории наиболее популярный проект YandexStation. Глобально очень хорошо себя зарекомендовал SonoffLAN. Но, думаю, XiaomiGateway3 его легко обгонит. За развитием этого и других моих проектов можно следить на моём канале Telegram.
Первая версия компонента XiaomiGateway3 для Home Assistant вышла 4 месяца назад, и с тех пор много всего изменилось. В прошлой статье я писал про создание компонента. А в этой статье расскажу, почему это решение так заинтересовало сотни пользователей.
Эта модель шлюза действительно получилась очень удачной. Иначе как можно объяснить, что компания Xiaomi уже два раза успела обновить прошивку шлюза на своих заводах. Это единственная модель шлюза, у которой прошивка теперь обновляется настолько оперативно на производстве.
В сентябре вышла первая версия компонента. В октябре, после долгих задержек, покупателям начали приходить шлюзы уже с новой прошивкой. В ней Telnet был закрыт паролем. В ноябре прошивка на заводах снова обновилась, и Telnet в ней совсем пропал. Определить проблемный шлюз можно по дате производства на коробке 2020.10 и выше.
В миг шлюз превратился из простого Plug and Play устройства в устройство, которое нужно обязательно вскрыть и прошить для интеграции в альтернативные системы умного дома.
Но шлюз хорош не только своим современным Zigbee-чипом и наличием чипа Bluetooth. Выбранный компанией-производителем SoC от Realtek позволяет в любой момент записать на шлюз любую прошивку, подключив всего три провода UART.
Если вы не любите паять, можно воспользоваться специальной прищепкой с контактами. Есть даже удачные примеры прошивки с бельевой прищепкой. Главное помнить, что обрыв контактов во время прошивки или UART на 5 В вместо 3.3 В вполне могут спалить микросхемы шлюза.
В развитии этого хаба участвует довольно много крутых людей. @serrj-sv собрал скрипт под Windows, который может прошить шлюз в полуавтоматическом режиме. А @zvldz собрал альтернативную версию прошивки, которая на 99% соответствует оригинальной. В ней поправлено недоразумение с закрытым Telnet и ещё пара мелочей. По особенностям прошивки и другим вопросам можно писать в этот чат Telegram.
Все полезные ссылки можно найти в вики проекта.
Многие гики не любят облака и стараются с ними не связываться. Идеология облаков нарушает и главный девиз Home Assistant: конфиденциальность прежде всего (privacy first).
С другой стороны, если при данном подходе сохраняется полноценное локальное управление (local control, вторая часть девиза Home Assistant), то ничего страшного в облаках нет.
Экосистемы производителей добавляют устройствам больше свободы и возможностей управления, включая внешний доступ из коробки и прямое подключение к популярным голосовым ассистентам вроде Яндекс.Алисы и Google Assistant.
Также с этим подходом новые пользователи могут плавно осваивать Open Source системы умного дома: если что-то не получается, можно легко продолжить пользоваться устройством в экосистеме производителя.
Да, где-то на китайском сервере будет хранится информация, включен ли у вас в туалете свет. Так ли много на вас компромата у лампочки? Особенно если сравнивать с данными в вашем браузере или смартфоне. А полный доступ к прошивке шлюза позволяет пытливому уму легко отследить, какая именно информация отправляется на родину.
Компонент поддерживает:
все популярные устройства: выключатели, кнопки, розетки, реле, лампочки, датчики движения, температуры, открытия, протечки, вибрации, газа, дыма, шторы и замки;
редкие устройства вроде термостата Aqara Thermostat S2 (KTWKQ03ES) такой термостат пока не поддерживается даже в zigbee2mqtt;
самые свежие устройства вроде новых: реле Aqara Relay T1 и высокоточный датчик присутствия Aqara Hight Precision Motion Sensor (RTCGQ13LM).
Альтернатива: разнообразные DIY и коммерческие Zigbee-стики и DIY-хабы. Вот довольно большое русскоязычное сообщество в Telegram, где могут ответить на ваши вопросы по поводу Zigbee.
Компонент поддерживает:
все популярные датчики: разнообразные датчики температуры с экраном, датчик ухода за растениями, фумигатор, ночник и умный кубик Рубика;
редкие устройства вроде сейфа Xiaomi Safe Box (BGX-5/X1-3001) да, есть и такое устройство;
самые свежие датчики вроде новых датчиков двери, протечки и движения на технологии BLE;
различные дверные замки экосистемы Xiaomi.
Компонент не поддерживает не BLE устройства вроде чайника и самоката Xiaomi.
Альтернатива: шлюз на основе ESP32 или встроенный Bluetooth на сервере умного дома. Обычно этими способами поддерживаются только популярные датчики температуры альтернативной поддержки дверных замков я не встречал.
Компонент поддерживает новые Mesh-лампы экосистемы Xiaomi под брендами MiJia и Yeelight. И один китайский пользователь уже второй месяц пытается добавить поддержку Mesh-выключателей. А я никак не найду время рассмотреть его pull request.
Новые лампы очень выгодно отличаются ценой и функциями от аналогов на технологиях Wi-Fi и Zigbee, поэтому к ним стоит присмотреться. Об одной из таких ламп я рассказываю в своей статье про адаптивное освещение.
Альтернатива: на ум приходит только новый хаб Yeelight и подключение его к Home Assistant через протокол HomeKit. Open Source проекты с поддержкой Mesh-ламп я не встречал.
Пользователи не любят хабы крупных компаний из-за того, что они поддерживают только устройства своего производителя. Так вот Xiaomi Gateway 3 лишен этого недостатка.
Я добавил в него режим, в котором Home Assistant напрямую подключается к Zigbee-чипу хаба через интеграцию Zigbee Home Automation.
Конечно, у подхода есть минусы:
Zigbee-чип перестаёт работать с Mi Home и начинает работать только с Home Assistant;
по количеству поддерживаемых устройств ZHA сильно уступает проекту zigbee2mqtt.
Но есть и плюсы:
в Китае не узнают, включен ли у вас в туалете свет;
в любой момент можно вернуть хаб в обычный режим работы c Mi Home без последствий для родной прошивки хаба;
BLE-датчики и Mesh-лампы продолжают работать в этом режиме;
команда Home Assistant активно развивает проект ZHA в рамках своей основной работы.
Альтернатива: шлюз Sonoff ZBBridge, прошитый Tasmota.
В некоторых случаях для поддержки сторонних Zigbee-устройств режим ZHA включать не обязательно.
Изучая проблему, почему лампы IKEA E27 из российских магазинов не подключаются к хабам Xiaomi, я пришел к выводу, что в хабах зашита поддержка лишь семи моделей ламп, хотя проект zigbee2mqtt поддерживает более 30 моделей ИКЕА.
Дело осталось за малым вместо настоящей модели лампы подсунуть хабу ту, которую он поддерживает. Реализовать код, организующий подмену модели устройства в момент добавления устройства в хаб, оказалось несложно. Как оказалось, этот способ отлично подошёл для диммеров и реле других фирм.
И самое интересное в данном способе то, что устройства работают и управляются в Mi Home без участия Home Assistant. И могут участвовать в автоматизациях.
Конечно, не стоит рассчитывать, что таким образом заработают любые устройства. Например, у меня не получилось заставить работать лампочки и датчик Philips Hue, а также кнопку Sonoff. А умные розетки, добавленные таким образом, не показывают энергопотребление.
Компонент поддерживает опциональную интеграцию с облаком. При этом компонентом можно пользоваться и без интеграции с облаком, просто добавив шлюз по IP-адресу и токену Mi Home.
Но если вы авторизуетесь в облаке Xiaomi, все данные о шлюзе загрузятся автоматически. Кроме адреса и токена шлюза из облака загрузятся все имена ваших Zigbee, BLE и Mesh-устройств. Вам не придётся снова заполнять их в Home Assistant, выясняя, что за устройство скрывается за именем 0x00158D0007396A5D.
Помимо получения данных о шлюзе и его устройствах - облачная интеграция позволяет получить Mi Home токены от любых Wi-Fi устройств в вашем аккаунте. Если ваш аккаунт использует одновременно разные сервера (например, европейский и китайский), это тоже поддерживается.
Функция получения токенов работает даже если у вас нет шлюза Xiaomi Gateway 3.
Один из самых популярных Zigbee-датчиков в экосистеме Xiaomi это датчик движения. Ранее я пользовался вторым шлюзом Xiaomi и писал автоматизации для этого датчика в Node-RED.
Довольно давно я придумал автоматизацию прогрессивного таймера для датчика движения с опцией быстрого возвращения. Первое движение человека перед датчиком запускало небольшой таймер, а следующие движения - запускали уже увеличенный таймер. Таким образом при постоянном нахождении в комнате свет мог гореть дольше, а при кратковременном пробегании мимо датчика - свет выключался быстро. Хорошо работает в помещении вроде кухни.
Также при обнаружении движения сразу после выключения света - свет включался с увеличенным таймером. Полезно, когда свет выключается не вовремя и злой человек машет рукой датчику.
Логика работы показана на картинке
Радиосвязь всегда менее надёжнее провода. Сигнал от датчика может по разным причинам не дойти в центр. Zigbee и Bluetooth работают на той же частоте, что Wi-Fi и микроволновки.
Для обнаружения возможных проблем в компоненте есть опциональная статистика по каждому Zigbee и BLE устройству.
В ней можно узнать время последнего сообщения от датчика, качество сигнала, количество пропущенных сообщений и через какой роутер устройство работает.
Хотя многие привыкли судить о стабильности в сети именно по качеству сигнала, на мой взгляд именно количество пропущенных сообщений является наиболее показательной величиной.
Именно этот показатель помог мне выпустить две заплатки и уменьшить количество пропусков срабатывания у популярного датчика движения Aqara Motion Sensor. Одна заплатка попала в компонент Home Assistant, а вторая в проект zigbee2mqtt.
За четыре месяца компонент оброс огромным количеством функционала и успел получить более 400 звёзд на GitHub. Но мысли по дальнейшему развитию и не думают кончаться.
Ещё остаётся добавить корректную работу с Bluetooth-устройствами при использовании нескольких хабов на одном сервере Home Assistant. Дело в том, что BLE датчики и Mesh-лампы не привязаны к какому-либо одному хабу. Все хабы могут получать данные с окружающих сенсоров и управлять окружающими лампами. Кстати огромный плюс в сравнении с технологией Zigbee.
Так же в планах добавить настройку параметров дочерних устройств - режим interlock в реле Aqara (переключатель пропал в последних версиях приложения Mi Home), чувствительность и задержки между срабатываниями нового датчика присутствия Aqara и многие другие.
Отдельным направление развития остаётся поддержка конвертеров проекта zigbee2mqtt. Если его удастся довести до релиза, пользователь сможет выбрать, как именно подключать имеющиеся у него устройства:
оригинальное ПО Xiaomi - готово
интеграция Zigbee Home Automation - готово
конвертеры zigbee2mqtt - есть рабочий прототип
И что немаловажно - переключаться между режимами можно в любой момент без последствий для оригинальной прошивки хаба.
На моём GitHub можно найти ссылки на другие компоненты и статьи. А за их развитием можно следить на моём канале в Telegram.
В последний год я разрабатывал Bluetooth Low Energy (BLE) приложения под iOS и это оказалось довольно простым. Далее было портирование их на Android насколько это могло быть сложным?
Могу точно сказать это было сложней, чем представлял, мне пришлось приложить немало усилий для стабильной работы под Android. Я изучил много статей в свободном доступе, некоторые оказались ошибочными, многие были очень полезными и помогли в деле. В этой серии статей я хочу описать свои выводы, чтобы вы не тратили уйму времени на поиски как я.
Google документация по BLE очень общая, в некоторых случаях нет важной информации или она устарела, примеры приложений не показывают, как правильно использовать BLE. Я обнаружил лишь несколько источников, как правильно сделать BLE.Презентация Stuart Kentдает замечательный материал для старта. Для некоторых продвинутых тем есть хорошая статьяNordic.
Android BLE API это низкоуровневые операции, в реальных приложениях нужно использовать несколько слоев абстракции (как например сделано из коробки в iOS-CoreBluetooth). Обычно нужно самостоятельно сделать: очередь команд, bonding, обслуживание соединений, обработка ошибок и багов, мультипоточный доступ . Самые известные библиотеки:SweetBlue,RxAndroidBleиNordic. На мой взгляд самая легкая для изучения - Nordic,см. детали тут.
Производители делают изменения в Android BLE стекеили полностью заменяют на свою реализацию. И надо учитывать разницу поведения для разных устройств в приложении. То что прекрасно работает на одном телефоне, может не работать на других! В целом не все так плохо, например реализация Samsung сделана лучше собственной реализации от Google!
В Android есть несколько известных (и неизвестных) баговкоторые должны быть обработаны, особенно в версиях 4,5 и 6. Более поздние версии работают намного лучше, но тоже имеют определенные проблемы, такие как случайные сбои соединения с ошибкой 133. Подробнее об этом ниже.
Не претендую на то, что я решил все проблемы, но мне удалось выйти на приемлемый уровень. Начнем со сканирования.
Перед подключением к устройству вам нужно его просканировать.
Это делается при помощи классаBluetoothLeScanner
:
BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();BluetoothLeScanner scanner = adapter.getBluetoothLeScanner();if (scanner != null) { scanner.startScan(filters, scanSettings, scanCallback); Log.d(TAG, "scan started");} else { Log.e(TAG, "could not get scanner object");}
Сканер пытается найти устройства в соответствии снастройками
filters
иscanSettings
, при обнаружении
устройства вызываетсяscanCallback
:
private final ScanCallback scanCallback = new ScanCallback() { @Override public void onScanResult(int callbackType, ScanResult result) { BluetoothDevice device = result.getDevice(); // ...do whatever you want with this found device } @Override public void onBatchScanResults(List<ScanResult> results) { // Ignore for now } @Override public void onScanFailed(int errorCode) { // Ignore for now }};
В результате сканирования мы получаем
экземплярScanResult
, в котором есть
объектBluetoothDevice
, его используют для подключения
к устройству. Но прежде чем начать подключаться, поговорим о
сканировании подробнее,ScanResult
содержит несколько
полезных сведений об устройстве:
Advertisement data- массив байтов с информацией
об устройстве, для большинства устройств это имя и UUID сервисов,
можно задать вfilters
имя устройства и UUID сервисов
для поиска конкретных устройств.
RSSI уровень- уровень сигнала (насколько близко устройство).
дополнительные данные, см. документацию
поScanResult
здесь.
Помним про жизненный
циклActivity
,onScanResult
может вызываться
многократно для одних и тех же устройств, при
пересозданииActivity
сканирование может запускаться
повторно, вызываю лавину вызововonScanResult
.
Вообще можно передать null вместо фильтров и получить все ближайшие устройства, иногда это полезно, но чаще требуются устройства с определенным именем или набором UUID сервисов.
Используется если вам необходимо найти устройства определенной категории, например мониторы артериального давления со стандартным сервисным UUID: 1810. При сканировании устройство может содержать вAdvertisement dataUUID сервис, который характеризует это устройство. На самом деле эти данные ненадежные, фактически сервисы могут не поддерживаться, или подделыватьсяAdvertisement dataданные, в общем тут есть творческий момент.
Прим. переводчика: одно из моих устройств со специфичной прошивкой, вообще не содержало список UUID сервисов вAdvertisement data, хотя все остальные прошивки этого устройства работали ожидаемо.
Пример сканирования службы с артериальным давлением:
UUID BLP_SERVICE_UUID = UUID.fromString("00001810-0000-1000-8000-00805f9b34fb");UUID[] serviceUUIDs = new UUID[]{BLP_SERVICE_UUID};List<ScanFilter> filters = null;if(serviceUUIDs != null) { filters = new ArrayList<>(); for (UUID serviceUUID : serviceUUIDs) { ScanFilter filter = new ScanFilter.Builder() .setServiceUuid(new ParcelUuid(serviceUUID)) .build(); filters.add(filter); }}scanner.startScan(filters, scanSettings, scanCallback);
Обратите внимание на короткий UUID (например1810
),
он называется16-bit UUID
и является частью
длинного128-bit UUID
(в данном
случае00001810-000000-1000-8000-000-00805f9b34fb
).
Короткий UUID это BASE_PART длинного UUID, см. спецификациюздесь.
Поиск устройств использует точное совпадение имени устройства, обычно это применяется в двух случаях:
поиск конкретного устройства
поиск конкретной модели устройства, например, мой нагрудный
напульсник Polar H7 определяется как Polar H7 391BBB014, первая
часть - Polar H7 общая для всех таких устройств этой модели, а
последняя часть 391BBB014 - уникальный серийный номер. Это очень
распространенная практика. Если вы хотите найти все устройства
Polar H7, то фильтр по имени вам не поможет, придется искать
подстроку у всех отсканированных устройств
вScanResult
. Пример с поискомточнопо
имени:
String[] names = new String[]{"Polar H7 391BB014"};List<ScanFilter> filters = null;if(names != null) { filters = new ArrayList<>(); for (String name : names) { ScanFilter filter = new ScanFilter.Builder() .setDeviceName(name) .build(); filters.add(filter); }}scanner.startScan(filters, scanSettings, scanCallback);
Обычно применяется дляпереподключенияк уже известным устройствам. Обычно мы не знаем MAC-адрес девайса, если не сканировали его раньше, иногда адрес печатается на коробке или на корпусе самого устройства, особенно это касается медицинских приборов. Существует другой способ повторного подключения, но в некоторых случаях придется еще раз сканировать устройство, например при очистке кеша Bluetooth.
String[] peripheralAddresses = new String[]{"01:0A:5C:7D:D0:1A"};// Build filters listList<ScanFilter> filters = null;if (peripheralAddresses != null) { filters = new ArrayList<>(); for (String address : peripheralAddresses) { ScanFilter filter = new ScanFilter.Builder() .setDeviceAddress(address) .build(); filters.add(filter); }}scanner.startScan(filters, scanSettings, scanByServiceUUIDCallback);
Вероятно вы уже поняли, что можно комбинировать в фильтре UUID, имя и MAC-адрес устройства. Выглядит неплохо, но на практике я не применял такое. Хотя может быть вам это пригодится.
ScanSettings
объясняют Android как сканировать
устройства. Там есть ряд настроек, которые можно задать, ниже
полный пример:
ScanSettings scanSettings = new ScanSettings.Builder() .setScanMode(ScanSettings.SCAN_MODE_LOW_POWER) .setCallbackType(ScanSettings.CALLBACK_TYPE_ALL_MATCHES) .setMatchMode(ScanSettings.MATCH_MODE_AGGRESSIVE) .setNumOfMatches(ScanSettings.MATCH_NUM_ONE_ADVERTISEMENT) .setReportDelay(0L) .build();
Безусловно, это самый важный параметр. Определяет метод и время сканирования в Bluetooth стеке. Такая операция требует много энергии и необходим контроль над этим процессом, чтобы не разрядить батарею телефона быстро. Есть 4 режима работы, в соответствии с руководствомNordics и официальной документацией:
SCAN_MODE_LOW_POWER
. В этом режиме Android
сканирует 0.5с, потом делает паузу на 4.5с. Поиск может занять
относительно длительное время, зависит от того насколько часто
устройство посылает пакет advertisement данных.
SCAN_MODE_BALANCED
. Время сканирования: 2с, время
паузы: 3с, компромиссный режим работы.
SCAN_MODE_LOW_LATENCY
. В этом случае, Android
сканирует непрерывно, что очевидно требует больше энергозатрат, при
этом получаются лучшие результаты сканирования. Режим подходит если
вы хотите найти свое устройство как можно быстрее. Не стоит
использовать для длительного сканирования.
SCAN_MODE_OPPORTUNISTIC
. Результаты будут получены,
если сканирование выполняется другими приложениями! Строго говоря,
это вообще не гарантирует, что обнаружится ваше устройство. Стек
Android использует этот режим в случае долгого сканирования, для
понижения качества результатов (см. ниже Непрерывное
сканирование).
Эта настройка контролирует как будет вызываться callback
соScanResult
в соответствии с заданными фильтрами, есть
3 варианта:
CALLBACK_TYPE_ALL_MATCHES
. Callback будет вызывать
каждый раз, при получении advertisement пакета от устройств. На
практике - каждые 200-500мс будет срабатывать сallback, в
зависимости от частоты отправки advertisement пакетов
устройствами.
CALLBACK_TYPE_FIRST_MATCH
. Callback сработает один
раз для устройства, даже если оно далее будет снова посылать
advertisement пакеты.
CALLBACK_TYPE_MATCH_LOST
. Callback будет вызван,
если получен первый advertisement пакет от устройства и дальнейшие
advertisement пакеты не обнаружены. Немного странное поведение.
В практике обычно используются
настройкаCALLBACK_TYPE_ALL_MATCHES
илиCALLBACK_TYPE_FIRST_MATCH
.
Правильный тип зависит от конкретного случая. Если не знаете -
используйтеCALLBACK_TYPE_ALL_MATCHES
, это дает больше
контроля при получении callback, если вы останавливаете
сканирование после получения нужных результатов - фактически
этоCALLBACK_TYPE_FIRST_MATCH
.
Настройка того, как Android определяет совпадения.
MATCH_MODE_AGGRESSIVE
. Агрессивность
обуславливается поиском минимального количества advertisement
пакетов и устройств даже со слабым сигналом.
MATCH_MODE_STICKY
. В противоположность, этот режим
требует большего количества advertisement пакетов и хорошего уровня
сигнала от устройств.
Я не тестировал эти настройки подробно, но я в основном
используюMATCH_MODE_AGGRESSIVE
, это помогает быстрее
найти устройства.
Параметр определяет сколько advertisement данных необходимо для совпадения.
MATCH_NUM_ONE_ADVERTISEMENT
. Одного пакета
достаточно.
MATCH_NUM_FEW_ADVERTISEMENT
. Несколько пакетов
нужно для соответствия.
MATCH_NUM_MAX_ADVERTISEMENT
. Максимальное
количество advertisement данных, которые устройство может
обработать за один временной кадр.
Нет большой необходимости в таком низкоуровневом контроле. Все что вам надо - быстро найти свое устройство, обычно используются первые 2 варианта.
Задержка для вызова сallback в миллисекундах. Если она больше
нуля, Android будет собирать результаты в течение этого времени и
вышлет их сразу все в обработчикеonBatchScanResults
.
Важно понимать чтоonScanResult
не будет вызываться.
Обычно применяется, когда есть несколько устройств одного типа и мы
хотим дать пользователю выбрать одно из них. Единственная проблема
здесь - предоставить информацию пользователю для выбора, это должен
быть не только MAC-адрес (например имя устройства).
Важно: естьизвестный багдля Samsung S6 / Samsung S6 Edge, когда все результаты сканирования имеют один и тот же RSSI (уровень сигнала) при задержке больше нуля.
В результате процесса сканирования вы получаете список BLE устройств и при этом данные устройств кешируются в Bluetooth стеке. Там хранится основная информация: имя, MAC-адрес, тип адреса (публичный, случайный), тип устройства (Classic, Dual, BLE) и т.д. Android нужны эти данные, чтобы подключится к устройству быстрее. Он кеширует все устройства, которые видит при сканировании. Для каждого из них записывается небольшой файл с данными. Когда вы пытаетесь подключиться к устройству, стек Android ищет соответствующий файл, чтобы прочитать данные для подключения. Важный момент - одного MAC-адреса недостаточно для успешного подключения к устройству!
Bluetooth кеш, как и любой другой, не существует вечно, есть 3 ситуации, когда он очищается:
Выключение и включение системного переключателя Bluetooth
Перезагрузка телефона
Очистка данных приложения (в ручном режиме в настройках телефона)
Это достаточно неудобный момент для разработчиков, потому что телефон часто перезагружается, пользователь может включать-выключать самолетный режим. Есть еще различия между производителями телефонов, например на некоторых телефонах Samsung, кеш не очищался при выключении Bluetooth.
Это значит, что нельзя полагаться на данные об устройстве из BT кеша. Есть небольшой трюк, он поможет узнать закешировано ли устройство или нет:
// Get device object for a mac addressBluetoothDevice device = bluetoothAdapter.getRemoteDevice(peripheralAddress)// Check if the peripheral is cached or notint deviceType = device.getType();if(deviceType == BluetoothDevice.DEVICE_TYPE_UNKNOWN) { // The peripheral is not cached} else { // The peripheral is cached}
Это важный момент, если нужно подключиться к устройству позже, не сканируя его. Подробнее об этом позже.
Вообще хорошая практика избегать непрерывного сканирования потому что, это очень энергоемкая операция, а пользователи любят, когда батарея их смартфона работает долго. Если вам действительно нужно постоянное сканирование, например при поиске BLE-маячков, выберите настройки сканирования с низким потреблением и ограничивайте время сканирования, например когда приложение находится только на переднем плане (foreground), либо сканируйте с перерывами.
Плохая новость в том, что Google в последнее время ограничивает (неофициально) непрерывное сканирование:
c Android 8.1сканирование без фильтров
блокируется при выключенном экране. Если у вас нет
никакихScanFilters
, Android приостановит сканирование,
когда экран выключен и продолжит, когда экран снова будет
включен.Комментарии от Google.Это
очевидно очередной способ энергосбережения от Google.
c Android 7 вы можете сканировать только в течение 30 минут,
после чего Android меняет параметры
наSCAN_MODE_OPPORTUNISTIC
.Очевидное решение,
перезапускать сканирование с периодом менее, чем30 мин. Посмотритеcommitв исходном коде.
с Android 7 запуск и останов сканирования более 5 раз за 30 секундвременно отключает сканирование.
Google значительно усложнил сканирование на переднем плане. Для фонового режима вы столкнетесь с еще большими трудностями! Новые версии Android имеют лимиты на работу служб в фоновом режиме, обычно после 10 минут работы, фоновый сервис прекращает свою работу принудительно. Посмотрите возможные решения этой проблемы:
Обсуждение наStackOverflow
СтатьяDavid Young
Есть еще несколько важных моментов, прежде чем мы закончим статью. Для начала сканирования нужны системные разрешения (permissions):
<uses-permission android:name="android.permission.BLUETOOTH" /><uses-permission android:name="android.permission.BLUETOOTH_ADMIN" /><uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
Убедитесь, что все разрешения одобрены, или запросите их у
пользователя. РазрешениеACCESS_COARSE_LOCATION
Google
считает опасным и для него требуется обязательное согласие
пользователя.
private boolean hasPermissions() { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { if (getApplicationContext().checkSelfPermission(Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED) { requestPermissions(new String[] { Manifest.permission.ACCESS_COARSE_LOCATION }, ACCESS_COARSE_LOCATION_REQUEST); return false; } } return true;}
Прим. переводчика, в моем проекте для корректной работы с
BLE потребовалось еще 2
разрешения:ACCESS_FINE_LOCATION
(для
API<23) иACCESS_BACKGROUND_LOCATION
обсуждение на
Stackoverflow.
В итоге полный список разрешений включая версию Android10:
<uses-permission android:name="android.permission.BLUETOOTH" /><uses-permission android:name="android.permission.BLUETOOTH_ADMIN" /><uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" /><uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" /><uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />
После получения всех нужный разрешений, нужно проверить включен
Bluetooth, если нет - используйтеIntent
для запуска
запроса на включение:
BluetoothAdapter bluetoothAdapter = BluetoothAdapter.getDefaultAdapter();if (!bluetoothAdapter.isEnabled()) { Intent enableBtIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE); startActivityForResult(enableBtIntent, REQUEST_ENABLE_BT);}
Мы научились запускать сканирование BLE устройств с учетом жизненного цикла Activity (Fragment / Service), использовать фильтры и различные настройки сканирования, также узнали все нужные разрешения (permissions) для удачного запуска сканирования и особенности работы Android-Bluetooth кеша. В следующей статье мы погрузимся глубже в процесс подключения и отключения к устройствам.
Спасибо!
Часть #2 (connecting/disconnecting)
Часть #3 (read/write), вы здесь
Впредыдущей статьемы подробно поговорили о подключении/отключении BLE устройств. Эта статья очтенииизаписихарактеристик, а такжевключении-выключении уведомлений.
Многие разработчики, которые начинают работать с BLE на Android, сталкиваются с проблемами чтения/записи BLE характеристик. НаStackoverflowполно людей, предлагающих просто использовать задержки Большинство таких советов неверные.
Есть две основные причины проблем:
Операции чтения/записи асинхронные. Это значит,
что вызов метода вернется немедленно, но результат вызова вы
получите немного позже в соответствующих колбеках.
НапримерonCharacteristicRead()
илиonCharacteristicWrite()
.
Одновременно может быть запущена только одна
операция. Нужно дождаться выполнения текущей операции, и
затем, запускать следующую. В исходном
кодеBluetoothGatt
есть блокирующая переменная, которая
при запуске операции устанавливается и при вызове колбека
сбрасывается. Google забыла про это упомянуть в документации
(Прим. переводчика: речь идет
оmDeviceBusy
иmDeviceBusyLock
здесь).
Первая причина, на самом деле, не является проблемой, такова природа BLE. Асинхронное программирование это распространенная штука, используется, например, при сетевых вызовах. Однако вторая причина раздражает и требует специального подхода.
Ниже кусок кодаBluetoothGatt.java
с блокировкой
переменнойmDeviceBusy
, перед чтением
характеристики:
public boolean readCharacteristic(BluetoothGattCharacteristic characteristic) { if ((characteristic.getProperties() & BluetoothGattCharacteristic.PROPERTY_READ) == 0) { return false; } if (VDBG) Log.d(TAG, "readCharacteristic() - uuid: " + characteristic.getUuid()); if (mService == null || mClientIf == 0) return false; BluetoothGattService service = characteristic.getService(); if (service == null) return false; BluetoothDevice device = service.getDevice(); if (device == null) return false; synchronized (mDeviceBusy) { if (mDeviceBusy) return false; mDeviceBusy = true; } try { mService.readCharacteristic(mClientIf, device.getAddress(), characteristic.getInstanceId(), AUTHENTICATION_NONE); } catch (RemoteException e) { Log.e(TAG, "", e); mDeviceBusy = false; return false; } return true;}
Когда приходит результат чтения/записи,
переменнаяmDeviceBusy
сбрасывается в false снова:
public void onCharacteristicRead(String address, int status, int handle, byte[] value) { if (VDBG) { Log.d(TAG, "onCharacteristicRead() - Device=" + address + " handle=" + handle + " Status=" + status); } if (!address.equals(mDevice.getAddress())) { return; } synchronized (mDeviceBusy) { mDeviceBusy = false; }....
Выполнять чтение/запись по одной операции за раз неудобно, но
любое сложное приложение должно это учитывать. Решение этой
проблемы - использованиеочереди команд. Все BLE
библиотеки, которые я ранее упоминал, так или иначе реализуют
очередь. Это одна из лучших практик! Идея простая каждая команда
сначала добавляется в очередь. Затем команда забирается из очереди
на исполнение, после результата, команда помечается как завершенная
и, удаляется из очереди. Запускать команды можно в любое время, но
они выполняются точно в том порядке, в котором поступают в очередь.
Это очень упрощает разработку под BLE. В iOS аналогично работает
фреймворкCoreBluetooth
(Прим. переводчика: который
намного удобнее, чем реализация Bluetooth стека в
Android).
Очередь создается для каждого объектаBluetoothGatt
.
К счастью, Android сможет обрабатывать очереди от нескольких
объектовBluetoothGatt
, вам не нужно об этом
беспокоиться (Прим. переводчика: у меня это не сработало, я
использовал глобальную очередь команд для всех устройств).
Есть много способов создать очередь, мы будем использовать простую
очередьQueue
сRunnable
для каждой команды и
переменнойcommandQueueBusy
для отслеживания работы
команды:
private Queue<Runnable> commandQueue;private boolean commandQueueBusy;
Мы добавляем новый экземплярRunnable
в очередь при
выполнении команды. Ниже пример чтения характеристики
(readCharacteristic):
public boolean readCharacteristic(final BluetoothGattCharacteristic characteristic) { if(bluetoothGatt == null) { Log.e(TAG, "ERROR: Gatt is 'null', ignoring read request"); return false; } // Check if characteristic is valid if(characteristic == null) { Log.e(TAG, "ERROR: Characteristic is 'null', ignoring read request"); return false; } // Check if this characteristic actually has READ property if((characteristic.getProperties() & PROPERTY_READ) == 0 ) { Log.e(TAG, "ERROR: Characteristic cannot be read"); return false; } // Enqueue the read command now that all checks have been passed boolean result = commandQueue.add(new Runnable() { @Override public void run() { if(!bluetoothGatt.readCharacteristic(characteristic)) { Log.e(TAG, String.format("ERROR: readCharacteristic failed for characteristic: %s", characteristic.getUuid())); completedCommand(); } else { Log.d(TAG, String.format("reading characteristic <%s>", characteristic.getUuid())); nrTries++; } } }); if(result) { nextCommand(); } else { Log.e(TAG, "ERROR: Could not enqueue read characteristic command"); } return result;}
В этом методе, сначала проверяем все ли готово для выполнения
(наличие и тип характеристики) и логгируем ошибки, если они есть.
ВнутриRunnable
, фактически вызывается
методreadCharacteristic()
, который выдает команду на
устройство. Мы также отслеживаем сколько было попыток, чтобы
сделать повтор в случае ошибки (Прим. переводчика: это лучшая
тактика, чтобы добиться стабильной работы с устройством). Если
чтение характеристики возвращаетfalse
, мы логгируем
ошибку, завершаем команду, чтобы можно было запустить следующую.
Наконец вызываетсяnextCommand()
, чтобы запустить
следующую команду из очереди:
private void nextCommand() { // If there is still a command being executed then bail out if(commandQueueBusy) { return; } // Check if we still have a valid gatt object if (bluetoothGatt == null) { Log.e(TAG, String.format("ERROR: GATT is 'null' for peripheral '%s', clearing command queue", getAddress())); commandQueue.clear(); commandQueueBusy = false; return; } // Execute the next command in the queue if (commandQueue.size() > 0) { final Runnable bluetoothCommand = commandQueue.peek(); commandQueueBusy = true; nrTries = 0; bleHandler.post(new Runnable() { @Override public void run() { try { bluetoothCommand.run(); } catch (Exception ex) { Log.e(TAG, String.format("ERROR: Command exception for device '%s'", getName()), ex); } } }); }}
Обратите внимание, мы используем методpeek()
для
получения объектаRunnable
из очереди, чтобы можно было
повторить запуск позже. Этот метод не удаляет объект из
очереди.
Результат чтения будет отправлен в ваш колбек:
@Overridepublic void onCharacteristicRead(BluetoothGatt gatt, final BluetoothGattCharacteristic characteristic, int status) { // Perform some checks on the status field if (status != GATT_SUCCESS) { Log.e(TAG, String.format(Locale.ENGLISH,"ERROR: Read failed for characteristic: %s, status %d", characteristic.getUuid(), status)); completedCommand(); return; } // Characteristic has been read so processes it ... // We done, complete the command completedCommand();}
Мы завершаем командуcompletedCommand()
после
обработки нового значения. Это помогает избежать одновременный
вызов другой команды и состояния гонки.
Теперь мы готовы завершить команду,
убираемRunnable
из очереди через
вызовpoll()
и запускаем следующую из очереди:
private void completedCommand() { commandQueueBusy = false; isRetrying = false; commandQueue.poll(); nextCommand();}
В некоторых случаях (ошибка, неожиданное значение), вам нужно
будет повторить команду. Сделать это просто, так как
объектRunnable
остается в очереди до
вызоваcompletedCommand()
. Чтобы не уйти в бесконечное
повторение проверяем лимит на повторы:
private void retryCommand() { commandQueueBusy = false; Runnable currentCommand = commandQueue.peek(); if(currentCommand != null) { if (nrTries >= MAX_TRIES) { // Max retries reached, give up on this one and proceed Log.v(TAG, "Max number of tries reached"); commandQueue.poll(); } else { isRetrying = true; } } nextCommand();}
Чтение характеристики достаточно простая операция, а запись требует дополнительных пояснений. Для выполнения записи нужно предоставитьхарактеристику,массив байтовитип записи. Существует несколько типов записи, важные для нас это:
WRITE_TYPE_DEFAULT
(вы получите ответ от устройства,
например, код завершения);
WRITE_TYPE_NO_RESPONSE
(никакого ответа от
устройства не будет).
Использовать тот или иной тип зависит от вашего устройства и характеристики (иногда она поддерживает оба типа записи, иногда только один конкретный тип).
В Android каждая характеристика имеет дефолтный тип записи, который определяется при ее создании. Ниже фрагмент кода из исходников Android, где определяется тип:
...if ((mProperties & PROPERTY_WRITE_NO_RESPONSE) != 0) { mWriteType = WRITE_TYPE_NO_RESPONSE;} else { mWriteType = WRITE_TYPE_DEFAULT;}...
Как вы видите, это работает нормально, если характеристика
поддерживает только один их двух типов записи. Если характеристика
поддерживает оба типа, то значение по умолчанию
будетWRITE_TYPE_NO_RESPONSE
. Имейте это ввиду!
Перед записью можно проверить характеристику, поддерживает ли она нужный тип записи:
// Check if this characteristic actually supports this writeTypeint writeProperty;switch (writeType) { case WRITE_TYPE_DEFAULT: writeProperty = PROPERTY_WRITE; break; case WRITE_TYPE_NO_RESPONSE : writeProperty = PROPERTY_WRITE_NO_RESPONSE; break; case WRITE_TYPE_SIGNED : writeProperty = PROPERTY_SIGNED_WRITE; break; default: writeProperty = 0; break;}if((characteristic.getProperties() & writeProperty) == 0 ) { Log.e(TAG, String.format(Locale.ENGLISH,"ERROR: Characteristic <%s> does not support writeType '%s'", characteristic.getUuid(), writeTypeToString(writeType))); return false;}
Я рекомендую всегда явно указывать тип записи и не полагаться на дефолтные настройки выбранные Android!
Итак, запись массива байтовbytesToWrite
в
характеристику выглядит так:
characteristic.setValue(bytesToWrite);characteristic.setWriteType(writeType);if (!bluetoothGatt.writeCharacteristic(characteristic)) { Log.e(TAG, String.format("ERROR: writeCharacteristic failed for characteristic: %s", characteristic.getUuid())); completedCommand();} else { Log.d(TAG, String.format("writing <%s> to characteristic <%s>", bytes2String(bytesToWrite), characteristic.getUuid())); nrTries++;}
Кроме самостоятельного чтения и записи характеристик, вы можете включить или отключить уведомления от устройств. При включении уведомления, устройство сообщит вам о появлении новых данных и отправит их автоматически.
Для включения уведомлений нужно сделать две вещи в Android:
вызватьsetCharacteristicNotification
. Bluetooth
стек будет ожидать уведомления для этой характеристики.
записать1или2какunsigned
int16
в дескриптор конфигурации характеристик (Client
Characteristic Configuration, сокращенно - ССС). Дескриптор CCC
имеет короткий UUID2902.
Почему1или2? Потому что под капотом Bluetooth стека есть Уведомление и Индикация. Полученное Уведомление не подтверждаются стеком Bluetooth, а Индикация наоборот подтверждается стеком. При использовании Индикации, устройство будет точно знать, что данные получены и может их, например, удалить из локального хранилища. С точки зрения Android приложения нет разницы: в обоих случаях вы просто получите массив байтов и Bluetooth стек уведомит устройство об этом, если вы используете Индикацию. Итак,1включает уведомления,2 индикацию. Чтобы выключить их, записываем0. Вы должны самостоятельно определить, что записать в дескриптор CCC.
В iOS методsetNotify()
делает всю работу за вас.
Ниже пример, как сделать тоже самое на Android, там сначала идут
проверки входных параметров, определяется что записать в дескриптор
и, наконец команда отправляется в очередь:
private final String CCC_DESCRIPTOR_UUID = "00002902-0000-1000-8000-00805f9b34fb";public boolean setNotify(BluetoothGattCharacteristic characteristic, final boolean enable) { // Check if characteristic is valid if(characteristic == null) { Log.e(TAG, "ERROR: Characteristic is 'null', ignoring setNotify request"); return false; } // Get the CCC Descriptor for the characteristic final BluetoothGattDescriptor descriptor = characteristic.getDescriptor(UUID.fromString(CCC_DESCRIPTOR_UUID)); if(descriptor == null) { Log.e(TAG, String.format("ERROR: Could not get CCC descriptor for characteristic %s", characteristic.getUuid())); return false; } // Check if characteristic has NOTIFY or INDICATE properties and set the correct byte value to be written byte[] value; int properties = characteristic.getProperties(); if ((properties & PROPERTY_NOTIFY) > 0) { value = BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE; } else if ((properties & PROPERTY_INDICATE) > 0) { value = BluetoothGattDescriptor.ENABLE_INDICATION_VALUE; } else { Log.e(TAG, String.format("ERROR: Characteristic %s does not have notify or indicate property", characteristic.getUuid())); return false; } final byte[] finalValue = enable ? value : BluetoothGattDescriptor.DISABLE_NOTIFICATION_VALUE; // Queue Runnable to turn on/off the notification now that all checks have been passed boolean result = commandQueue.add(new Runnable() { @Override public void run() { // First set notification for Gatt object if(!bluetoothGatt.setCharacteristicNotification(descriptor.getCharacteristic(), enable)) { Log.e(TAG, String.format("ERROR: setCharacteristicNotification failed for descriptor: %s", descriptor.getUuid())); } // Then write to descriptor descriptor.setValue(finalValue); boolean result; result = bluetoothGatt.writeDescriptor(descriptor); if(!result) { Log.e(TAG, String.format("ERROR: writeDescriptor failed for descriptor: %s", descriptor.getUuid())); completedCommand(); } else { nrTries++; } } }); if(result) { nextCommand(); } else { Log.e(TAG, "ERROR: Could not enqueue write command"); } return result;}
Результат записи в CCC дескриптор обрабатывается в
колбекеonDescriptorWrite
. Здесь вы должны отличить
запись в CCC от записей в другие дескрипторы. Во время обработки
колбека, мы также должны хранить, какие в данный момент
характеристики уведомляются.
@Overridepublic void onDescriptorWrite(BluetoothGatt gatt, final BluetoothGattDescriptor descriptor, final int status) { // Do some checks first final BluetoothGattCharacteristic parentCharacteristic = descriptor.getCharacteristic(); if(status!= GATT_SUCCESS) { Log.e(TAG, String.format("ERROR: Write descriptor failed value <%s>, device: %s, characteristic: %s", bytes2String(currentWriteBytes), getAddress(), parentCharacteristic.getUuid())); } // Check if this was the Client Configuration Descriptor if(descriptor.getUuid().equals(UUID.fromString(CCC_DESCRIPTOR_UUID))) { if(status==GATT_SUCCESS) { // Check if we were turning notify on or off byte[] value = descriptor.getValue(); if (value != null) { if (value[0] != 0) { // Notify set to on, add it to the set of notifying characteristics notifyingCharacteristics.add(parentCharacteristic.getUuid()); } } else { // Notify was turned off, so remove it from the set of notifying characteristics notifyingCharacteristics.remove(parentCharacteristic.getUuid()); } } } // This was a setNotify operation .... } else { // This was a normal descriptor write.... ... }); } completedCommand();}
Чтобы узнать из какой характеристики пришло уведомление
используйте методisNotifying()
:
public boolean isNotifying(BluetoothGattCharacteristic characteristic) { return notifyingCharacteristics.contains(characteristic.getUuid());}
К сожалению, нельзя включить столько уведомлений, сколько хочешь. Начиная с Android-5 лимит равен 15. В более старых версиях он был равен 7 или даже 4. Большинство смартфонов поддерживают 15 уведомлений. Не забывайте отключать их, если они вам больше не нужны, чтобы не исчерпать лимит.
Итак, мы научились читать/писать характеристики, включать/выключать уведомления, а значит готовы использовать это в реальном проекте. Я думаю, что устройства BLE можно разделить на две категории:
Простые устройства. Например, термометр, который использует официальный Bluetooth Health Thermometer сервис. Такие устройства легко использовать, вы просто включаете уведомления и данные начинают поступать. Здесь мы используем только операции чтения характеристики, запись не нужна;
Сложные устройства. Это может быть любое устройство, но обычно все они используют свой внутренний протокол обмена данными. Часто эти протоколы не спроектированы под BLE, а просто транслируют внутренний последовательный протокол в BLE, где одна характеристика используется для отправки данных, а другая для приема. Сложность в том, что вам требуется знать большое количество команд для работы с устройством: авторизация, обновление пользовательских параметров, параметров самого устройства, получение сохраненных данных и т.д.
Простые устройства обычно не создают проблем с потоками, для сложных следует работать внимательно. Чтение, запись и уведомления в этом случае будут чередоваться и могут мешать друг другу, особенно если у вас устройство с высокой частотой передачи данных (30Hz или около).
Типичная проблема с потоками выглядит так:
приходит уведомление
вы отправляете событие в свою собственную очередь для обработки
запускается обработку полученных данных
в это время приходит новое уведомление и перезаписывает
предыдущее значение вBluetoothGattCharacteristic
если ваша обработка данных медленная, вы потенциально теряете значение из первого уведомления.
Причины такого поведения:
как только сообщение доставлено, Android будет отправлять следующее(если оно есть). Посколько обработка данных отправляется в другой поток, текущий освобождается и Android продолжит доставку уведомлений;
Androidпереиспользует BluetoothGattCharacteristic
объекты внутри. Они создаются в время обнаружения сервисов
(services discovering) и после
этогопереспользуютсямногократно. Таким образом,
когда приходит уведомления Android сохраняет значение в
объектBluetoothGattCharacteristic
. Если характеристика
в этот момент обрабатывается в другом потоке мы получим гонку
состояний (race condition) и результат будет непредсказуемым.
Очевидно, чтонужно всегда работать с копией массива байтов. Получили данные, сразу же делаем копию и работаем с ней.
Ниже пример, который использует такую тактику:
@Overridepublic void onCharacteristicChanged(BluetoothGatt gatt, final BluetoothGattCharacteristic characteristic) { // Copy the byte array so we have a threadsafe copy final byte[] value = new byte[characteristic.getValue().length]; System.arraycopy( characteristic.getValue(), 0, value, 0, characteristic.getValue().length); // Characteristic has new value so pass it on for processing bleHandler.post(new Runnable() { @Override public void run() { myProcessor.onCharacteristicUpdate(BluetoothPeripheral.this, value, characteristic); } });}
Есть несколько дополнительных рекомендаций по работе с BLE на Android. Поскольку стек BLE в основном асинхронный, у нас есть мульти-поточная обработка задач.
Android использует потоки:
При сканировании (результаты приходят
вmain
поток);
Вызове колбековBluetoothGattCallback
(выполняются в
потокахBinder
);
Обработка результатов сканирования на main потоке не будет
проблемой. Но с потоками Binder все немного сложнее. При вызове
колбека на потоке Binder, Android не будет отправлять новые данные
пока не закончится обработка текущих, то есть поток Binder
блокируется пока ваш код не завершится. Следует избегать тяжелых
операций в колбеках, никакихsleep()
или что-то
подобное. Кроме того, никаких новых вызовов в
объектеBluetoothGatt
, пока вы находитесь в потоке
Binder, хотя большинство методов асинхронные.
Я рекомендую следующее:
Всегда выполняйте вызовыBluetoothGattCallback
в
отдельном потоке, возможно даже из потока пользовательского
интерфейса (Прим. переводчика: работать на main потоке - плохая
идея, если у вас есть активный обмен с устройством, обязательно
будут залипания UI, не делайте так);
Освобождайте потокиBinder
как можно быстрее
иникогдане блокируйте их;
Самый простой способ выполнить рекомендации выше создать
выделенныйHandler
и использовать его для обработки
данных и выдачи новых команд. Обратите внимание, я уже
использовалHandler
на примере кода для
колбекаonCharacteristicUpdate
.
Объявление объекта:
Handler bleHandler = new Handler();
Если хотите
запуститьHandler
наmain
потоке:
Handler bleHandler = new Handler(Looper.getMainLooper());
Прокрутите назад и взгляните на наш
методnextCommand()
,
каждыйRunnable
выполняется в нашем
собственномHandler
, следовательно, мы гарантируем, что
все команды выполняются вне потокаBinder
.
В этой статье мы разобрались с чтением и записью характеристик, включением и выключением уведомлений/нотификаций. В следующей статье, мы детально изучим процесс спряжения с устройством (bonding).
Не терпится поработать с BLE? Попробуйтемою библиотеку Blessed for Android. Она использует все подходы из этой серии статей и упрощает работу с BLE в вашем приложении.
Содержание
Часть #2 (connecting/disconnecting)
Часть #4 (bonding), вы здесь
Впредыдущей статьемы разобрались с операциями чтения/записи, включения/выключения нотификаций и организации очереди команд. В этой статье мы поговорим осопряжении устройств(Прим. переводчика далее я буду использовать термин bonding).
Некоторые устройства для правильной работы требуют bonding. Технически это обозначает, что генерируются ключи шифрования, обмениваются и хранятся, для безопасного обмена данными. При запуске процедуры bonding, Android может запросить у пользователя согласие, пин-код или кодовую фразу. При следующих подключениях, Android уже знает, что устройство сопряжено и обмен ключами шифрования происходит скрытно без участия пользователя. Использование bonding делает подключение к устройству более безопасным, так как соединение зашифровано.
Тема bonding плохо описана в документации Google, полностью
непонятно, как приложение должно работать с bonding. Первое на что
вы обратите внимание это методcreateBond()
. Что
интересно, в iOS такого метода нет вообще и
фреймворкCoreBluetooth
делает все за вас! Тогда зачем
вызыватьcreateBond()
? Кажется немного странным, вам
заранее надо знать, какие устройства требуют bonding, а какие нет.
Протокол Bluetooth был спроектирован так, что обычно устройства
явно говорят им требуется или нет bonding. Я копнул немного глубже
и поэкспериментировал. Чтобы разобраться с этим, ушло некоторое
время, но в конце концов, все оказалось просто.
Принципы работы с bonding:
Пусть Android сам работает с bonding.Android
сделает bonding за вас, когда устройство скажет, что нужен bonding,
или во время операции чтения/записи зашифрованной характеристики. В
большинстве случаев не надо
вызыватьcreateBond()
самостоятельно (Прим.
переводчика: мне пришлось это делать самостоятельно, из-за
особенностей прошивки устройства. Кроме того, Samsung работает
по-другому, чем другие вендоры);
Нельзя запускать другие операции, в процессе работы bonding.Если вы будете запускать обнаружение сервисов или читать/писать характеристики, это приведет к ошибками и сбросу соединения. Просто дождитесь пока Android выполнит bonding;
Продолжайте очередь операций после завершения bonding.Как только операция bonding завершилась, продолжайте выполнение операций из очереди;
Если вы знаете, что делаете, и это необходимовы
можете вызватьcreateBond()
для запуска bonding с
устройством самостоятельно. Но это должно быть исключением.
Есть три причины, по которым запускается процесс bonding:
При соединении с устройством, оно сигнализирует, что требуется bonding, до любых других операций;
Характеристика может быть зашифрована для чтения или
записи.При попытке прочитать или записать такую
характеристику, запустится bonding. Если он пройдет удачно
чтение/запись также выполнится, в случае ошибки bonding
чтение/запись выполнится с
ошибкойINSUFFICIENT_AUTHENTICATION
. Такая же ошибка
есть в iOS.
Вызапускаете процесс bonding
самостоятельночерез вызовcreateBond()
. Если
этого требует ваше устройство, оно вероятно не будет совместимо с
iOS, так как там нет аналогичного метода. Но формально в протоколе
Bluetooth такое возможно.
Давайте обсудим каждый случай.
Если устройство требует bonding сразу после подключения, то при
вызове колбекаonConnectionStateChange
состояние bonding
будетBOND_BONDING
. Это означает что идет процесс
bonding ивы не должны ничего делать в этот момент,
например вызыватьdiscoverServices()
, до тех пор пока
процесс bonding не закончится! Иначе возможны неожиданные
дисконнекты или ошибки обнаружения сервисов. Поэтому следует
специально обрабатывать эту ситуацию
вonConnectionStateChanged
:
// Take action depending on the bond stateif(bondstate == BOND_NONE || bondstate == BOND_BONDED) { // Connected to device, now proceed to discover it's services ... } else if (bondstate == BOND_BONDING) { // Bonding process has already started let it complete Log.i(TAG, "waiting for bonding to complete");}
Чтобы следить, как идет процесс bonding, необходимо
зарегистрировать колбекBroadcastReceiver
для
интентаACTION_BOND_STATE_CHANGED
до
вызоваconnectGatt
. Этот колбек будет вызываться
несколько раз в процессе bonding.
context.registerReceiver(bondStateReceiver, new IntentFilter(ACTION_BOND_STATE_CHANGED));private final BroadcastReceiver bondStateReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { final String action = intent.getAction(); final BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); // Ignore updates for other devices if (bluetoothGatt == null || !device.getAddress().equals(bluetoothGatt.getDevice().getAddress())) return; // Check if action is valid if(action == null) return; // Take action depending on new bond state if (action.equals(ACTION_BOND_STATE_CHANGED)) { final int bondState = intent.getIntExtra(EXTRA_BOND_STATE, ERROR); final int previousBondState = intent.getIntExtra(BluetoothDevice.EXTRA_PREVIOUS_BOND_STATE, -1); switch (bondState) { case BOND_BONDING: // Bonding started ... break; case BOND_BONDED: // Bonding succeeded ... break; case BOND_NONE: // Oh oh ... break; } } }};
После завершения bonding, мы запускаем обнаружение сервисов (service discovery), если они еще не обнаружены, это можно проверить:
case BOND_BONDED: // Bonding succeeded Log.d(TAG, "bonded"); // Check if there are services if(bluetoothGatt.getServices().isEmpty()) { // No services discovered yet bleHandler.post(new Runnable() { @Override public void run() { Log.d(TAG, String.format("discovering services of '%s'", getName())); boolean result = bluetoothGatt.discoverServices(); if (!result) { Log.e(TAG, "discoverServices failed to start"); } } }); }
Вот и все, что касается особенностей bonding при подключении.
Если bonding стартует при чтении/записи зашифрованной
характеристики, то самая первая операция чтения/записи окончится с
ошибкойGATT_INSUFFICIENT_AUTHENTICATION
. На версиях
Android-6, 7 вы получите эту ошибку
вonCharacteristicRead
/onCharacteristicWrite
,
при этом процесс bonding уже будет запущен внутри Android. С версии
Android-8 ошибки не будет и Android самостоятельно повторит
операцию после завершения bonding. Получается на Android-6, 7 надо
повторить операцию чтения/записи самостоятельно. Итак, вам надо
поймать ошибку и сделать повтор операции после bonding.
При получении такой ошибки, не продолжайте запуск операций:
public void onCharacteristicRead(BluetoothGatt gatt, final BluetoothGattCharacteristic characteristic, int status) { // Perform some checks on the status field if (status != GATT_SUCCESS) { if (status == GATT_INSUFFICIENT_AUTHENTICATION ) { // Characteristic encrypted and needs bonding, // So retry operation after bonding completes // This only happens on Android 5/6/7 Log.w(TAG, "read needs bonding, bonding in progress"); return; } else { Log.e(TAG, String.format(Locale.ENGLISH,"ERROR: Read failed for characteristic: %s, status %d", characteristic.getUuid(), status)); completedCommand(); return; } }...
После bonding проверяем, есть ли операция в процессе выполнения и повторяем ее:
case BOND_BONDED: // Bonding succeeded Log.d(TAG, "bonded"); // Check if there are services ... // If bonding was triggered by a read/write, we must retry it if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) { if (commandQueueBusy && !manuallyBonding) { bleHandler.postDelayed(new Runnable() { @Override public void run() { Log.d(TAG, "retrying command after bonding"); retryCommand(); } }, 50); } }
Как я говорил выше, лучше не
вызыватьcreateBond
самостоятельно, хотя сделать это,
конечно можно. Спросите себя, это действительно необходимо? На iOS
нет эквивалента методаcreateBond()
, если этот метод
единственный способ сделать bonding для вашего устройства, то
скорее всего оно несовместимо с iOS. Это прямо указывается в
документации iOS. Я перепробовал несколько десятков BLE устройств,
и только в единственном случае я
вызывалcreateBond()
самостоятельно из-за исключительных
обстоятельств.
При вызовеcreateBond
самостоятельно, также нельзя
ничего делать, пока bonding не завершится и требуется
регистрировать колбекBroadcastReceiver
для отслеживания
процесса. Если устройство уже сопряжено (bonding завершился),
тоcreateBond()
вызовет ошибку, надо проверить состояние
bonding перед вызовом.
Еще одна причина
запускатьcreateBond()
самостоятельно упростить
повторное подключение. ОбъектBluetoothDevice
можно
получить при помощи MAC-адреса, если устройство закешировано или
сопряжено (bonding). Таким образом вам не придется снова
сканировать устройство Может пригодиться! (Прим. переводчика: я
как раз работал с таким вариантом подключения, его требовалось
сделать полностью детерминированным, разбитым на подфазы, для
точного понимания что происходит).
Как пользователь Android, я могу увидеть список сопряженных устройств в Bluetooth настройках. Там можно удалить устройство, bonding также будет удален.
Требуется некоторое время на удаление устройства.
Достаточно странно, что нет официального способа удалить bonding
устройства программно. Это можно сделать, используя скрытый
методremoveBond()
, доступный через механизм рефлексии
в Java:
try { Method method = device.getClass().getMethod("removeBond", (Class[]) null); result = (boolean) method.invoke(device, (Object[]) null); if (result) { Log.i(TAG, "Successfully removed bond"); } return result;} catch (Exception e) { Log.e(TAG, "ERROR: could not remove bond"); e.printStackTrace(); return false;}
Большинство BLE устройств поддерживают bonding только с одним смартфоном. Типичный сценарий, когда мы теряем bonding такой:
Смартфон А делает bonding с устройством Х
Смартфон B делает bonding с устройством Х
Смартфон А переподключается к устройству Х, и теперь bonding потерян.
При реконнекте смартфон А получит состояние
bondingBOND_NONE
в
колбекеBroadcastReceiver
. Сравнивайте предыдущее
состояние bonding, чтобы понять была потеря или нет:
case BOND_NONE: if(previousBondState == BOND_BONDING) { // Bonding failed ... } else { // Bond lost ... } disconnect(); break;
Если случилась потеря bonding, отключаемся от устройства, иначе будут происходить странные вещи и соединение с устройством не будет нормально работать. Когда вы делаете реконнект, Android снова запускает процедуру bonding. Тоже самое происходит и при обрыве связи.
Существует мелкий баг, о котором следует знать. При потере bonding, кажется нужнаодна секундадля того, чтобы Bluetooth стек обновил свое внутреннее состояние. Если сделать реконнект сразу после потери bonding, Android может сказать, что устройство все еще сопряжено, но на самом деле это будет не так. Сделайте задержку в одну секунду перед переподключением.
Прим. переводчика: не нашел толковой замены слова pairing, спаривание - звучит неблагозвучно здесь.
Когда Android запускает процесс bonding, может появится всплывающее окно. Я говорю может, потому что некоторые вендоры используют свою логику показа этого попапа (Прим. переводчика: на моем Samsung-S9, после обновления до Android-10, это попап стал появляться всегда, при коннекте любого нового устройства, до этого обновления, такого не было). На смартфонах Google (или других вендоров, где код Android в этой части не изменялся), всплывающий попап появляется только при определенный условиях.
Pairing попап появляется на переднем фоне если:
Устройство недавно было в режиме обнаружения;
Устройство было обнаружено недавно;
Устройство недавно было выбрано в сборщике устройств;
Экран настроек Bluetooth виден.
Значение недавно означаетв течение последних 60 секунд. Условия выглядят непонятными, поэтому лучше посмотреть наисходный код. Если все эти условия не выполняются, то вместо попапа появится уведомление, которое большинство пользователей не замечает. Но если они заметят и нажмут на него, всплывающее окно сбивает с толку своей опцией доступа к контактам. Ужасный UI по-моему! Некоторые производители (справедливо) решили исправить такое поведение! На устройствах Samsung всплывающее окно-подтверждение (для подключений в режиме JustWorks) вообще не отображается, а всплывающие окна всегда появляются на переднем плане. При этом всплывающее окно открывается только при вводе PIN-кода или кодовой фразы. Никаких доступов к контактам и всегда передний план. Так намного лучше!
Так что, если вдруг вы захотите, чтобы всплывающее окно всегда отображалось на переднем плане, запускайте обнаружение на одну секунду перед подключением к устройству. Выглядит как хак, но это работает. Код ниже:
public void startPairingPopupHack() { String manufacturer = Build.MANUFACTURER; if(!manufacturer.equals("samsung")) { bluetoothAdapter.startDiscovery(); callBackHandler.postDelayed(new Runnable() { @Override public void run() { Log.d(TAG, "popup hack completed"); bluetoothAdapter.cancelDiscovery(); } }, 1000); }}
Важный момент здесь вы не должны запускать никакие BLE операции пока попап на экране. Подождите ответа от пользователя.
Если учтете все эти моменты, bonding будет работать как часики!
На этом мы завершаем цикл статей о BLE в Android (Прим. переводчика: я готовлю отдельную статью-заключение, где опишу свои подходы к работе с BLE устройствами на Android, небольшие ньюансы и решения для стабильной продолжительной работы с устройствами). Надеюсь эта информация будет полезной вам и сделает работу с BLE комфортнее. Чем больше знаешь про BLE, тем лучше работает ваше приложение. Успехов!
Не терпится поработать с BLE? Попробуйтемою библиотеку Blessed for Android. Она использует все подходы из этой серии статей и упрощает работу с BLE в вашем приложении.
/dev/tty.usbmodemflip_Oleg