Проектирование BLE GATT: UUID, форматы данных, уведомления и совместимость версий
Соединение BLE может успешно установиться, а интеграция продукта при этом не работать. Приложение может обнаружить не ту характеристику, неверно декодировать знаковое измерение или перестать получать обновления после смены прошивки. Для рабочего интерфейса GATT нужен письменный контракт, описывающий идентификацию, байты, семантику доставки, безопасность и изменения версий. В этом руководстве такой контракт рассматривается на примере собственного интерфейса датчиков и управления.
Определите интерфейс до разработки приложения
Для каждой службы и характеристики укажите UUID, свойства, разрешения, максимальную длину и ответы об ошибках. Используйте службу Bluetooth SIG только тогда, когда её установленный смысл и формат соответствуют продукту. Для собственной семантики назначьте постоянный 128-битный UUID, а не выдумывайте нераспределённый короткий UUID. Спецификация типов данных SIG разрешает короткие UUID служб только для значений, назначенных SIG.
Храните UUID в едином реестре под контролем версий, общем для прошивки, приложения и средств тестирования. Не используйте имя устройства как единственный идентификатор: имена могут меняться, а несколько устройств могут объявлять одно имя. Аналогично, не фиксируйте дескрипторы атрибутов в коде для всех версий прошивки. Найдите службу, выберите нужный экземпляр и определите характеристики внутри него.
| Характеристика | Рекомендуемая операция | Что зафиксировать в контракте |
|---|---|---|
| Информация о протоколе | Чтение | Основная и младшая версии формата, возможности и максимальный кадр приложения |
| Поток измерений | Уведомление | Единицы, счётчик последовательности, опорное время и политика переполнения |
| Конфигурация | Чтение и запись с ответом | Диапазоны, авторизация, проверка и момент сохранения в энергонезависимой памяти |
| Результат команды | Indication или уведомление с подтверждением приложения | Идентификатор транзакции, код результата и смысл завершения |
Задайте проверяемый байтовый формат
Ниже приведён пример 12-байтового кадра измерения, а не стандартный профиль Bluetooth. Все многобайтовые поля используют порядок little-endian. Температура с фиксированной точкой позволяет не зависеть от сериализации чисел с плавающей точкой в конкретном языке. Отдельное чтение информации о протоколе сообщает младшую версию и поддерживаемые возможности.
| Смещение | Поле | Определение |
|---|---|---|
| 0 | major | 8-битная основная версия без знака, изначально 1 |
| 1 | flags | Бит 0: температура действительна; остальные зарезервированы и равны нулю |
| 2–3 | sequence | 16-битный счётчик без знака, по модулю 65536 |
| 4–7 | uptime_ms | 32-битное время отсчёта после загрузки без знака; циклический переход по модулю 2³² |
| 8–9 | temperature | 16 бит со знаком, 0,01 °C на единицу; игнорируется при недействительном значении |
| 10–11 | battery_mV | 16-битное напряжение в милливольтах без знака; 65535 означает недоступность |
В этом примере последовательность и время работы сбрасываются при перезагрузке. Идентификатор загрузки, считываемый при установлении сеанса, позволяет отличить перезапуск от циклического переполнения счётчика. Отрицательная температура, недопустимые флаги, усечённые кадры и переполнение должны входить в общие эталонные тестовые векторы. Этот декодер Python демонстрирует явную проверку:
import struct
def decode_v1(payload):
if len(payload) != 12:
raise ValueError("expected 12 bytes")
major, flags, seq, ms, temp, mv = struct.unpack("<BBHIhH", payload)
if major != 1 or flags & 0xFE:
raise ValueError("unsupported schema or flags")
return {
"sequence": seq, "uptime_ms": ms,
"temperature_C": temp / 100 if flags & 1 else None,
"battery_mV": None if mv == 65535 else mv,
}
Эталонный вектор 01 01 2A 00 E8 03 00 00 2E FB E4 0C означает: основная версия 1, температура действительна, номер 42, время работы 1000 мс, −12,34 °C и 3300 мВ. Декодируйте его на обеих сторонах до подключения реального оборудования.
Строгая проверка зарезервированных битов означает, что этот декодер не может молча принять новое значение флагов. Сохраните кадр v1, согласуйте поддерживаемое расширение или введите новую основную версию формата. Добавление байтов в конец совместимо со старыми версиями только тогда, когда существующий анализатор специально спроектирован для их приёма.

