Работаем с Bluetooth в iOS

Одно из моих любимых направлений в мобильной разработке — это разработка мобильных приложений, которые работают с объектами вне их инфраструктуры, с различными интересными гаджетами, датчиками и объектами из мира интернета вещей. Сегодня поговорим как раз про это направление, а точнее, про Bluetooth.

📦 Демо проект с простым приложением для приёма сообщений

Эта статья — конспект к моему докладу на неделе «Нестандартной разработки iOS» прошедшей в рамках пятого сезона Podlodka iOS Crew. На этой неделе, помимо работы с Bluetooth, были рассмотрены и другие интересные моменты: Metal, Core Audio и многое другое! Вы еще можете приобрести доступ к докладам или залететь на следующий сезон!


Наверное, никому из вас не нужно объяснять что такое блютус и зачем он применяется. Сейчас эта технология беспроводной передачи данных имеет индекс версии 5.1 и очень сильно скакнула в своём развитии. Свой первый айфон я купил в 2009 году, и даже тогда мне приходилось выслушивать тонны хейта по поводу несовершенства этого девайса. Одним из саааамых частых и бесящих меня поинтов было постоянные упреки в отсутствии передачи файлов по блютусу. И вообще в неполноценности Bluetooth на iOS и невозможности им пользоваться кроме как подключить беспроводные наушники. Курьезный факт: уже через 3 года, iOS имела самый мощный среди мобильных платформ фреймворк, который конкуренты еще долго не смогут догнать (ИМХО и не догнали). Действительно, большинство вещей, о которых я сегодня расскажу были представлены в 2012 году (а часть и вовсе в 2011) и мало менялись с тех пор.

Итак, Блютус. Я не буду давать каких-то формальных объяснений или уходить глубоко в обзорную историю. Пройдусь максимально поверхностно. Для нас, как для разработчиков, это интерфейс взаимодействия с внешними устройствами. История его развития начинается в далёком 1998 году, но более менее массовое распространение он получил в середине 2000х с широким развитием рынка смартфонов и коммуникаторов. На заре iOS, вплоть до 4й версии, разработчики не имели возможности взаимодействовать с устройствами посредством блютус вообще, но всё изменилось в 2011 году, с выходом iOS 5. Ключом к такому стремительному росту стало принятие стандарта Bluetooth 4.0, который в 2010 году, который стал отвечать требованием рынка по параметрам скорости и энергопотребления. Последнее играло ключевую роль. Именно тогда появился стандарт, а точнее спецификация, BLE – Bluetooth Low Energy, который позволил сильно расширить сферу применения этой беспроводной технологии.

В чем же особенности BLE? Ну, как не сложно догадаться из названия, главная фишка в экстремально (на 2010 год уж точно) низком энергопотреблении. Спецификация позволяет разрабатывать устройства, которые будут минимально расходовать свою батарею и работать до одного года на одном заряде небольшого элемента питания. Спецификацию разработала компания Bluetooth Special Interest Group (SIG) и эти ребята настолько заморочились, насколько, как мне кажется, это было вообще возможно. Чтобы не давать волю полёта фантазии разработчикам и не плодить кучу конкурирующих страндартов, SIG разработала спецификацию под все возможные типы устройств, начиная с носимых датчиков пульса, заканчивая стационарными дверными замками. Именно взаимодействие через Bluetooth LE нам доступно через встроенный фреймворк CoreBluetooth. Apple тоже проделали гигантскую работу, скрыв от нас все детали работы с низкоуровневыми штучками и предоставили очень простой API с которым разберется и любой начинающий iOS разработчик!

Bluetooth Low Energy в деталях

Основой BLE является профиль GATT – General Attribute Profile, который предоставляет нам абстракции сервиса и характеристик. Характеристика — это, грубо говоря, ячейка данных, а сервис — это некоторое логическое их объединение. Может звучать непонятно, но сейчас я приведу пример и всё сразу встанет на свои места. Самый классический и на самом деле распространенный пример — термометр. Он может иметь сервис измерения температуры и иметь две характеристики: единицу измерения (℃, ℉) и собственно саму температуру. А еще он может определять влажность и для неё будет отдельный сервис с одной лишь характеристикой — влажности в процентах.

