Данный проект — это собственная реализация нестандартного BLE-сервиса (Custom Service) и протокола обмена данными между:
- Central (клиент) — Android-приложение, которое сканирует, подключается, инициирует сопряжение и обменивается данными;
- Peripheral (сервер) — Android-приложение, которое поднимает GATT-сервер, запускает рекламу и обслуживает подключение Central.
Цель работы: разработать комплекс из двух программ для связи по BLE между Android и любым другим устройством (Android/Windows/Linux), где:
- на Android должно быть хост-приложение, к которому выполняется подключение по BLE;
- на другой стороне имитируется BLE-устройство, которое подключается к хосту и осуществляет приём-передачу данных.
В рамках данного проекта реализовано:
- Central-приложение под Android, которое:
- сканирует устройства и находит Peripheral по UUID сервиса;
- выполняет сопряжение;
- подключается к GATT-серверу и инициализирует соединение;
- умеет отправлять команды (например,
Ping) и получать ответ (Pong) через уведомления; - умеет отправлять поток данных Central → Peripheral блоками фиксированного размера.
- Peripheral-приложение под Android, которое:
- запускает рекламу (Advertising) с UUID сервиса;
- поднимает GATT-сервер с характеристиками команд и данных;
- принимает команды и данные от Central;
- отправляет уведомления Peripheral → Central (ответы на команды, а также поток данных).
При необходимости заменить Peripheral-часть на ПК (под Linux/Windows) — протокол и UUID уже выделены в shared модуль, их можно переиспользовать.
- Kotlin
- Android SDK Bluetooth (BLE) — встроенный API Android для работы с Bluetooth Low Energy:
- Сканирование (Central):
BluetoothAdapter,BluetoothLeScanner,ScanCallback,ScanFilter,ScanSettings,ScanResult - GATT-клиент (Central):
BluetoothDevice,BluetoothGatt,BluetoothGattCallback,BluetoothGattCharacteristic,BluetoothGattDescriptor,BluetoothProfile - GATT-сервер (Peripheral):
BluetoothManager,BluetoothGattServer,BluetoothGattServerCallback,BluetoothGattService,BluetoothGattCharacteristic,BluetoothGattDescriptor,BluetoothDevice - Advertising (Peripheral):
BluetoothLeAdvertiser,AdvertiseSettings,AdvertiseData,AdvertiseCallback,ParcelUuid
- Сканирование (Central):
- Jetpack Compose (Material 3)
- Coroutines + Flow
StateFlow— для состояния (подключение, работа сервера)SharedFlow— для событий и логов
- Clean Architecture: разделение на слои по смыслу (Presentation → Data → Domain)
- MVVM: ViewModel + State + Events
- внедрение зависимостей: Hilt
- Assisted Factory для
AndroidGattClientFactory, так как адрес устройства известен только во время работы
Модуль shared содержит общие для обеих реализаций вещи:
- UUID сервиса, характеристик (
BleUuids) - параметры протокола (
Protocol) - 160 байт каждые 60 мс - разрешения (
BlePermissions) - шлюз разрешений (
BlePermissionGate) - кодирование, декодирование команд (
CommandCodec)
Peripheral (сервер):
- Проверяет поддержку рекламы BLE (наличие
BluetoothLeAdvertiser). - Запускает GATT-сервер и добавляет сервис
BleUuids.SERVICE. - Запускает рекламу с UUID сервиса (UUID в рекламных данных).
- При подключении Central ведёт счётчики:
- сколько устройств подключено,
- кто подписался на уведомления
CMD_TXиDATA_TX.
Central (клиент):
- Сканирует BLE-устройства и фильтрует только те, у которых в рекламе есть
BleUuids.SERVICE. - После выбора устройства:
- выполняет сопряжение (bonding),
- подключается к GATT,
- делает
discoverServices(), - привязывается к характеристикам,
- запрашивает MTU (чтобы гарантированно влезали блоки данных, заданные в
Protocol), - включает уведомления на
CMD_TXиDATA_TX.
Проект состоит из двух приложений:
- Central (центральное устройство) — сканирует, подключается к Peripheral, отправляет команды и поток данных.
- Peripheral (периферийное устройство) — рекламирует сервис, поднимает GATT-сервер, принимает записи от Central и отправляет уведомления.
Архитектура слоистая:
- Интерфейс (Compose): показывает состояние и журнал, отправляет события в модель представления.
- Модель представления (ViewModel): преобразует нажатия кнопок в вызовы сценариев, подписывается на потоки состояния и журнал.
- Сценарии (UseCase): общая логика приложения, независимо от реализации Data слоя.
- Репозиторий: единая точка доступа к BLE-операциям и потокам данных.
- BLE-реализация (Android BLE API): сканер, GATT-клиент, GATT-сервер, реклама.
- UI → ViewModel:
UiEvent(нажатия кнопок). - ViewModel → UseCase →
BleRepository: операции сканирования, подключения, пинг, отключения, передачи. BleRepository→ UI:connectionState: StateFlow<ConnectionState>— текущее состояние соединения;logs: Flow<String>— строки журнала;notifications: Flow<BleNotification>— входящие уведомления от Peripheral.
Idle
├─(Scan)──────────────► Scanning ──(found/timeout)──► Idle
└─(Connect)───────────► Bonding ──(bond ok)─────────► Connecting
│
└─(bond fail)────────────► Error ─► Idle
Connecting ──(init ok)──► Ready
│
└─(init fail)──────► Error ─► Idle
Ready
├─(Disconnect)────────► Idle
└─(remote disconnect)─► Disconnected ─► Idle
Сервис содержит 4 ключевые характеристики:
- CMD_RX — куда Central пишет команду (write, с подтверждением)
- CMD_TX — откуда Peripheral шлёт уведомления о командах или ответах (notify)
Пример:
- Central пишет
PingвCMD_RX - Peripheral отвечает
Pongчерез уведомлениеCMD_TX
- DATA_RX — куда Central пишет блоки данных (write без подтверждения)
- DATA_TX — откуда Peripheral шлёт уведомления с блоками данных (notify)
Размер блока и период задаются в Protocol:
STREAM_BLOCK_SIZE— размер блока (160 байт)STREAM_PERIOD_MS— период отправки (60 мс)
Для потока данных важнее пропускная способность и стабильный период отправки, чем подтверждение каждого блока.
WRITE_TYPE_NO_RESPONSE:
- уменьшает объем служебного обмена на уровне BLE;
- снижает задержки из-за подтверждений;
- лучше подходит для частых небольших пакетов (поток).
- состояние соединения публикуется как
StateFlow<ConnectionState>для инициализации начальным состоянием и отображения актуального состояния - логи идут потоком строк (
Flow<String>) и в UI собираются в список с ограничением до 2000 строк - разрыв соединения ловится через событие
GattEvent.Disconnected, репозиторий закрывает GATT и переводит состояние вDisconnected/Idle
- состояние сервера публикуется как
StateFlow<PeripheralState> - логи публикуются через
PeripheralLogBus(в Logcat + в UI потоком)
| Роль | Устройство | Версия Android | Результат |
|---|---|---|---|
| Central | Google Pixel 6 | Android 16 | сканирование→сопряжение→подключение→ping→передача |
| Peripheral | Xiaomi Redmi 7 | Android 10 | реклама→GATT→уведомления→передача |
Примечания по совместимости:
- На Android 12+ (API 31+) нужны отдельные runtime-разрешения Bluetooth (
BLUETOOTH_*). - На Android 11 и ниже поведение по разрешениям другое (для сканирования часто требуется включенная геолокация на Central устройстве).
- Запускал также в противоположных ролях - все также работает
Проект реализует рабочую основу для Custom Service BLE:
- Полный цикл: реклама → сканирование → сопряжение → подключение → инициализация → обмен командами → поток данных → корректная обработка разрыва.
- Единая обработка разрешений (шлюз
BlePermissionGate+BlePermissions), чтобы действия не выполнялись без разрешений. - Реактивное состояние: UI всегда отображает актуальное состояние соединения.
- Отладка: потоки логов из BLE-слоя показываются в интерфейсе и в Logcat.
- Разделение ответственности: протокол и UUID вынесены в общий модуль и не прибиты гвоздями в приложениях.
- Android Studio (AGP 9+)
- два физических устройства Android 9+ (SDK 28+)
- включённый Bluetooth на обоих устройствах. Также при работе Central устройства на SDK<31 (Android 12<) требуется включенная геолокация
- Соберите и установите модуль Peripheral-приложения на устройство-сервер.
- Выдайте разрешения (на Android 12+):
BLUETOOTH_ADVERTISEBLUETOOTH_CONNECT
- Нажмите Start Server:
- запустится реклама и GATT-сервер;
- в логе появятся сообщения о старте.
- Соберите и установите модуль Central-приложения на устройство-клиент.
- Выдайте разрешения (на Android 12+):
BLUETOOTH_SCANBLUETOOTH_CONNECT- На Android 11 и ниже может понадобиться
ACCESS_FINE_LOCATIONдля сканирования - также включите геолокацию.
- Нажмите Scan (сканирование ограничено по времени - 5 секунд).
- Нажмите Connect и дождись
INIT OK. - Проверьте обмен командами:
- Ping → в логе Peripheral должен быть приём, а Central должен получить
Pongчерез уведомление.
- Ping → в логе Peripheral должен быть приём, а Central должен получить
- Проверьте поток данных:
- Central: Stream start — отправка данных Central → Peripheral
- Peripheral: Start TX — отправка данных Peripheral → Central. Также работает при отсуствии подписчика в лице Central устройства. При отключении и переподключении Central устройства Peripheral продолжает отправлять данные.
- Peripheral/Advertising не поддерживается
Устройство не умеет в рекламу BLE (нетBluetoothLeAdvertiser). Я простукивал оба устройства через BLE Scanner, когда каждое из них по очереди находилось с запущенным GATT-сервером в роли Peripheral. Может быть полезно в качестве проверки. - Bluetooth выключен
Peripheral требует включённый Bluetooth перед запуском. - Нет разрешений на Android 12+
Выдайте данные разрешения. - На Android 11 и ниже не видит устройства
Для сканирования может требоватьсяACCESS_FINE_LOCATION(и включённая геолокация на устройстве).