Разделяйте подписку и надёжную доставку на уровне приложения
Уведомления Notifications не имеют подтверждения ATT, а сообщения Indications имеют. Ни то ни другое не доказывает, что измерение попало в облачную базу или двигатель выполнил команду. Соединённый канал BLE имеет собственные механизмы повторной передачи, однако разрывы соединения, заполненные программные очереди и перезапуски приложения всё равно требуют сквозной политики. Используйте номера последовательности для обнаружения пропущенных отсчётов, а идентификаторы транзакций — для сопоставления результатов команд. Базовые процедуры определены в спецификации ATT.
Оформляйте подписку через API платформы и проверяйте её завершение до объявления потока готовым. На уровне GATT дескриптор Client Characteristic Configuration с UUID 0x2902 включает Notifications значением 0x0001, а Indications — значением 0x0002. Состояние подписки индивидуально для каждого клиента. Сохранение состояния различается для устройств с сохранённой связью безопасности и без неё, поэтому при переподключении нужно восстановить локальный обработчик и убедиться в активности подписки. См. спецификацию GATT.
Ограничьте очередь и документируйте реакцию на перегрузку: удалять самые старые отсчёты, сохранять только последнее значение или приостанавливать сбор, если продукт это допускает. Предоставьте счётчик отброшенных отсчётов. Для команд различайте состояния «принята», «выполняется» и «завершена». Повтор одной транзакции после разрыва не должен случайно дважды приводить исполнительный механизм в действие.
Явно учитывайте MTU и различия платформ
В обычном уведомлении с одним дескриптором значение должно укладываться в ATT_MTU минус 3 байта; отдельно действует предел значения атрибута 512 байт. Таким образом, стандартная LE ATT MTU, равная 23, оставляет 20 байт для такого уведомления. Более длинные значения требуют согласованной фрагментации на уровне приложения или другого подходящего способа передачи. Увеличение MTU само по себе не выбирает более быстрый PHY и не увеличивает длину данных канального уровня. См. форматы пакетов ATT.
Начиная с Android 14, первый запрос MTU от клиента GATT заставляет стек запросить 517; последующие запросы игнорируются. Размер кадра определяйте по согласованному значению из обратного вызова, а не по запрошенному числу. На платформах Apple используйте maximumWriteValueLength(for:) для выбранного типа записи вместо переноса предположений с Android. Сверяйтесь со справочником Android BluetoothGatt и справочником Apple по длине записи.
Выполняйте асинхронные этапы настройки последовательно, если выбранный стек явно не поддерживает требуемую параллельность. Успешный вызов часто означает лишь постановку запроса в очередь; результат сообщает обратный вызов. Совместно журналируйте согласованную MTU, UUID выбранной характеристики, состояние безопасности, результат подписки и версию анализатора.
Планируйте совместимость версий прошивки и приложения
Версия формата протокола, версия прошивки и структура базы GATT — разные вопросы. В рамках поддерживаемого формата сохраняйте смысл существующих полей. Явно сообщайте возможности вместо того, чтобы заставлять клиентов угадывать функции по строке версии прошивки. До отправки управляющих команд отклоняйте неподдерживаемую основную версию с полезной диагностикой.
Если обновление меняет базу служб, реализуйте применимое поведение Service Changed и правила кэширования. При наличии Database Hash он помогает выявлять изменения. Эти механизмы не преобразуют изменившиеся форматы данных приложения. Проверяйте обновление и откат как с ранее связанными клиентами, так и с чистыми установками. Требования к базе объясняют правила кэширования GATT.
Используйте матрицу проверок, выявляющую отказы
| Тест | Провоцируемый отказ | Сохраняемое свидетельство |
|---|---|---|
| Старое приложение с новой прошивкой; новое приложение со старой прошивкой | Неизвестная основная версия, отсутствующее необязательное поле или изменённая структура | Чтение возможностей и явное принятие либо отклонение |
| MTU 23 и большая согласованная MTU | Слишком длинная нагрузка, неверная длина фрагмента или усечение | Принятая длина и декодированный эталонный вектор |
| Разрыв во время команды и подписки | Повторное действие или молчащий поток | Идентификатор транзакции, завершение подписки и первый действительный отсчёт |
| Обновление и откат с сохранением связей безопасности | Устаревший кэш дескрипторов или обращение к другой характеристике | Переход поиска/кэша и соответствие UUID |
| Медленный потребитель и полная очередь | Рост памяти или необъяснимые потери | Пиковое заполнение очереди и учёт отброшенных данных |
| Клиент без аутентификации или авторизации | Конфигурация принята при неподходящем уровне безопасности | Ожидаемая ошибка и неизменная конфигурация |
Если поток молчит, последовательно проверьте свойство Notify, завершение подписки, разрешения и активность источника данных. Если значения неправдоподобны, сопоставьте исходные байты со знаковостью, масштабом и порядком байтов до настройки радиопараметров. Если сбой возникает только на обновлённых устройствах, сначала проверьте кэш базы и согласование формата.
Подготовьте проверяемое техническое задание на интеграцию
Для анализа прошивки подготовьте реестр UUID, спецификацию пакетов, эталонные векторы, перечень поддерживаемых телефонов и ОС и матрицу совместимости. Подходящей отправной точкой служит услуга диагностики прошивки и BSP Obeita. описание выполненного проекта интеграции радиомодуля WS63 иллюстрирует определение аппаратной части и радиорежима; она не подтверждает, что приведённый пример контракта GATT реализован или проверен на этом модуле.