Всё взаимодействие в BLE строиться на классической клиент-серверной модели: тут тоже есть клиент и сервер, есть запросы. С этого момента я буду плавно переходить к самому фреймворку CoreBluetooth и уже новые термины объяснять ближе к предметной области, т.к. большинство терминов совпадают с теримнами BLE, только имеют префикс CB — CoreBluetooth.

CoreBluetooth

Итак, CoreBluetooth. Как уже было сказано, появился в iOS 5, был серьезно доработан в iOS 6 и существует до сих пор, не претерпев каких-либо серьезных изменений с тех пор. Как я уже сказал, всё взаимодействие строиться на классической клиент-серверной модели и в CB у нас есть сервер – Peripheral и клиент Сentral. Такой нейминг может слегка путать некоторое время, т.к. мы привыкли что именно сервер является неким центром, а CB переворачивает все с ног на голову. Но с другой стороны всё логично: Peripheral device — периферийное устройство, внешнее по отношению к нашему, центральному. Peripheral device’ом может быть фитнес трекер, умная лампочка, замок или целая система умного дома, а может быть и другое мобильное устройство или компьютер.

Поиск

Хотя и процесс является клиент-серверным, в отличии от HTTP, точный адрес или алиас устройства нам не известен, поэтому чтобы начать с ним взаимодействие необходимо его найти в эфире. Процесс обнаружения устройства называется сканированием или дискаверингом. Он выполняется Central устройством, той стороной, которая хочет к чему-то подключиться. Процесс заключается в сканировании Bluetooth эфира на предмет так называемых Advertisement-пакетов, которые должно отправлять периферийное устройство. Advertisement-пакет — это крохотный пакет регулярно отправляемый периферийным устройством, когда оно хочет чтобы его нашли. Он обычно содержит базовую информацию, необходимую для того, чтобы понять что класс устройства: термометр, замок, самокат или другой айфон. В зависимости от режима работы устройства и настроек его энергопотребления, пакеты могут рассылаться с различными интервалом, от раза в несколько микросекунд, до раза в десятки секунд, поэтому длительность сканирования напрямую зависит от режима работы периферийного устройства.

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

Итак, мы хотим найти термометр, который для нас является периферийным устройством. Значит, мы в этом случае будем клиентом, то есть Central Device. Объект который представляет клиента в фреймворке CoreBluetooth имеет тип CBCentralManager. Как не сложно догадаться, всё взаимодействие c внешним устройством предполагается асинхронным. И еще проще догадаться, что Apple предпочла паттерн делегат для обеспечения асинхронной работы. Нужный нам делегат имеет тип CBCentralManagerDelegate и объект, реализующий этот протокол и будет получать уведомления о событиях связанных с поиском и подключением.

Как только мы будем готовы сканировать эфир, нужно вызвать метод scanForPeripherals у менеджера CBCentralManager. Как только наше устройство увидит новый рекламный пакет, будет вызван метод делегата didDiscoverPeripheral с объектом CBPeripheral в параметрах метода. Когда мы найдем нужное устройство или устройства, нам нужно остановить сканирование методом stopScan и вызвать connect указав экземпляр класса CBPeripheral.





Поиск сервисов

Как только соединение будет установлено, мы можем начать опрашивать устройство. С этого момента мы уже работаем с объектом CBPeripheral и его делегат CBPeripheralDelegate которые представляют внешнее устройство, к которому мы подключились. Чтобы считать какое-то значение (характеристику) сначала нужно найти сервис, в составе которого эта характеристика существует. Запустим поиск сервисов. Мы можем как указать сервис или сервисы которые нас точно интересует, либо не указывать их и найти все, что реализует устройство.


☝🏻

