Files
utun2/doc/ble_transport.txt
T
evgeny ba1c4cf160 Add cross-platform BLE transport and Bluetooth settings
Integrate shared C transport with desktop and Android frontends, add radio probes and detailed Windows diagnostics, and fix GUI build dependencies and early configuration startup errors.
2026-10-29 10:51:19 +01:00

228 lines
20 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
BLE транспорт VibeChat (lib/ble)
Область модуля
---------------
Независимая от Qt библиотека предоставляет manufacturer-броадкасты и адресные
двусторонние GATT-сессии. Один локальный GATT-сервис обслуживает до 16 соединений.
Броадкаст — повторяемое ОС объявление, а не гарантированно доставляемая датаграмма.
Общий codec ble_identity.h: VC (2 байта), версия 1 (1), node_id BE64 (8),
имя UTF-8 (0..13). Имя сокращается по границе символа. Всего 11..24 байта.
Приглашения и автоматический выбор соседей — следующий слой; транспорт не запускает JOIN/ETCP.
Node ID передаётся в HELLO для сопоставления сессий; подлинность он не доказывает.
Публичный API: lib/ble/ble_transport.h.
Платформенный контракт: lib/ble/ble_backend.h.
Desktop GUI запускает библиотеку по настройкам «Bluetooth» (по умолчанию выключена).
Потоки и время жизни
-------------------
Все функции и callbacks выполняются в одном переданном uasync-потоке.
Из GUI отправлять команды через существующий gui_bridge_post_bound_fn/owned_fn.
Остановить BLE до остановки/уничтожения этого uasync; из callbacks запрещены
stop/destroy/disconnect (отложить действие в uasync). send/read допустимы.
Входящие аргументы callbacks заимствованы до возврата; endpoint можно скопировать.
ID соединения монотонен в пределах handle transport. Старый ID не переиспользуется
после stop/start; после destroy ни один ID/указатель этого transport не действителен.
Порядок использования
--------------------
1. ble_create(ua, config): задать ненулевой node_id, manufacturer company_id,
callbacks и context. Linux adapter=NULL выбирает первый адаптер, иначе hciN.
Windows использует системный адаптер, adapter должен быть NULL.
2. ble_start(): асинхронная регистрация сервиса и scanning; ждать status(BLE_READY).
3. ble_advertise(data,len): 0..24 произвольных байта; NULL,0 выключает объявление.
company_id должен быть одинаковым у участников. 0xffff — только эксперимент.
advertising(READY) означает регистрацию/запуск ОС, не получение соседом.
Обновления могут объединяться до последнего значения; точный период не задаётся.
4. broadcast callback получает endpoint, bytes и RSSI (127 = отсутствует).
ble_connect(endpoint, expected_node) самостоятельно выполняет GATT discovery,
подписку и HELLO. expected_node=0 принимает любой ненулевой ID кроме своего.
5. validate вызывается для полученного HELLO/ACCEPT до допуска кадров. NULL
разрешает всех; ненулевой результат callback разрешает конкретный запрос.
connection(READY) сообщает согласованную сессию. GATT/ACL само по себе не READY.
6. Для отправки выделить ll_alloc_lldgram(length), заполнить dgram и len,
вызвать ble_tx_put. При успехе entry принадлежит transport, иначе остаётся
вызывающему. WOULD_BLOCK: сохранить entry и ждать writable, затем повторить.
7. received уведомляет о целых кадрах. ble_rx_get извлекает ll_entry;
после обработки освободить queue_dgram_free(entry), queue_entry_free(entry).
8. ble_disconnect закрывает свою сессию. ble_destroy выполняет stop и освобождение.
Память и backpressure
--------------------
Для каждого link: ll_queue TX/RX до 64 кадров, один текущий TX и один RX-буфер
сборки. Текущий TX входит в лимит 64. writable выдаётся после снижения TX ниже
32 кадров, если производитель ранее получил WOULD_BLOCK. Не обращаться к
внутренним очередям напрямую: API сохраняет правила владения при переполнении.
RX при заполнении отбрасывает целый новый кадр с WARN; старые кадры сохраняются.
Кадр означает непрозрачный буфер длиной 1..1500 байт ВКЛЮЧАЯ Ethernet-заголовок,
если он присутствует. Это не Ethernet payload MTU 1500 (которому нужно >=1514).
На link не больше одной незавершённой GATT-передачи. Она хранит свой fragment
до завершения ОС. Очередь ядра/Qt не используется как неограниченный TX-буфер.
Переподключение не переносит старые кадры и не повторяет неоднозначные отправки.
Wire protocol
-------------
UUID service: f61a9020-4abc-45ac-98e1-761df4c13811
UUID RX: f61a9021-4abc-45ac-98e1-761df4c13811 (Write)
UUID TX: f61a9022-4abc-45ac-98e1-761df4c13811 (Indicate)
RX/TX названы относительно сервера; API одинаков для обеих ролей.
UUID определяют эту версию. Это отдельный протокол от Android BLE test v2;
автоматическую совместимость с tools/ble-test-android он не заявляет.
Все многобайтные целые big-endian, без выравнивания C-структур.
HELLO: [type=1:1][source_node:8][target_node:8], Central -> Peripheral.
ACCEPT: [type=2:1][source_node:8][target_node:8], Peripheral -> Central.
DATA: [type=3:1][frame_id:2][total_len:2][offset:2][payload:1..].
frame_id увеличивается modulo 65536 отдельно для каждого направления.
Размер значения <= min(ATT_MTU-3,512), минимальный ATT MTU=23.
Нулевой offset начинает новый кадр и заменяет незавершённую предыдущую сборку.
Далее обязательны тот же frame_id/total_len и offset==число принятых байтов.
Пропуск, неверная длина, новое начало или 15-секундный таймаут сбрасывают сборку;
в RX помещается только целый кадр. Собственных ACK/ретрансляций/окна протокола нет.
Write Request и Indication используют подтверждения стека. Это не подтверждение
чтения кадра приложением и не защита от заполнения его RX.
Linux
-----
BlueZ D-Bus, клиент sd-bus из libsystemd; зависимость от Qt отсутствует.
Собственная приватная D-Bus connection: закрытие освобождает registrations и scan.
Основной процесс ОС не обязан быть systemd, но нужны системный D-Bus и bluetoothd.
Connectable-объявление UUID и пользовательский manufacturer-броадкаст занимают
два advertising instances при одновременном включении. Оба connectable:
адрес manufacturer-маяка должен позволять GATT connect (отдельный broadcast
instance может иметь другой random address). Нехватка ресурса — ошибка.
Входящий WriteValue содержит device; серверный AcquireNotify(device,mtu) получает
отдельный SOCK_SEQPACKET socketpair для каждого клиента. В fd пишется indication;
байт 1 в обратную сторону подтверждает её ATT completion. EAGAIN ждёт writable.
Value/Confirm без адреса клиента для серверной передачи не используются.
Выходящий клиент: WriteValue(type=request) и StartNotify для indicate-only TX.
Не вызывается глобальный Device1.Disconnect: ACL может обслуживать другие сервисы.
Проверено с BlueZ 5.85; требуется серверный AcquireNotify с indication completion.
Windows
-------
WinRT BluetoothLEAdvertisementWatcher/Publisher и GattServiceProvider.
GATT-клиент использует WriteValueWithResultAsync, сервер — адресный
NotifyValueAsync(value, subscribedClient) для indicate-only характеристики.
Результаты WinRT сериализуются в uasync через bounded mailbox (256 событий).
Опоздавшие callbacks используют weak ownership и ID, закрытые handles не трогают.
Перед остановкой uasync снять источники событий, отменить операции и posted_task.
WinRT может сохранять физический ACL для других сервисов. Гарантия close относится
к нашей сессии и ресурсам; принудительный разрыв общего ACL в API не обещается.
Нужен адаптер с central+peripheral и Windows с GATT server API/BytesSent
(Windows 10 1709+); версия ОС сама по себе поддержку драйвером не гарантирует.
Радио Windows необходимо проверить на реальном Windows-ПК; Wine этого не заменяет.
Сборка и проверка
----------------
Linux: пакет libsystemd-dev и dbus-daemon (dbus-run-session).
MSYS2 UCRT64: mingw-w64-ucrt-x86_64-cppwinrt; C++20, windowsapp.
cmake -S lib/ble -B /tmp/utun-ble-build -DCMAKE_BUILD_TYPE=Debug
cmake --build /tmp/utun-ble-build -j4
ctest --test-dir /tmp/utun-ble-build --output-on-failure
test_ble_transport: реальные common transports с управляемым backend; MTU
23/247/517, duplex, пропущенный фрагмент, TX/RX full, несколько пиров,
отказ validate и уничтожение во время отправки; проверяется учёт памяти.
test_ble_bluez: отдельный dbus-run-session с подставным BlueZ; настоящее
экспортирование сервиса/объявлений, два AcquireNotify fd, адресные подтверждения,
приём кадров и независимое закрытие. К системному Bluetooth тест не обращается.
ble_probe 3: ручная проверка реального радио (scan, GATT registration, публикация
и замена тестового manufacturer-объявления); не подключается к чужим устройствам.
Логи: discovery=debug. INFO — состояния; DEBUG — очереди и целые кадры;
WARN/ERROR — причины отброса/отказа. Содержимое кадров/объявлений не выводится.
Отладка Bluetooth на Windows
---------------------------
В существующей секции [debug] файла vibechat.cfg установить discovery=debug;
общий [gui] debug_level может оставаться info. Журнал задаёт [gui] debug_file
(по умолчанию chatgui.log рядом с конфигом). После изменения фильтра перезапустить GUI.
Ранние ошибки подготовки INI всегда пишутся в chatgui.log до чтения настроек лога;
их точные причины также показаны в окне ошибки независимо от фильтра.
DEBUG: адаптер (BLE, central/peripheral, extended advertising, поддержка 2M/Coded),
этап/результат каждой служебной async-операции, состояния рекламы и сканирования,
адрес и роль соединения, список UUID/свойств характеристик, CCCD, MTU/PDU,
целые TX/RX-пакеты и backpressure, закрытие с pending-состоянием и очистка mailbox.
HRESULT записывается с названием операции и текстом Windows; BluetoothError —
числом и именем, GATT status — числом, ATT protocol error — числом либо -1 (нет).
GATT status: 0=Success, 1=Unreachable, 2=ProtocolError, 3=AccessDenied.
PHY_2M_supported — возможность адаптера, НЕ измеренная скорость соединения;
-1 у необязательных возможностей означает отсутствие API в этой версии Windows.
Для каждого отдельного ATT-фрагмента временно использовать discovery=trace.
На DEBUG фрагменты/каждое повторное радиообъявление не засоряют журнал.
Desktop GUI
-----------
Настройки → Bluetooth: включить транспорт, рекламировать ID/имя, принимать входящие.
В [gui] bluetooth_flags: bit 0 — транспорт, bit 1 — объявление, bit 2 — входящие;
по умолчанию 0. Save сохраняет флаги и будит uasync, без перезапуска ядра.
Системный переключатель Bluetooth пользователь включает в ОС.
DesktopBle создаётся/уничтожается в runLoop UtunNode и использует общий C API.
GUI читает копию состояния; список обнаружения ограничен 64 узлами с TTL 30 секунд.
Изменение имени обновляет объявление. Входящая валидация пока разрешает всех.
UtunNode::bluetoothTransport() — заимствованный handle ТОЛЬКО для uasync-потока;
кадры остаются в RX-очередях до чтения потребителем. Автоматических JOIN/ETCP нет.
Ручная проверка с Android BlePeerProbe initiator=true: build/ble_desktop_probe.
Проба проверяет готовность радио/объявления, обнаружение и входящую GATT-сессию,
затем останавливает transport до уничтожения uasync.
Android
-------
Общие ble_transport.c/ble_identity.c компилируются в libutun.so; android_ble.c
реализует ble_backend.h через BleRadio.kt (Android BluetoothGatt/Server/LE).
Данные и callbacks core — в instance_lite uasync. Android callbacks сериализованы
HandlerThread, JNI mailbox ограничен 256 событиями, каждое несёт generation.
eventfd немедленно будит uasync при новом событии; polling-таймера между фрагментами нет.
Переполнение mailbox останавливает radio с ошибкой. На соединение не более одной
ожидающей отправки; сервер indications обслуживает адресно, по очереди.
Политика входящих ble_set_incoming закрывает только входящие сессии.
Настройки используют DataStore; Bluetooth off останавливает только радио приложения.
Android 12+: SCAN/CONNECT/ADVERTISE runtime permissions; системное включение BT
через ACTION_REQUEST_ENABLE. На Android 8–11 сканирование требует геопозицию.
Длительная работа принадлежит существующему HeadlessService (connectedDevice).
Перед teardown worker вызывает android_ble_detach, старые callbacks отбрасываются.
android_ble_transport() возвращает общий C handle только для использования в worker.
В обычном режиме нет автоматического connect/echo или подключения пакетов к ETCP.
Физическая проверка
-------------------
./gradlew assembleDebug assembleDebugAndroidTest (в tools/chatgui-android)
adb install -r app/build/outputs/apk/debug/app-debug.apk
adb install -r app/build/outputs/apk/androidTest/debug/app-debug-androidTest.apk
Выдать приложению разрешения Bluetooth, включить системный Bluetooth.
Запустить одновременно:
adb shell am instrument -w -e seconds 90 -e initiator false \
com.utun.chat.test/com.utun.chat.BlePeerProbe
/tmp/utun-ble-build/ble_peer_probe connect 75
Обратные роли: initiator=true и ble_peer_probe listen 75.
Instrumentation временно включает echo, возвращает сохранённую политику после
теста. Успех определяет desktop RESULT ok=1: 96 пакетов размеров
1/13/14/20/244/512/1499/1500, побайтовая сверка, TX WOULD_BLOCK и writable.
Не запускать этот тест одновременно с обычным использованием приложения.
Профиль скорости Android
------------------------
После подключения client просит CONNECTION_PRIORITY_HIGH и ATT MTU 517.
Обе роли запрашивают PHY_LE_2M для TX/RX при поддержке локального контроллера.
Запрос не гарантирует смену PHY: onPhyUpdate/onPhyRead логируют фактические TX/RX.
При отсутствии поддержки остаётся согласованный PHY. S2/S8 — параметры только
LE Coded PHY (дальность/скорость), с выбранным 2M применяется NO_PREFERRED.
У Android BluetoothGattServer нет public requestConnectionPriority; интервал
для входящего подключения согласует central. Сам UUID/advertising остаётся legacy
1M для обнаружения; PHY 2M относится к установленному соединению.
Проверка 2026-10-29
------------------
Физический Q8 DA6B8P01, Android 14 (API 34), Linux BlueZ на этом ПК.
Обе роли подключения: RESULT ok=1, sent=96, verified=96, blocked=1, discovered=1.
Каждое направление: 45636 байт с побайтовой сверкой; пакеты до 1500 включительно.
После профиля скорости: desktop central 19.801 с, Android central HIGH 9.977 с.
Это elapsed теста с подтверждениями/echo, не измерение предельной канальной скорости.
Q8 сообщает isLe2MPhySupported=false; readPhy возвращает status=6. PHY 2M
реализован как запрос, его фактическое согласование на этом телефоне не проверено.
Общий C codec/transport и BlueZ mock: ASAN+UBSAN, leak detection — 3/3 PASS.
Сборки Android arm64-v8a/x86_64, desktop GUI Linux и WinRT cross-build проходят.
Физический Windows radio и несколько одновременных Android-клиентов не проверены.