diff --git a/doc/ble_transport.txt b/doc/ble_transport.txt index 467cbd4e..50f838f3 100644 --- a/doc/ble_transport.txt +++ b/doc/ble_transport.txt @@ -278,6 +278,35 @@ BluetoothError записывается GATT status: 0=Success, 1=Unreachable, 2=ProtocolError, 3=AccessDenied. PHY_2M_supported — возможность адаптера, НЕ измеренная скорость соединения; -1 у необязательных возможностей означает отсутствие API в этой версии Windows. +При Waiting периодическая строка BLE WinRT radio snapshot читает непосредственно +Publisher.Status, GATT.AdvertisementStatus и Watcher.Status. publisher_event — последнее +обработанное событие; *_actual — состояние ОС сейчас, -1=объекта нет, -2=ошибка чтения +(рядом пишется HRESULT). Эти чтения не меняют состояния/соединения и не перезапускают радио. +payload_bytes — тело manufacturer data, manufacturer_ad_bytes включает ещё четыре байта +company ID, AD type и length; это не размер полного пакета, сформированного Windows. + +Windows: проверка конкуренции GATT и manufacturer publisher на реальном адаптере: + 1. Закрыть VibeChat и другие экземпляры probe на проверяемом ПК. + 2. После обычной сборки chatgui перейти в tools/chatgui/build и запустить: + .\ble_advertising_probe.exe + Либо собрать отдельно цель ble_advertising_probe в lib/ble (CMake, Windows). + 3. Приложить ble_advertising_probe.log из текущего каталога. +Утилита создаёт тот же GATT-сервис/характеристики, scan и 18-байтовое объявление probe18 +текущей wire-версии. Входящие подключения отклоняются: это тест обнаружения, не чат. +Три окна по 20 секунд: gatt_on, gatt_off, gatt_restored. Можно передать длительность +каждого окна 10..60 секунд единственным аргументом; startup ограничен ещё 20 секундами. +Publisher, payload и scan остаются теми же; меняется только реклама GATT-сервиса. +В result: количество наблюдений Started/Waiting при нужном состоянии GATT и работающем +scan, unsettled_samples (переходные состояния), задержка первого Started и конечные +состояния ОС. first_started_ms имеет смысл только при started_samples>0. +Waiting → Started при выключении GATT → Waiting при восстановлении указывает на +конкуренцию ресурсов. Если Waiting остаётся и без GATT, причина этим тестом не установлена. +Started подтверждает обслуживание публикации Windows, но не приём вторым устройством. +Код выхода 0 означает завершённое сравнение, а не успешную передачу во всех трёх окнах. +Ошибка/таймаут — 1, неправильные аргументы — 2. При завершении все объекты освобождаются. +Утилита собирается рядом с vibechat.exe, в пользовательский установщик не включается. +Объяснение приоритета GATT от Microsoft: +https://learn.microsoft.com/en-us/answers/questions/189891/windows10-uwp-app-conflict-ble-advertisement-and-g Для отдельных кадров, ATT-фрагментов и переходов backpressure использовать discovery=trace. WinRT TRACE содержит channel (0=DATA, 1=INVITE, 2=ACK), направление, длину и elapsed_us локальной отправки до её обработки в uasync; это время включает ожидание mailbox. diff --git a/lib/ble/tests/test_ble_winrt.cpp b/lib/ble/tests/test_ble_winrt.cpp index 322723f8..3bc83263 100644 --- a/lib/ble/tests/test_ble_winrt.cpp +++ b/lib/ble/tests/test_ble_winrt.cpp @@ -90,6 +90,56 @@ static void advertisement_addresses(UASYNC* ua) { DEBUG_INFO(CAT, "WinRT test PASS: 1809 events use unspecified type; 2004 events preserve public/random/unspecified"); } +struct TestPublisher : implements { + bool read_error = false; + BluetoothLEAdvertisementPublisherStatus Status() { + if (read_error) throw hresult_error(E_ACCESSDENIED, L"test publisher status denied"); + return BluetoothLEAdvertisementPublisherStatus::Started; + } + BluetoothLEAdvertisement Advertisement() { CHECK(false); return nullptr; } + void Start() { CHECK(false); } + void Stop() { CHECK(false); } + event_token StatusChanged(const TypedEventHandler&) { CHECK(false); return {}; } + void StatusChanged(event_token) noexcept { CHECK(false); } +}; +struct TestProvider : implements { + bool read_error = false; + GattLocalService Service() { CHECK(false); return nullptr; } + GattServiceProviderAdvertisementStatus AdvertisementStatus() { + if (read_error) throw hresult_error(E_ACCESSDENIED, L"test GATT status denied"); + return GattServiceProviderAdvertisementStatus::Stopped; + } + void StartAdvertising() { CHECK(false); } + void StartAdvertising(const GattServiceProviderAdvertisingParameters&) { CHECK(false); } + void StopAdvertising() { CHECK(false); } + event_token AdvertisementStatusChanged(const TypedEventHandler&) { CHECK(false); return {}; } + void AdvertisementStatusChanged(event_token) noexcept { CHECK(false); } +}; + +/* Снимок отличает ОС от задержанных событий; ошибка одного getter не мешает остальным и не останавливает radio. */ +static void radio_snapshot(UASYNC* ua) { + ble_transport owner; + auto s = make_state(ua, owner); + auto empty = s->radio_status(); + CHECK(empty.publisher == -1 && empty.service == -1 && empty.scan == -1); + auto publisher = make_self(); auto provider = make_self(); + s->publisher = publisher.as(); + s->provider = provider.as(); + s->publisher_status = int(BluetoothLEAdvertisementPublisherStatus::Waiting); s->service_ready = true; + auto actual = s->radio_status(); + CHECK(actual.publisher == 2 && actual.service == 1 && actual.scan == -1); + publisher->read_error = true; + actual = s->radio_status(); CHECK(actual.publisher == -2 && actual.service == 1); + publisher->read_error = false; provider->read_error = true; + actual = s->radio_status(); CHECK(actual.publisher == 2 && actual.service == -2); + CHECK(!s->failed && !owner.radio_failures && !owner.link_failures && s->links.size() == 2); + CHECK(s->publisher_status == 1 && s->service_ready); + s->publisher = nullptr; s->provider = nullptr; s->shutdown(); + DEBUG_INFO(CAT, "WinRT test PASS: actual radio snapshot tolerates getter failures without changing radio/events/links"); +} + /* Ошибка GATT одного пира не переводит radio в failed и не теряет событие другого пира. */ static void link_exception(UASYNC* ua) { ble_transport owner; @@ -153,7 +203,7 @@ int main() { debug_config_init(); debug_set_level(DEBUG_LEVEL_INFO); init_apartment(apartment_type::multi_threaded); UASYNC* ua = uasync_create(); CHECK(ua); - advertisement_addresses(ua); link_exception(ua); radio_exception(ua); pending_shutdown(ua); + advertisement_addresses(ua); radio_snapshot(ua); link_exception(ua); radio_exception(ua); pending_shutdown(ua); uasync_destroy(ua, 0); uninit_apartment(); return 0; }