Start Windows BLE publisher before GATT and sequence updates

This commit is contained in:
evgeny committed 2026-10-30 17:52:42 +01:00
1 parent 4eefe43053
commit 77979c4058
2 files changed
+123 -20

No files matched your search

+83 -16
View File
@@ -107,6 +107,9 @@ struct WinState : std::enable_shared_from_this<WinState> {
int publisher_status = -1; /* Последний StatusChanged; -1: publisher ещё не создан. */
uint64_t publisher_wait_since = 0;
bool publisher_wait_warned = false;
bool adapter_ready = false, server_created = false, ready_reported = false;
bool service_requested = false, service_stop_pending = false, publication_pending = false;
uint64_t publication_deadline = 0; /* Ограничивает запуск/замену publisher, включая освобождение GATT-рекламы. */
IAsyncInfo startup{nullptr};
BluetoothLEAdvertisementWatcher watcher{nullptr};
BluetoothLEAdvertisementPublisher publisher{nullptr};
@@ -170,7 +173,8 @@ struct WinState : std::enable_shared_from_this<WinState> {
static void diagnostics_tick(void* arg) {
auto& s = *static_cast<WinState*>(arg); s.diagnostics_timer = nullptr;
s.discovery_stats("periodic");
if (s.stopped) return;
s.publication_timeout();
if (s.stopped || s.failed) return;
s.diagnostics_timer = uasync_set_timeout(s.ua, 100000, &s, diagnostics_tick, "ble_winrt_diagnostics"); /* 10s, TB=0.1ms. */
if (!s.diagnostics_timer) DEBUG_WARN(CAT, "BLE WinRT diagnostics timer allocation failed; periodic summaries unavailable");
}
@@ -279,7 +283,46 @@ struct WinState : std::enable_shared_from_this<WinState> {
}
void ready() {
DEBUG_DEBUG(CAT, "BLE WinRT readiness scan=%d service=%d", scan_ready, service_ready);
if (scan_ready && service_ready) ble_backend_status(owner, 1, "WinRT scan/GATT ready");
if (scan_ready && service_ready && !ready_reported) {
ready_reported = true; ble_backend_status(owner, 1, "WinRT scan/GATT ready");
}
}
void publication_timeout() {
if (!publication_deadline || get_time_us() < publication_deadline || failed || stopped) return;
publication_deadline = 0;
DEBUG_ERROR(CAT, "BLE WinRT publication timeout publisher=%d service_requested=%d stop_pending=%d generation=%llu",
publisher_status, service_requested, service_stop_pending, (unsigned long long)publish_generation);
ble_backend_advertising(owner, BLE_CLOSED, "Windows BLE publisher did not start within 20 seconds");
fail("Windows BLE publication timed out; GATT startup/update incomplete");
}
void start_service_advertising() {
GattServiceProviderAdvertisingParameters parameters; parameters.IsConnectable(true); parameters.IsDiscoverable(true);
DEBUG_INFO(CAT, "BLE WinRT start GATT advertisement service=%s publisher=%d generation=%llu connectable=1 discoverable=1",
BLE_SERVICE_UUID, publisher_status, (unsigned long long)publish_generation);
service_requested = true;
step("GattServiceProvider.StartAdvertising"); provider.StartAdvertising(parameters);
}
/* Первичная регистрация — только после Started publisher либо при выключенном объявлении. */
void ensure_service() {
if (!adapter_ready || failed || stopped || service_stop_pending) return;
if (!server_created) {
server_created = true;
DEBUG_INFO(CAT, "BLE WinRT create GATT after publication enabled=%d publisher=%d", broadcast_enabled, publisher_status);
make_server();
} else if (provider && !service_requested) start_service_advertising();
}
void service_status(GattServiceProviderAdvertisementStatus status, BluetoothError error) {
DEBUG_DEBUG(CAT, "BLE WinRT service advertising state=%d error=%d (%s) requested=%d stop_pending=%d",
int(status), int(error), bluetooth_error(error), service_requested, service_stop_pending);
if (error != BluetoothError::Success) { radio_error("GATT advertising failed", error); return; }
if (service_stop_pending && !service_requested && (status == GattServiceProviderAdvertisementStatus::Stopped ||
status == GattServiceProviderAdvertisementStatus::Aborted)) {
service_stop_pending = false; service_ready = false;
DEBUG_INFO(CAT, "BLE WinRT requested GATT stop confirmed state=%d; updating manufacturer advertisement", int(status));
publish();
} else if (status == GattServiceProviderAdvertisementStatus::Started) { service_ready = true; ready(); }
else if (status == GattServiceProviderAdvertisementStatus::Aborted) radio_error("GATT advertising aborted", error);
else DEBUG_INFO(CAT, "BLE WinRT service advertising state=%d", int(status));
}
/* Только доступные Win11 API: фактические параметры отделены от запрошенного профиля. */
void log_connection(ble_link_id id) {
@@ -497,16 +540,10 @@ struct WinState : std::enable_shared_from_this<WinState> {
step("GattServiceProvider.AdvertisementStatusChanged registration (auto_revoke)");
service_changed = provider.AdvertisementStatusChanged(auto_revoke, [weak](auto const&, auto const& args) {
if (auto s = weak.lock()) s->post("GattServiceProvider.AdvertisementStatusChanged", 0, [args](WinState& s) {
auto status = args.Status(); auto error = args.Error();
DEBUG_DEBUG(CAT, "BLE WinRT service advertising state=%d error=%d (%s)", (int)status, (int)error, bluetooth_error(error));
if (status == GattServiceProviderAdvertisementStatus::Started) { s.service_ready = true; s.ready(); }
else if (status == GattServiceProviderAdvertisementStatus::Aborted) s.radio_error("GATT advertising aborted", error);
else DEBUG_INFO(CAT, "BLE WinRT service advertising state=%d", (int)status);
s.service_status(args.Status(), args.Error());
});
});
GattServiceProviderAdvertisingParameters parameters; parameters.IsConnectable(true); parameters.IsDiscoverable(true);
DEBUG_DEBUG(CAT, "BLE WinRT start server service=%s connectable=1 discoverable=1", BLE_SERVICE_UUID);
step("GattServiceProvider.StartAdvertising"); provider.StartAdvertising(parameters);
start_service_advertising();
step("BluetoothLEAdvertisementWatcher construction/ScanningMode");
watcher = BluetoothLEAdvertisementWatcher(); watcher.ScanningMode(BluetoothLEScanningMode::Active);
step("Watcher.Received registration (auto_revoke)");
@@ -558,12 +595,21 @@ struct WinState : std::enable_shared_from_this<WinState> {
});
step("Watcher.Start"); watcher.Start();
DEBUG_DEBUG(CAT, "BLE WinRT scan requested mode=active status=%d company=%04x", (int)watcher.Status(), company);
scan_ready = true; diagnostics_tick(this); ready();
scan_ready = true; ready();
}
void publish() {
if (failed || stopped) return;
step("Publisher.Status");
DEBUG_DEBUG(CAT, "BLE WinRT publish requested enabled=%d bytes=%zu generation=%llu", broadcast_enabled,
broadcast_size, (unsigned long long)publish_generation);
if (!publication_deadline) publication_deadline = get_time_us() + 20000000;
if (service_stop_pending) return;
if (provider && service_requested && broadcast_enabled) {
service_requested = false; service_stop_pending = true;
DEBUG_INFO(CAT, "BLE WinRT pause GATT advertisement before publisher update links=%zu generation=%llu",
links.size(), (unsigned long long)publish_generation);
step("GattServiceProvider.StopAdvertising for publisher"); provider.StopAdvertising(); return;
}
if (publisher) {
auto status = publisher.Status();
if (status == BluetoothLEAdvertisementPublisherStatus::Started || status == BluetoothLEAdvertisementPublisherStatus::Waiting) {
@@ -573,11 +619,14 @@ struct WinState : std::enable_shared_from_this<WinState> {
publish_changed.revoke(); publisher = nullptr;
publisher_status = -1; publisher_wait_since = 0; publisher_wait_warned = false;
}
if (!broadcast_enabled) return;
if (!broadcast_enabled) {
publication_pending = false; publication_deadline = 0; ensure_service(); return;
}
step("Publisher construction/manufacturer data");
BluetoothLEAdvertisement advertisement;
BluetoothLEManufacturerData data; data.CompanyId(company); data.Data(buffer(broadcast.data(), broadcast_size));
advertisement.ManufacturerData().Append(data); publisher = BluetoothLEAdvertisementPublisher(advertisement);
publication_pending = false;
publisher_status = int(BluetoothLEAdvertisementPublisherStatus::Created);
auto weak = weak_from_this(); auto generation = ++publish_generation;
step("Publisher.StatusChanged registration (auto_revoke)");
@@ -604,7 +653,12 @@ struct WinState : std::enable_shared_from_this<WinState> {
if (status == BluetoothLEAdvertisementPublisherStatus::Aborted) s.radio_error("broadcast publication aborted", error);
else if (status == BluetoothLEAdvertisementPublisherStatus::Stopped) {
ble_backend_advertising(s.owner, BLE_CLOSED, "publisher stopped"); s.publish();
} else if (status == BluetoothLEAdvertisementPublisherStatus::Started) ble_backend_advertising(s.owner, BLE_READY, "publisher started");
} else if (status == BluetoothLEAdvertisementPublisherStatus::Started) {
ble_backend_advertising(s.owner, BLE_READY, "publisher started");
if (!s.publication_pending && s.publisher.Status() == BluetoothLEAdvertisementPublisherStatus::Started) {
s.publication_deadline = 0; s.ensure_service();
}
}
else if (status == BluetoothLEAdvertisementPublisherStatus::Waiting)
ble_backend_advertising(s.owner, BLE_STARTING, "publisher waiting for radio resources");
else if (status == BluetoothLEAdvertisementPublisherStatus::Stopping)
@@ -755,7 +809,8 @@ struct WinState : std::enable_shared_from_this<WinState> {
try { if (startup) startup.Cancel(); } catch (...) { DEBUG_WARN(CAT, "%s", exception_reason("cancel startup").c_str()); }
try { if (watcher) watcher.Stop(); } catch (...) { DEBUG_WARN(CAT, "%s", exception_reason("stop scan").c_str()); }
try { if (publisher) publisher.Stop(); } catch (...) { DEBUG_WARN(CAT, "%s", exception_reason("stop publisher").c_str()); }
try { if (provider) provider.StopAdvertising(); } catch (...) { DEBUG_WARN(CAT, "%s", exception_reason("stop server").c_str()); }
try { if (provider && service_requested) provider.StopAdvertising(); }
catch (...) { DEBUG_WARN(CAT, "%s", exception_reason("stop server").c_str()); }
watcher = nullptr; publisher = nullptr; provider = nullptr;
local_rx = nullptr; local_tx = nullptr; local_invite = nullptr; local_ack = nullptr; startup = nullptr;
if (apartment) { uninit_apartment(); apartment = false; }
@@ -765,6 +820,10 @@ struct ble_backend { std::shared_ptr<WinState> state; };
extern "C" struct ble_backend* ble_backend_create(struct ble_transport* owner, struct UASYNC* ua, const struct ble_config* cfg) {
if (cfg->adapter) { DEBUG_ERROR(CAT, "BLE WinRT explicit adapter selection unsupported"); return nullptr; }
auto state = std::make_shared<WinState>(); state->owner = owner; state->ua = ua; state->company = cfg->company_id;
state->broadcast_size = cfg->initial_advertisement_size;
state->broadcast_enabled = state->publication_pending = state->broadcast_size != 0;
if (state->broadcast_size > state->broadcast.size()) { DEBUG_ERROR(CAT, "BLE WinRT initial advertisement too large"); return nullptr; }
if (state->broadcast_size) std::memcpy(state->broadcast.data(), cfg->initial_advertisement, state->broadcast_size);
try {
DEBUG_DEBUG(CAT, "BLE WinRT initialize company=%04x node=%016llx apartment=MTA", cfg->company_id, (unsigned long long)cfg->node_id);
init_apartment(apartment_type::multi_threaded); state->apartment = true;
@@ -797,7 +856,8 @@ extern "C" struct ble_backend* ble_backend_create(struct ble_transport* owner, s
if (!adapter.IsLowEnergySupported() || !adapter.IsCentralRoleSupported() || !adapter.IsPeripheralRoleSupported()) {
s.fail("adapter lacks BLE central/peripheral support"); return;
}
s.make_server();
s.adapter_ready = true; WinState::diagnostics_tick(&s);
if (s.broadcast_enabled) s.publish(); else s.ensure_service();
});
void* memory = u_malloc(sizeof(ble_backend));
if (!memory) { DEBUG_ERROR(CAT, "BLE WinRT allocation failed"); state->shutdown(); return nullptr; }
@@ -810,7 +870,14 @@ extern "C" void ble_backend_destroy(struct ble_backend* b) {
}
extern "C" int ble_backend_advertise(struct ble_backend* b, const uint8_t* data, size_t len) {
try {
auto& s = *b->state; s.broadcast_enabled = data != nullptr; s.broadcast_size = len;
auto& s = *b->state;
if (s.failed || s.stopped) { DEBUG_WARN(CAT, "BLE WinRT advertisement requested on closed backend"); return BLE_NOT_READY; }
if (len > s.broadcast.size() || (len && !data)) { DEBUG_ERROR(CAT, "BLE WinRT invalid advertisement bytes=%zu", len); return BLE_ERROR; }
if (s.broadcast_enabled == (data != nullptr) && s.broadcast_size == len && (!len || !std::memcmp(s.broadcast.data(), data, len))) {
DEBUG_DEBUG(CAT, "BLE WinRT advertisement unchanged bytes=%zu generation=%llu", len, (unsigned long long)s.publish_generation);
return BLE_OK;
}
s.broadcast_enabled = data != nullptr; s.broadcast_size = len; s.publication_pending = true;
if (len) std::memcpy(s.broadcast.data(), data, len);
s.publish(); return BLE_OK;
} catch (...) { DEBUG_ERROR(CAT, "%s", exception_reason(b->state->operation).c_str()); return BLE_ERROR; }
+40 -4
View File
@@ -7,7 +7,7 @@
struct ble_transport {
WinState* state = nullptr;
unsigned radio_failures = 0, link_failures = 0;
unsigned radio_failures = 0, link_failures = 0, ready_events = 0, advertising_events = 0;
ble_link_id closed = 0;
std::string reason;
};
@@ -15,12 +15,13 @@ extern "C" void ble_backend_closed(ble_transport* t, ble_link_id id, const char*
++t->link_failures; t->closed = id; t->reason = reason; t->state->close(id);
}
extern "C" void ble_backend_status(ble_transport* t, int ready, const char* reason) {
CHECK(!ready); ++t->radio_failures; t->reason = reason;
if (ready) { ++t->ready_events; return; }
++t->radio_failures; t->reason = reason;
while (!t->state->links.empty()) t->state->close(t->state->links.begin()->first);
}
/* Эти события не должны возникать в проверке mailbox. */
extern "C" ble_link_id ble_backend_incoming(ble_transport*, const ble_endpoint*) { CHECK(false); return 0; }
extern "C" void ble_backend_advertising(ble_transport*, ble_state, const char*) { CHECK(false); }
extern "C" void ble_backend_advertising(ble_transport* t, ble_state, const char*) { ++t->advertising_events; }
extern "C" void ble_backend_broadcast(ble_transport*, const ble_endpoint*, const uint8_t*, size_t, int) { CHECK(false); }
extern "C" void ble_backend_opened(ble_transport*, ble_link_id, unsigned) { CHECK(false); }
extern "C" void ble_backend_received(ble_transport*, ble_link_id, ble_wire_channel, const uint8_t*, size_t) { CHECK(false); }
@@ -140,6 +141,40 @@ static void radio_snapshot(UASYNC* ua) {
DEBUG_INFO(CAT, "WinRT test PASS: actual radio snapshot tolerates getter failures without changing radio/events/links");
}
static void publication_lifecycle(UASYNC* ua) {
ble_transport owner;
auto s = make_state(ua, owner);
s->scan_ready = true; s->ready(); CHECK(owner.ready_events == 0);
s->service_ready = true; s->ready(); s->ready(); CHECK(owner.ready_events == 1);
s->broadcast_enabled = true; s->broadcast_size = 3; std::memcpy(s->broadcast.data(), "abc", 3);
s->publish_generation = 7;
ble_backend backend{s};
CHECK(ble_backend_advertise(&backend, reinterpret_cast<const uint8_t*>("abc"), 3) == BLE_OK);
CHECK(s->publish_generation == 7 && !s->publication_deadline && !s->publication_pending && owner.advertising_events == 0);
/* Подтверждение StopAdvertising/Success не должно закрывать существующие links или повторять READY. */
s->broadcast_enabled = false; s->broadcast_size = 0;
for (auto status : {GattServiceProviderAdvertisementStatus::Stopped, GattServiceProviderAdvertisementStatus::Aborted}) {
s->service_stop_pending = true; s->service_requested = false;
s->service_status(status, BluetoothError::Success);
CHECK(!s->failed && !s->service_ready && !s->service_stop_pending && s->links.size() == 2);
s->service_status(GattServiceProviderAdvertisementStatus::Started, BluetoothError::Success);
CHECK(s->service_ready && owner.ready_events == 1);
}
s->publication_deadline = get_time_us() - 1;
s->publication_timeout(); s->publication_timeout();
CHECK(s->failed && owner.radio_failures == 1 && owner.advertising_events == 1 && s->links.empty());
CHECK(ble_backend_advertise(&backend, nullptr, 0) == BLE_NOT_READY);
s->shutdown();
DEBUG_INFO(CAT, "WinRT test PASS: readiness gate, unchanged payload, requested stop/restart, publication timeout and closed backend");
for (auto error : {BluetoothError::Success, BluetoothError::RadioNotAvailable}) {
ble_transport failed_owner;
auto state = make_state(ua, failed_owner);
state->service_stop_pending = error != BluetoothError::Success;
state->service_status(GattServiceProviderAdvertisementStatus::Aborted, error);
CHECK(state->failed && failed_owner.radio_failures == 1); state->shutdown();
}
}
/* Ошибка GATT одного пира не переводит radio в failed и не теряет событие другого пира. */
static void link_exception(UASYNC* ua) {
ble_transport owner;
@@ -203,7 +238,8 @@ 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); radio_snapshot(ua); link_exception(ua); radio_exception(ua); pending_shutdown(ua);
advertisement_addresses(ua); radio_snapshot(ua); publication_lifecycle(ua);
link_exception(ua); radio_exception(ua); pending_shutdown(ua);
uasync_destroy(ua, 0); uninit_apartment();
return 0;
}