Тут нужно отвлечься и объяснить как мы можем отличать сервисы друг от друга. Идентификатором для них и их характеристик служит UUID, уникальный идентификатор который бывает двух видов: 16 бит и 128 бит. 128 битные идентификаторы мы можем генерировать и использовать на своё усмотрение при написании собственных клиент-серверных приложений. А вот 16 битные зарезервированы SIG. Помните, я говорил что эти ребята сильно упоролись и описали почти всё что взаимодействует или может взаимодействовать по BLE? Так вот они пронумеровали все эти сервисы и характеристики и дали им уникальный 16 битный UUID. Если вы когда-либо захотите разработать железное устройство, с которым сможет работать не только ваше приложение, то вам следует изучить их спеки и следовать этим рекомендациям. Ну а если вы разрабатываете приложение для какого-то класса устройств, вы можете использовать эту спеку чтобы найти идентификатор нужного вам сервиса. Мы ищем термометр, можем смело указывать UUID 0x1809 и игнорировать остальные (если они есть).


Поиск характеристик

Окей, мы нашли сервисы. О чем получим уведомление в делегате. Теперь схожим образом можем найти характеристики в них, для каждой вызовем discoverCharacteristics(for:). Точно так же, мы можем указать UUIDы интересующих нас характеристик, так и получить все, но это будет медленнее. Когда процесс исследования характеристик завершиться делегат получит уведомление вызовом метода didDiscoverCharacteristicsForService а самы характеристики будут доступны в соответствующем свойстве.





Характеристики

На этом этапе мы наконец добрались до самого «вкусного» — до данных, а точнее, до характеристик. В большинстве несложных кейсов их можно разделить по типам операций которые они поддерживают: read, write, notify. На самом деле опций (корректнее — свойств) несколько больше, но в рамках сегодняшней статьи мы рассмотрим только эти.

Характеристика должна поддерживать хотя-бы одну из них, но может поддерживать и все вместе. Интересным тут является тип notify. Как не сложно догадаться, вместо прямого считывания, у нас есть возможность получить уведомление когда устройство самостоятельно решит отправить нам данные, например, когда они изменились. Возвращаясь к примеру с термометром, характеристика с единицей измерения будет иметь флаг read и write, а сама температура read и notify. Единицу измерения достаточно считать при подключении, а вот температуру считывать по таймеру — не лучшее решение. Мы же пытаемся быть Low Energy — постоянно опрашивать устройство может быть накладно как для нас, так и для периферийного устройства.

Для работы с характеристиками используется три основных метода:

С чтением данных всё просто: дернули метод readValue — отправиться запрос на устройство. Ответ получим в делегате CBPeripheralDelegate когда данные придут. Тоже самое произойдет, если данные обновятся и мы будем подписаны на них с помощью setNotifyValue

Запись же бывает двух видов — withResponse и withoutResponse. Не вдаваясь в подробности, это запись с и без подтверждения. Конечно, withResponse медленнее, т.к. на каждый запрос записи будет генерироваться ответ. Это стоит учитывать особенно, для передачи относительно больших объемов данных — запись withoutResponse будет предпочтительнее в этом случае.

Ограничения

Кстати, а что по лимитам? Не забываем что в названии маячит LE, что значит что он был спроектирован с максимальной экономией на всём, в том числе и размерах пакетов. BLE — это совсем не про стриминг и большие объемы данных. Скорость по стандарту 4.0 согласно Википедии всего 0,27 Мбит/сек.

По умолчанию, размер пакета с полезной информацией равен 23 байта, из которых 3 зарезервированных системой. Остаётся 20 байт полезной нагрузки. Работая с BLE вам придется столкнуться с этим ограничением и придумывать как делить ваши данные на пакеты вручную, т.к. очень часто 20 байт оказыватся недостаточно. В ряде случаев, когда обмен информацией будет происходить с современным смартфоном, данное ограничение может быть увеличено вплоть до 512 байт. CoreBluetooth берет на себя ответственность за переговоры о максимальном MTU, ровно как и на автоматическое деление на пакеты, если это поддерживается устройством. Если вам нужна другая скорость или объемы, то вероятно вам нужно использовать L2CAP канал и работать более низкоуровнево, минуя GATT, благо такая возможность есть с iOS 11 (только для устройств Apple).

Серверная часть

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

