Start Windows BLE publisher before GATT and sequence updates
This commit is contained in:
1 parent
4eefe43053
commit
77979c4058
2 files changed
+123
-20
No files matched your search
+83
-16
@@ -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; }
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
Reference in new issue
Block a user