По аналогии с CBCentralManager, для создания сервера у нас есть CBPeripheralManager и его CBPeripheralManagerDelegate. Для создания нашего сервиса нам нужно будет в первую очередь создать сервис и его характеристики. Пусть у нас будет только один сервис с единственной характеристикой доступной только для чтения. Ответная часть CBService на стороне сервера — это CBMutableService. Да, API CoreBluetooth не «освифтили», поэтому у нас есть префикс Mutual для мутабельных версий классов. При создании нам нужно указать UUID, который можно сгенерировать в консоле командой uuidgen и указать основной ли это сервис у нашего устройства.

После создания сервиса его нужно наполнить характеристиками. Их представляет класс CBMutableCharacteristic. При его создании указывается

  • UUID
  • Свойства
  • Начальное значение (для статичных характеристик)
  • Права доступа

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


☝🏻

В начале рассказа о CBCentral я намеренно упустил момент, что при сканировании эфира можно указать UUID сервисов в которых мы заинтересованы. Согласитесь, было бы глупо и долго сканировать эфир, и потом подключаться ко всем найденым устройствам просто чтобы узнать есть ли у них искомый сервис или нет. Вместо этого сервер может добавить в рекламный пакет UUID наших главных сервисов, а клиенты при сканировании смогут фильтровать устройства которые им не интересны без подключения и считывания списка сервисов. Сделать это мы можем как раз указав соотвествующий ключ в упомянутый ранее словарь на начале Advertising`а.


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

Состояния

До сих пор я ничего не говорил о ситуациях когда Bluetooth недоступен или выключен. Состояние менеджера очень важно отслеживать и выполнять операции только в разрешенных состояниях, иначе не избежать крешей. Менеджер, будь то Central или Peripheral может находиться в следующих состояниях:

  • unknown
  • resetting — BLE стек перезагружается
  • unsupported — BLE не поддерживается
  • unauthorized — Пользователь отклонил передача прав
  • poweredOff — Bluetooth выключен
  • poweredOn — Bluetooth включен, можно работать

Начинать поиск устройств, рассылать Advertisement пакеты и даже добавлять сервисы в менеджер можно только в poweredOn состоянии, поэтому обделить вниманием метод этот метод делегата не получиться, на это нам намекает и сам протокол делегата менеджера — метод для отслеживания состояния единственный помечен как обязательный.

Безопасность

Как уже упоминалось не раз, в BLE вопрос энергоэффективности всегда ставился в первый ряд. Поэтому, по-умолчанию весь трафик между устройствами не шифруется. Экономятся и ресурсы ЦП и байты в пакетах. Такой трафик может быть легко перехвачен или даже подменен злоумышленником. Для большинства прикладных задач, которые инжинеры решают с помощью BLE, такой вариант вполне подходит. Ведь нет ничего страшного, если кто-то перехватит температуру вашего термометра или заряд батареи. Но есть и случаи, когда важно защитить приватные данные от чтения злоумышленником или быть уверенным что запись происходит от доверенного источника.

В случае, когда требуется дополнительная защита, можно установить соотвествующие права на характеристику. При попытке чтения или записи такой характиристики сервер отклонит процесс, сообщив, что общение должно быть зашифровано, ответив на запрос ошибкой Insufficient Authetication. Чтобы продолжить, клиенту нужно создать защищенное соединение, которые мы чаще привыкли называть пейрингом, реже — бондингом.

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

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





Пермишенны

Говоря о безопасности, стоит упомянуть о том, что с недавних пор Apple требует указания в Info.plist текста с объяснением для конечного пользователя для чего вашему приложению нужен доступ к Bluetooth. При первой попытке сканирования или начала отправки рекламных пакетов, система спросит пользователя разрешает ли он использовать Bluetooth, точно так же, как это обычно происходит при запросе прав на фотогалерею или пуш уведомления. У соседей по платформе, в Андроид, система также требует прав на локацию низкой точности, т.к. потенциально можно сканирование можно использовать для определения положения пользователя. Возможно, такие правила не за горами и в iOS.

Переподключение

При работе с Bluetooth, как и с любой другой технологией связи, случаются разрывы соединения. Разрыв может быть вызван как по инициативе одной из сторон, так и при потере сигнала. Фреймворк CoreBluetooth устроен таким образом, что пытается постоянно поддерживать соединение до тех пор, пока не будет вызван метод cancelConnection(from:). Таким образом, если связь прервалась по причине неустойчивого соединения, или же при выключении удаленного устройства, CoreBluetooth будет пытаться восстановить соединение в фоновом режиме пока запущено ваше приложение. Он самостоятельно будет сканировать эфир, и как только появиться возможность соединиться сделает это. Кажется довольно простой вещью, но поверьте — вам не захочется писать этот алгоритм с нуля. Коллеги по платформе, опять же не имеют такого механизма из коробки и команде iOS сэкономил несколько дней обсуждений и имплементации.

В примере выше рассмотрели кейс, где дисконнект был неожиданным для нас явлением. Но стоит рассмотреть переподключение как естественный процесс жизненного цикла. Допустим, поработав с устройством пару минут, мы узнали от него всё что хотели на данный момент, и не хотим больше тратить ресурсы на поддержание соединения. В этом случае мы можем сохранить уникальный UUID переферийного устройства и вызвать метод cancelPeripheralConnection(). Позже, когда нам снова потребуются какие-то данные от утройства, мы можем использовать сохраненный UUID для подключения. Подсвечу тот факт, что метод connect принимает на вход параметр экземпляр класса CBPeripheral, который не имеет публичного конструктора. Для получения экземплара у нас есть метод retrievePeripherals(withIdentifiers:) у CBCentralManager, которому можно скормить массив UUID.


⚠️

Важно, что метод работет уже не CBUUID, а с UUID из Foundation библиотеки, т.к. это уже платформенная реализация и не имеет референса в спецификации CoreBluetooth. После маппинга UUID в массив CBPerepheral можно к ним подключаться.


Тут может образоваться вопрос, как быть, если искомое устройство не в зоне видимости Bluetooth или же ушло в сон на какое-то время. В этом случае... ничего не произойдет. Мы не получим ошибки таймаута или какой-то другой. CoreBluetooth будет пытаться подключиться к устройству бесконечно, пока вы не вызовете cancelPeripheralConnection(:). Такая логика может показаться странной, действительно, зачем мне бесконечно подключаться к термометру, если он не в зоне видимости? Но не забываем, что сценариве использования очень много, и мы работаем с LE его версией. Это значит, что какие-то устройства by design могут выходить в эфир на довольно редкие промежутки времени для экономии батареи. Тогда такое поведение приобретает смысл. В конечном итоге, накинуть логику с таймаутом намного легче, чем написать цепочку переподключения, будь этот таймаут системным.

Работа в бекграунде

Как и все ресурсоёмкие операции в iOS работа с устройствами в бекграунде по умолчанию недоступна по умолчанию. При сворачивании приложения, будь ваше приложение сервером или клиентом потеряет соединение и как и все остальные перестанет обслуживать все очереди. Но по возращению в форграунд, CoreBluetooth восстановит соединение.

Автоматическое восстановление соединения это конечно хорошо, но явно недостаточно. Полноценно контролировать процесс взаимодействия с устройствами в фоне можно проставив соответствующий бекраунд мод в Info.plist.

Для каждой роли, сервера и клиента, есть отдельный чекбокс. Можно отметить оба. При активации бекграунд мода, приложение сможет взаимодействовать с устройствами, читать и писать характеристики, сканировать сеть и т.д., но конечно же процесс этот не вечный и как это происходит с почти с любым приложением, рано или поздно iOS может выгрузить из памяти приложение. С этим ничего не поделать и придется с этим жить. Хорошая новость в том, что по возвращению в приложение, в CoreBluetooth есть возможность восстановить состояние, со всеми подключенными устройствами, найденными характеристиками и т.д. Всё выше сказанное распространяется на случаи, когда приложение было выгружено по просьбе системы, а не закрыто пользователем из таск-менеджера.

Еще раз напомню, что Apple серьезно позаботилась об энергопотреблении, поэтому процессы в бекграунде будут медленнее, например, сканирование будет происходить дискретно и менее интенсивно.

Важно отметить, что при реализации соединения между двумя iPhone, iPad или Mac без соответствующего бекграунд-мода, у серверной части уход в бекграунд будет сопровождаться отключением зарегистрированных сервисов, а не завершением Bluetooth соединения. То есть удаленная сторона (клиент) не получит события отключения, но получит уведомление didModifyServices о том, что часть сервисов у Peripheral устройства пропало. Помните, в этом случае вы подключены к одному физическому устройству — другому телефону, планшету или компьютеру, а не программе как в случае HTTP взаимодействия.

L2CAP соединение

В 2017 году Apple представила разработчикам возможность коммуницировать с Apple устройствами не только через GATT протокол, но и открывать своё низкоуровневое соединение через низлежащий протокол L2CAP. Это позволяет написать свою реализацию взаимодействия по радиоканалу, минуя ограничения характеристик GATT. Это, конечно, сложнее в реализации, чем читать и записывать характеристики, но всё равно достаточно просто.

Со стороны сервера, мы всё так же, должны просканировать эфир, найти и подключиться к устройству. Если устройство поддерживает такой тип соединения, оно будет содержать специальный сервис с характеристикой содержащей ID канала. Для начала обмена информацией нужно считать этот ID и попросить CBPeripheral открыть канал методом openL2CAPChannel().

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

После успешного соделинения, делегаты соотвествующих сторон процесс получат уведомление didOpenL2CAPChannel с указанием канала. Сам канал имеет два свойства inputStream и outputStream которые позволят реализовать и чтение и запись данных по каналу используя стандартную для iOS / Mac абстракцию потоков.

Грабли

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

Кеширование

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

На одном из проектов, потребовалось написать симулятор железного устройтва, чтобы QA могли тестировать взаимодействие на всякие краевые кейсы, которые с реальным девайсом не воспроизвести, или воспроизведение было кране долгим. При написании серверной части, мы можем изменить имя нашего устройства в рекламном пакете и надеятся что мы увидим измененное имя. Если бы мы были единственными пользователями Bluetooth на устройстве, всё бы было замечательно: указали имя в реалмном пакете и именно его увидит клиент. Но Bluetooth на устройстве использует и ОС, часто отправляя рекламные пакеты с именем устройства из системных настроек. Например для быстрого обнаружения устройства сервисом AirDrop. Если устройство, где вы планируете тестировать вашу клиентскую часть уже поймала хоть один рекламный пакет, то имя закешируется и при попытке чтения имени CBPeripheral мы увидим именно это закешированное имя, а не то, которое указали в настройках рекламного пакета.

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

Версионирование

Очень короткая “грабля” где я призываю вас предусмотреть характеристику с версией ПО. Если вы работаете с отдельным устройством такой привелегии может и не быть, но если реализуете клиент-серверное взаимодействие между Apple устройствами, то не поленитесь это сделать. Это уменьшит количество костылей в коде, когда вы будете догадываться о версии устройства или ПО по наличию/отсутствию того или иного сервиса или характеристики. Такое часто приходиться делать чтобы определить поддерживаемые функции на периферийном устройстве.

Хардварные проблемы

Готовьтесь что всё пойдет не так, как вы предполагали. Разработчики железа — тоже разработчики, у которых тоже случаются и баги и креши, но только релизный цикл обычно у них намного длиньше, особенно, когда у вас ограничены коммуникации с ними. Будьте готовы, что кто-то отойдет от стандартов. Например, не будет включать в рекламный пакет информацию о сервисах.

Так, я в самом начале моего пути на проекте с BLE я столкнулся с такой проблемой. Написал и отладил клиентскую сторону по документации, проверил с написанным симулятором, все отлично работает. Когда через месяц пришел реальный девайс — его просто нельзя было найти. Я вижу что он есть в эфире, а уведомления в делегат я не вижу. Несолько дней переписок и по счастливой случайности я методом перебора понимаю, что если отключить фильтрацию по UUID сервисов всё работает. Пришлось фильтровать по имени, а имя иногда менялось, и тут вспоминаем проблему с кешированием. Короче, весело.

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

Share
Send
Pin
2021