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.
531 lines
35 KiB
C++
531 lines
35 KiB
C++
/* WinRT backend: события Windows сериализуются в uasync через ограниченный mailbox.
|
|
* Каждая отправка адресована link/subscribedClient; одна незавершённая операция на link.
|
|
* Слабые ссылки в WinRT handlers и отменяемый posted_task исключают обращения к уничтоженному core. */
|
|
#include "ble_backend.h"
|
|
#include "../u_async.h"
|
|
#include "../mem.h"
|
|
#include "../debug_config.h"
|
|
#include <winrt/Windows.Foundation.h>
|
|
#include <winrt/Windows.Foundation.Collections.h>
|
|
#include <winrt/Windows.Devices.Bluetooth.h>
|
|
#include <winrt/Windows.Devices.Bluetooth.Advertisement.h>
|
|
#include <winrt/Windows.Devices.Bluetooth.GenericAttributeProfile.h>
|
|
#include <winrt/Windows.Storage.Streams.h>
|
|
#include <array>
|
|
#include <algorithm>
|
|
#include <vector>
|
|
#include <deque>
|
|
#include <functional>
|
|
#include <map>
|
|
#include <memory>
|
|
#include <mutex>
|
|
#include <string>
|
|
#include <cstring>
|
|
#include <cstdio>
|
|
using namespace winrt;
|
|
using namespace Windows::Foundation;
|
|
using namespace Windows::Devices::Bluetooth;
|
|
using namespace Windows::Devices::Bluetooth::Advertisement;
|
|
using namespace Windows::Devices::Bluetooth::GenericAttributeProfile;
|
|
using namespace Windows::Storage::Streams;
|
|
static constexpr auto CAT = DEBUG_CATEGORY_DISCOVERY;
|
|
static const char* bluetooth_error(BluetoothError error) {
|
|
switch (error) {
|
|
case BluetoothError::Success: return "Success";
|
|
case BluetoothError::RadioNotAvailable: return "RadioNotAvailable";
|
|
case BluetoothError::ResourceInUse: return "ResourceInUse";
|
|
case BluetoothError::DeviceNotConnected: return "DeviceNotConnected";
|
|
case BluetoothError::DisabledByPolicy: return "DisabledByPolicy";
|
|
case BluetoothError::NotSupported: return "NotSupported";
|
|
case BluetoothError::DisabledByUser: return "DisabledByUser";
|
|
case BluetoothError::ConsentRequired: return "ConsentRequired";
|
|
case BluetoothError::TransportNotSupported: return "TransportNotSupported";
|
|
default: return "OtherError";
|
|
}
|
|
}
|
|
/* Вызывать только из catch; сохраняем контекст и HRESULT вместо немого catch(...). */
|
|
static std::string exception_reason(const char* phase, ble_link_id id = 0) {
|
|
char prefix[192];
|
|
try { throw; }
|
|
catch (const hresult_error& e) {
|
|
std::snprintf(prefix, sizeof(prefix), "BLE WinRT phase=%s link=%llu HRESULT=0x%08x: ",
|
|
phase, (unsigned long long)id, (unsigned)e.code().value);
|
|
return prefix + to_string(e.message());
|
|
} catch (const std::exception& e) {
|
|
return std::string("BLE WinRT phase=") + phase + ": " + e.what();
|
|
} catch (...) { return std::string("BLE WinRT phase=") + phase + ": unknown exception"; }
|
|
}
|
|
struct WinState;
|
|
struct WinLink {
|
|
ble_link_id id = 0;
|
|
std::string endpoint;
|
|
hstring device_id;
|
|
bool outgoing = false, opened = false, sending = false;
|
|
BluetoothLEDevice device{nullptr};
|
|
GattDeviceService service{nullptr};
|
|
GattCharacteristic rx{nullptr}, tx{nullptr};
|
|
GattSession session{nullptr};
|
|
GattSubscribedClient subscriber{nullptr};
|
|
IAsyncInfo operation{nullptr};
|
|
GattCharacteristic::ValueChanged_revoker value_changed;
|
|
GattSession::SessionStatusChanged_revoker session_changed;
|
|
GattSession::MaxPduSizeChanged_revoker pdu_changed;
|
|
};
|
|
struct WriteDeferral {
|
|
Deferral value;
|
|
explicit WriteDeferral(Deferral v) : value(v) {}
|
|
~WriteDeferral() { try { value.Complete(); } catch (...) { DEBUG_WARN(CAT, "%s", exception_reason("write deferral").c_str()); } }
|
|
};
|
|
struct WinState : std::enable_shared_from_this<WinState> {
|
|
struct UASYNC* ua = nullptr;
|
|
struct ble_transport* owner = nullptr;
|
|
uint16_t company = 0;
|
|
std::mutex mutex;
|
|
std::deque<std::function<void(WinState&)>> events;
|
|
struct posted_task* posted = nullptr;
|
|
bool stopped = false, failed = false, overflow = false, apartment = false, scan_ready = false, service_ready = false;
|
|
IAsyncInfo startup{nullptr};
|
|
BluetoothLEAdvertisementWatcher watcher{nullptr};
|
|
BluetoothLEAdvertisementPublisher publisher{nullptr};
|
|
GattServiceProvider provider{nullptr};
|
|
GattLocalCharacteristic local_rx{nullptr}, local_tx{nullptr};
|
|
BluetoothLEAdvertisementWatcher::Received_revoker received;
|
|
BluetoothLEAdvertisementWatcher::Stopped_revoker scan_stopped;
|
|
BluetoothLEAdvertisementPublisher::StatusChanged_revoker publish_changed;
|
|
GattServiceProvider::AdvertisementStatusChanged_revoker service_changed;
|
|
GattLocalCharacteristic::WriteRequested_revoker write_requested;
|
|
GattLocalCharacteristic::SubscribedClientsChanged_revoker subscriptions_changed;
|
|
std::map<ble_link_id, std::unique_ptr<WinLink>> links;
|
|
std::array<uint8_t, BLE_BROADCAST_MAX> broadcast{};
|
|
size_t broadcast_size = 0;
|
|
bool broadcast_enabled = false;
|
|
uint64_t publish_generation = 0;
|
|
static void dispatch(void* arg) {
|
|
auto& s = *static_cast<WinState*>(arg);
|
|
std::deque<std::function<void(WinState&)>> batch;
|
|
bool overloaded;
|
|
{ std::lock_guard guard(s.mutex); s.posted = nullptr; batch.swap(s.events); overloaded = s.overflow; s.overflow = false; }
|
|
if (overloaded) { s.fail("WinRT event queue overflow"); return; }
|
|
DEBUG_TRACE(CAT, "BLE WinRT dispatch events=%zu links=%zu", batch.size(), s.links.size());
|
|
for (auto& event : batch) {
|
|
if (s.stopped || s.failed) break;
|
|
try { event(s); }
|
|
catch (...) { s.fail(exception_reason("event dispatch").c_str()); }
|
|
}
|
|
}
|
|
void post(std::function<void(WinState&)> event) {
|
|
std::lock_guard guard(mutex);
|
|
if (stopped) return;
|
|
if (events.size() >= 256) {
|
|
if (!overflow) DEBUG_ERROR(CAT, "BLE WinRT event mailbox full queued=%zu limit=256", events.size());
|
|
overflow = true;
|
|
}
|
|
else events.push_back(std::move(event));
|
|
if (posted) return;
|
|
posted = static_cast<struct posted_task*>(u_calloc(1, sizeof(struct posted_task)));
|
|
if (!posted) { overflow = true; DEBUG_ERROR(CAT, "BLE WinRT event allocation failed"); return; }
|
|
posted->callback = dispatch; posted->arg = this; uasync_post_reserved(ua, posted);
|
|
}
|
|
WinLink* link(ble_link_id id) { auto it = links.find(id); return it == links.end() ? nullptr : it->second.get(); }
|
|
void fail(const char* reason) {
|
|
if (failed) return;
|
|
failed = true; DEBUG_ERROR(CAT, "BLE WinRT failed: %s", reason); ble_backend_status(owner, 0, reason);
|
|
}
|
|
void error(ble_link_id id, const char* reason) {
|
|
DEBUG_WARN(CAT, "BLE WinRT link=%llu failed: %s", (unsigned long long)id, reason);
|
|
ble_backend_closed(owner, id, reason);
|
|
}
|
|
void radio_error(const char* phase, BluetoothError error) {
|
|
char reason[192];
|
|
std::snprintf(reason, sizeof(reason), "%s: BluetoothError=%d (%s)", phase, (int)error, bluetooth_error(error));
|
|
fail(reason);
|
|
}
|
|
template<typename Result, typename Callback>
|
|
void await(const char* phase, const IAsyncOperation<Result>& operation, ble_link_id id, Callback callback) {
|
|
const bool fragment = !std::strcmp(phase, "write frame") || !std::strcmp(phase, "indicate frame");
|
|
if (!fragment) DEBUG_DEBUG(CAT, "BLE WinRT async begin phase=%s link=%llu", phase, (unsigned long long)id);
|
|
if (id) { auto* l = link(id); if (!l) { operation.Cancel(); return; } l->operation = operation; }
|
|
else startup = operation;
|
|
auto weak = weak_from_this();
|
|
operation.Completed([weak, id, phase, fragment, callback](const IAsyncOperation<Result>& op, AsyncStatus status) {
|
|
if (auto s = weak.lock()) s->post([id, op, status, phase, fragment, callback](WinState& self) {
|
|
auto* l = id ? self.link(id) : nullptr;
|
|
if (id && !l) {
|
|
DEBUG_DEBUG(CAT, "BLE WinRT stale completion phase=%s link=%llu status=%d", phase, (unsigned long long)id, (int)status);
|
|
return;
|
|
}
|
|
if (l) l->operation = nullptr; else self.startup = nullptr;
|
|
if (status != AsyncStatus::Completed) {
|
|
char reason[192];
|
|
std::snprintf(reason, sizeof(reason), "phase=%s async_status=%d HRESULT=0x%08x", phase, (int)status,
|
|
(unsigned)op.ErrorCode().value);
|
|
if (id) self.error(id, reason); else self.fail(reason);
|
|
return;
|
|
}
|
|
if (!fragment) DEBUG_DEBUG(CAT, "BLE WinRT async complete phase=%s link=%llu", phase, (unsigned long long)id);
|
|
callback(self, op.GetResults());
|
|
});
|
|
});
|
|
}
|
|
static IBuffer buffer(const uint8_t* data, size_t len) {
|
|
DataWriter writer; writer.WriteBytes(array_view<const uint8_t>(data, data + len)); return writer.DetachBuffer();
|
|
}
|
|
static std::vector<uint8_t> bytes(const IBuffer& buffer) {
|
|
if (buffer.Length() > BLE_VALUE_MAX) throw hresult_invalid_argument();
|
|
std::vector<uint8_t> result(buffer.Length()); DataReader::FromBuffer(buffer).ReadBytes(result); return result;
|
|
}
|
|
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");
|
|
}
|
|
void watch_session(WinLink& l) {
|
|
auto weak = weak_from_this(); auto id = l.id;
|
|
l.session_changed = l.session.SessionStatusChanged(auto_revoke, [weak, id](auto const&, GattSessionStatusChangedEventArgs const& a) {
|
|
auto status = a.Status(); auto error = a.Error();
|
|
if (auto s = weak.lock()) s->post([id, status, error](WinState& s) {
|
|
DEBUG_DEBUG(CAT, "BLE WinRT session link=%llu status=%d error=%d (%s)",
|
|
(unsigned long long)id, (int)status, (int)error, bluetooth_error(error));
|
|
if (status == GattSessionStatus::Closed && s.link(id)) s.error(id, "GATT session closed");
|
|
});
|
|
});
|
|
l.pdu_changed = l.session.MaxPduSizeChanged(auto_revoke, [weak, id](auto const& session, auto const&) {
|
|
auto mtu = session.MaxPduSize();
|
|
if (auto s = weak.lock()) s->post([id, mtu](WinState&) {
|
|
DEBUG_DEBUG(CAT, "BLE WinRT negotiated PDU changed link=%llu mtu=%u", (unsigned long long)id, (unsigned)mtu);
|
|
});
|
|
});
|
|
}
|
|
WinLink* incoming(const GattSession& session) {
|
|
hstring key = session.DeviceId().Id();
|
|
for (auto& [id, l] : links) if (l->device_id == key) return l->outgoing ? nullptr : l.get();
|
|
std::string endpoint = to_string(key);
|
|
if (endpoint.size() >= BLE_ENDPOINT_MAX || links.size() >= BLE_LINKS_MAX) {
|
|
DEBUG_WARN(CAT, "BLE WinRT incoming endpoint/connection limit"); return nullptr;
|
|
}
|
|
struct ble_endpoint ep{}; std::memcpy(ep.id, endpoint.c_str(), endpoint.size() + 1);
|
|
ble_link_id id = ble_backend_incoming(owner, &ep); if (!id) return nullptr;
|
|
auto l = std::make_unique<WinLink>(); l->id = id; l->endpoint = endpoint; l->device_id = key; l->session = session;
|
|
DEBUG_DEBUG(CAT, "BLE WinRT incoming link=%llu endpoint=%s mtu=%u", (unsigned long long)id, endpoint.c_str(), session.MaxPduSize());
|
|
auto* result = l.get(); links.emplace(id, std::move(l)); watch_session(*result); return result;
|
|
}
|
|
void subscriptions() {
|
|
DEBUG_DEBUG(CAT, "BLE WinRT subscriptions clients=%u links=%zu", local_tx.SubscribedClients().Size(), links.size());
|
|
std::vector<ble_link_id> active;
|
|
for (auto const& client : local_tx.SubscribedClients()) {
|
|
auto* l = incoming(client.Session()); if (!l) continue;
|
|
l->subscriber = client; active.push_back(l->id);
|
|
if (!l->opened) {
|
|
DEBUG_DEBUG(CAT, "BLE WinRT subscribed link=%llu notification_max=%u", (unsigned long long)l->id, client.MaxNotificationSize());
|
|
l->opened = true; ble_backend_opened(owner, l->id, client.MaxNotificationSize() + 3);
|
|
}
|
|
}
|
|
std::vector<ble_link_id> gone;
|
|
for (auto& [id, l] : links) if (!l->outgoing && l->opened && std::find(active.begin(), active.end(), id) == active.end()) gone.push_back(id);
|
|
for (auto id : gone) error(id, "indication subscription removed");
|
|
}
|
|
void make_server() {
|
|
await("create GATT service", GattServiceProvider::CreateAsync(guid(BLE_SERVICE_UUID)), 0, [](WinState& s, GattServiceProviderResult const& result) {
|
|
if (result.Error() != BluetoothError::Success) { s.radio_error("create GATT service", result.Error()); return; }
|
|
s.provider = result.ServiceProvider();
|
|
GattLocalCharacteristicParameters parameters;
|
|
parameters.CharacteristicProperties(GattCharacteristicProperties::Write);
|
|
parameters.WriteProtectionLevel(GattProtectionLevel::Plain);
|
|
s.await("create RX characteristic", s.provider.Service().CreateCharacteristicAsync(guid(BLE_RX_UUID), parameters), 0,
|
|
[](WinState& s, GattLocalCharacteristicResult const& result) {
|
|
if (result.Error() != BluetoothError::Success) { s.radio_error("create RX characteristic", result.Error()); return; }
|
|
s.local_rx = result.Characteristic(); s.make_tx();
|
|
});
|
|
});
|
|
}
|
|
void make_tx() {
|
|
GattLocalCharacteristicParameters parameters; parameters.CharacteristicProperties(GattCharacteristicProperties::Indicate);
|
|
parameters.ReadProtectionLevel(GattProtectionLevel::Plain);
|
|
await("create TX characteristic", provider.Service().CreateCharacteristicAsync(guid(BLE_TX_UUID), parameters), 0,
|
|
[](WinState& s, GattLocalCharacteristicResult const& result) {
|
|
if (result.Error() != BluetoothError::Success) { s.radio_error("create TX characteristic", result.Error()); return; }
|
|
s.local_tx = result.Characteristic(); s.start_server();
|
|
});
|
|
}
|
|
void start_server() {
|
|
auto weak = weak_from_this();
|
|
write_requested = local_rx.WriteRequested(auto_revoke, [weak](auto const&, GattWriteRequestedEventArgs const& args) {
|
|
try {
|
|
auto deferral = std::make_shared<WriteDeferral>(args.GetDeferral());
|
|
auto request = args.GetRequestAsync(); auto session = args.Session();
|
|
request.Completed([weak, deferral, session](auto const& operation, AsyncStatus status) {
|
|
if (auto s = weak.lock()) s->post([deferral, session, operation, status](WinState& s) {
|
|
if (status != AsyncStatus::Completed) {
|
|
DEBUG_WARN(CAT, "BLE WinRT GetRequest status=%d HRESULT=0x%08x", (int)status, (unsigned)operation.ErrorCode().value);
|
|
return;
|
|
}
|
|
auto request = operation.GetResults(); if (!request) { DEBUG_WARN(CAT, "BLE WinRT expired write request"); return; }
|
|
if (request.Offset() || request.Option() != GattWriteOption::WriteWithResponse || request.Value().Length() > BLE_VALUE_MAX) {
|
|
DEBUG_WARN(CAT, "BLE WinRT invalid write offset=%u option=%d bytes=%u", request.Offset(),
|
|
(int)request.Option(), request.Value().Length());
|
|
request.RespondWithProtocolError(0x06); return;
|
|
}
|
|
auto* l = s.incoming(session);
|
|
if (!l) { request.RespondWithProtocolError(0x08); return; }
|
|
auto id = l->id; auto data = bytes(request.Value());
|
|
DEBUG_TRACE(CAT, "BLE WinRT RX write link=%llu bytes=%zu", (unsigned long long)id, data.size());
|
|
ble_backend_received(s.owner, id, data.data(), data.size());
|
|
if (s.link(id)) request.Respond(); else request.RespondWithProtocolError(0x08);
|
|
});
|
|
});
|
|
} catch (...) {
|
|
auto reason = exception_reason("WriteRequested");
|
|
if (auto s = weak.lock()) s->post([reason](WinState& s) { s.fail(reason.c_str()); });
|
|
}
|
|
});
|
|
subscriptions_changed = local_tx.SubscribedClientsChanged(auto_revoke, [weak](auto const&, auto const&) {
|
|
if (auto s = weak.lock()) s->post([](WinState& s) { s.subscriptions(); });
|
|
});
|
|
service_changed = provider.AdvertisementStatusChanged(auto_revoke, [weak](auto const&, auto const& args) {
|
|
auto status = args.Status(); auto error = args.Error();
|
|
if (auto s = weak.lock()) s->post([status, error](WinState& s) {
|
|
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);
|
|
});
|
|
});
|
|
GattServiceProviderAdvertisingParameters parameters; parameters.IsConnectable(true); parameters.IsDiscoverable(true);
|
|
DEBUG_DEBUG(CAT, "BLE WinRT start server service=%s connectable=1 discoverable=1", BLE_SERVICE_UUID);
|
|
provider.StartAdvertising(parameters);
|
|
watcher = BluetoothLEAdvertisementWatcher(); watcher.ScanningMode(BluetoothLEScanningMode::Active);
|
|
received = watcher.Received(auto_revoke, [weak](auto const&, BluetoothLEAdvertisementReceivedEventArgs const& args) {
|
|
auto state = weak.lock(); if (!state) return;
|
|
bool matching = false;
|
|
for (auto const& value : args.Advertisement().ManufacturerData()) if (value.CompanyId() == state->company) matching = true;
|
|
if (!matching) return;
|
|
state->post([args](WinState& s) {
|
|
for (auto const& value : args.Advertisement().ManufacturerData()) {
|
|
if (value.CompanyId() != s.company || value.Data().Length() > BLE_BROADCAST_MAX) continue;
|
|
struct ble_endpoint ep{};
|
|
std::snprintf(ep.id, sizeof(ep.id), "%012llx/%d", (unsigned long long)args.BluetoothAddress(), (int)args.BluetoothAddressType());
|
|
auto data = bytes(value.Data());
|
|
DEBUG_TRACE(CAT, "BLE WinRT broadcast endpoint=%s company=%04x bytes=%zu rssi=%d", ep.id, s.company,
|
|
data.size(), args.RawSignalStrengthInDBm());
|
|
ble_backend_broadcast(s.owner, &ep, data.data(), data.size(), args.RawSignalStrengthInDBm());
|
|
}
|
|
});
|
|
});
|
|
scan_stopped = watcher.Stopped(auto_revoke, [weak](auto const&, auto const& args) {
|
|
auto error = args.Error(); if (auto s = weak.lock()) s->post([error](WinState& s) {
|
|
s.radio_error("scan stopped", error);
|
|
});
|
|
});
|
|
watcher.Start();
|
|
DEBUG_DEBUG(CAT, "BLE WinRT scan requested mode=active status=%d company=%04x", (int)watcher.Status(), company);
|
|
scan_ready = true; ready();
|
|
}
|
|
void publish() {
|
|
DEBUG_DEBUG(CAT, "BLE WinRT publish requested enabled=%d bytes=%zu generation=%llu", broadcast_enabled,
|
|
broadcast_size, (unsigned long long)publish_generation);
|
|
if (publisher) {
|
|
auto status = publisher.Status();
|
|
if (status == BluetoothLEAdvertisementPublisherStatus::Started || status == BluetoothLEAdvertisementPublisherStatus::Waiting) {
|
|
publisher.Stop(); return;
|
|
}
|
|
if (status == BluetoothLEAdvertisementPublisherStatus::Stopping) return;
|
|
publish_changed.revoke(); publisher = nullptr;
|
|
}
|
|
if (!broadcast_enabled) return;
|
|
BluetoothLEAdvertisement advertisement;
|
|
BluetoothLEManufacturerData data; data.CompanyId(company); data.Data(buffer(broadcast.data(), broadcast_size));
|
|
advertisement.ManufacturerData().Append(data); publisher = BluetoothLEAdvertisementPublisher(advertisement);
|
|
auto weak = weak_from_this(); auto generation = ++publish_generation;
|
|
publish_changed = publisher.StatusChanged(auto_revoke, [weak, generation](auto const&, auto const& args) {
|
|
auto status = args.Status(); auto error = args.Error();
|
|
if (auto s = weak.lock()) s->post([status, error, generation](WinState& s) {
|
|
if (generation != s.publish_generation) return;
|
|
DEBUG_DEBUG(CAT, "BLE WinRT broadcast state=%d error=%d (%s) generation=%llu", (int)status, (int)error,
|
|
bluetooth_error(error), (unsigned long long)generation);
|
|
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 ble_backend_advertising(s.owner, BLE_STARTING, "publisher waiting/stopping");
|
|
});
|
|
});
|
|
publisher.Start();
|
|
}
|
|
void connect(ble_link_id id, uint64_t address, BluetoothAddressType type) {
|
|
DEBUG_DEBUG(CAT, "BLE WinRT connect link=%llu address=%012llx type=%d", (unsigned long long)id, (unsigned long long)address, (int)type);
|
|
await("open BLE device", BluetoothLEDevice::FromBluetoothAddressAsync(address, type), id, [id](WinState& s, BluetoothLEDevice const& device) {
|
|
auto* l = s.link(id);
|
|
if (!device) { s.error(id, "BLE device unavailable"); return; }
|
|
l->device = device; l->device_id = device.BluetoothDeviceId().Id();
|
|
DEBUG_DEBUG(CAT, "BLE WinRT device link=%llu endpoint=%s status=%d", (unsigned long long)id,
|
|
to_string(l->device_id).c_str(), (int)device.ConnectionStatus());
|
|
s.await("discover GATT service", device.GetGattServicesForUuidAsync(guid(BLE_SERVICE_UUID), BluetoothCacheMode::Uncached), id,
|
|
[id](WinState& s, GattDeviceServicesResult const& result) {
|
|
auto protocol = result.ProtocolError();
|
|
DEBUG_DEBUG(CAT, "BLE WinRT services link=%llu status=%d ATT_error=%d count=%u", (unsigned long long)id,
|
|
(int)result.Status(), protocol ? int(protocol.Value()) : -1,
|
|
result.Status() == GattCommunicationStatus::Success ? result.Services().Size() : 0);
|
|
if (result.Status() != GattCommunicationStatus::Success || result.Services().Size() != 1) {
|
|
s.error(id, "VibeChat GATT service unavailable/ambiguous"); return;
|
|
}
|
|
auto* l = s.link(id); l->service = result.Services().GetAt(0);
|
|
s.await("discover GATT characteristics", l->service.GetCharacteristicsAsync(BluetoothCacheMode::Uncached), id,
|
|
[id](WinState& s, GattCharacteristicsResult const& result) {
|
|
auto protocol = result.ProtocolError();
|
|
DEBUG_DEBUG(CAT, "BLE WinRT characteristics link=%llu status=%d ATT_error=%d count=%u", (unsigned long long)id,
|
|
(int)result.Status(), protocol ? int(protocol.Value()) : -1,
|
|
result.Status() == GattCommunicationStatus::Success ? result.Characteristics().Size() : 0);
|
|
if (result.Status() != GattCommunicationStatus::Success) { s.error(id, "GATT discovery failed"); return; }
|
|
auto* l = s.link(id);
|
|
for (auto const& ch : result.Characteristics()) {
|
|
DEBUG_DEBUG(CAT, "BLE WinRT characteristic link=%llu uuid=%s properties=0x%x", (unsigned long long)id,
|
|
to_string(to_hstring(ch.Uuid())).c_str(), (unsigned)ch.CharacteristicProperties());
|
|
if (ch.Uuid() == guid(BLE_RX_UUID)) l->rx = ch;
|
|
if (ch.Uuid() == guid(BLE_TX_UUID)) l->tx = ch;
|
|
}
|
|
if (!l->rx || !l->tx) { s.error(id, "RX/TX characteristics missing"); return; }
|
|
s.subscribe(id);
|
|
});
|
|
});
|
|
});
|
|
}
|
|
void subscribe(ble_link_id id) {
|
|
auto* l = link(id);
|
|
await("open GATT session", GattSession::FromDeviceIdAsync(l->device.BluetoothDeviceId()), id, [id](WinState& s, GattSession const& session) {
|
|
auto* l = s.link(id);
|
|
if (!session) { s.error(id, "GATT session unavailable"); return; }
|
|
l->session = session; session.MaintainConnection(true); s.watch_session(*l);
|
|
DEBUG_DEBUG(CAT, "BLE WinRT session opened link=%llu mtu=%u maintain_connection=1", (unsigned long long)id, session.MaxPduSize());
|
|
auto weak = s.weak_from_this();
|
|
l->value_changed = l->tx.ValueChanged(auto_revoke, [weak, id](auto const&, GattValueChangedEventArgs const& args) {
|
|
if (auto s = weak.lock()) s->post([id, args](WinState& s) {
|
|
if (!s.link(id)) return;
|
|
auto data = bytes(args.CharacteristicValue());
|
|
DEBUG_TRACE(CAT, "BLE WinRT RX indication link=%llu bytes=%zu", (unsigned long long)id, data.size());
|
|
ble_backend_received(s.owner, id, data.data(), data.size());
|
|
});
|
|
});
|
|
s.await("subscribe indications", l->tx.WriteClientCharacteristicConfigurationDescriptorAsync(GattClientCharacteristicConfigurationDescriptorValue::Indicate), id,
|
|
[id](WinState& s, GattCommunicationStatus status) {
|
|
DEBUG_DEBUG(CAT, "BLE WinRT subscribe link=%llu status=%d", (unsigned long long)id, (int)status);
|
|
if (status != GattCommunicationStatus::Success) { s.error(id, "GATT subscription failed"); return; }
|
|
auto* l = s.link(id); l->opened = true; ble_backend_opened(s.owner, id, l->session.MaxPduSize());
|
|
});
|
|
});
|
|
}
|
|
void close(ble_link_id id) {
|
|
auto it = links.find(id); if (it == links.end()) return;
|
|
auto l = std::move(it->second); links.erase(it);
|
|
DEBUG_DEBUG(CAT, "BLE WinRT close link=%llu outgoing=%d opened=%d sending=%d pending=%d", (unsigned long long)id,
|
|
l->outgoing, l->opened, l->sending, bool(l->operation));
|
|
l->value_changed.revoke(); l->session_changed.revoke(); l->pdu_changed.revoke();
|
|
try {
|
|
if (l->operation) l->operation.Cancel();
|
|
if (l->outgoing) {
|
|
if (l->session) { l->session.MaintainConnection(false); l->session.Close(); }
|
|
if (l->service) l->service.Close();
|
|
if (l->device) l->device.Close();
|
|
}
|
|
} catch (...) { DEBUG_WARN(CAT, "%s", exception_reason("close", id).c_str()); }
|
|
}
|
|
void shutdown() {
|
|
struct posted_task* task;
|
|
std::deque<std::function<void(WinState&)>> discarded;
|
|
{ std::lock_guard guard(mutex); stopped = true; task = posted; posted = nullptr; discarded.swap(events); }
|
|
DEBUG_DEBUG(CAT, "BLE WinRT shutdown links=%zu discarded_events=%zu pending_dispatch=%d", links.size(), discarded.size(), task != nullptr);
|
|
discarded.clear();
|
|
if (task) uasync_cancel_post(ua, task);
|
|
received.revoke(); scan_stopped.revoke(); publish_changed.revoke(); service_changed.revoke();
|
|
write_requested.revoke(); subscriptions_changed.revoke();
|
|
while (!links.empty()) close(links.begin()->first);
|
|
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()); }
|
|
watcher = nullptr; publisher = nullptr; provider = nullptr; local_rx = nullptr; local_tx = nullptr; startup = nullptr;
|
|
if (apartment) { uninit_apartment(); apartment = false; }
|
|
}
|
|
};
|
|
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;
|
|
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;
|
|
state->await("get default adapter", BluetoothAdapter::GetDefaultAsync(), 0, [](WinState& s, BluetoothAdapter const& adapter) {
|
|
if (!adapter) { s.fail("no default Bluetooth adapter"); return; }
|
|
DEBUG_DEBUG(CAT, "BLE WinRT adapter id=%s address=%012llx BLE=%d central=%d peripheral=%d", to_string(adapter.DeviceId()).c_str(),
|
|
(unsigned long long)adapter.BluetoothAddress(), adapter.IsLowEnergySupported(),
|
|
adapter.IsCentralRoleSupported(), adapter.IsPeripheralRoleSupported());
|
|
try {
|
|
auto advertising = adapter.try_as<IBluetoothAdapter3>();
|
|
auto phy = adapter.try_as<IBluetoothAdapter4>();
|
|
DEBUG_DEBUG(CAT, "BLE WinRT adapter extended_advertising=%d advertisement_max=%d PHY_2M_supported=%d PHY_coded_supported=%d (-1=API unavailable)",
|
|
advertising ? int(advertising.IsExtendedAdvertisingSupported()) : -1,
|
|
advertising ? int(advertising.MaxAdvertisementDataLength()) : -1,
|
|
phy ? int(phy.IsLowEnergyUncoded2MPhySupported()) : -1, phy ? int(phy.IsLowEnergyCodedPhySupported()) : -1);
|
|
} catch (...) { DEBUG_WARN(CAT, "%s", exception_reason("optional adapter capabilities").c_str()); }
|
|
if (!adapter.IsLowEnergySupported() || !adapter.IsCentralRoleSupported() || !adapter.IsPeripheralRoleSupported()) {
|
|
s.fail("adapter lacks BLE central/peripheral support"); return;
|
|
}
|
|
s.make_server();
|
|
});
|
|
void* memory = u_malloc(sizeof(ble_backend));
|
|
if (!memory) { DEBUG_ERROR(CAT, "BLE WinRT allocation failed"); state->shutdown(); return nullptr; }
|
|
return new(memory) ble_backend{state};
|
|
} catch (...) { DEBUG_ERROR(CAT, "%s", exception_reason("initialize").c_str()); state->shutdown(); return nullptr; }
|
|
}
|
|
extern "C" void ble_backend_destroy(struct ble_backend* b) {
|
|
if (!b) return;
|
|
b->state->shutdown(); b->~ble_backend(); u_free(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;
|
|
if (len) std::memcpy(s.broadcast.data(), data, len);
|
|
s.publish(); return BLE_OK;
|
|
} catch (...) { DEBUG_ERROR(CAT, "%s", exception_reason("advertise").c_str()); return BLE_ERROR; }
|
|
}
|
|
extern "C" int ble_backend_connect(struct ble_backend* b, ble_link_id id, const struct ble_endpoint* ep) {
|
|
unsigned long long address; int type, consumed = 0;
|
|
if (std::sscanf(ep->id, "%llx/%d%n", &address, &type, &consumed) != 2 || ep->id[consumed] || address >> 48 || type < 0 || type > 2) {
|
|
DEBUG_ERROR(CAT, "BLE invalid WinRT endpoint=%s", ep->id); return BLE_ERROR;
|
|
}
|
|
try {
|
|
auto& s = *b->state; auto l = std::make_unique<WinLink>(); l->id = id; l->endpoint = ep->id; l->outgoing = true;
|
|
s.links.emplace(id, std::move(l)); s.connect(id, address, static_cast<BluetoothAddressType>(type)); return BLE_OK;
|
|
} catch (...) { DEBUG_ERROR(CAT, "%s", exception_reason("connect", id).c_str()); b->state->close(id); return BLE_ERROR; }
|
|
}
|
|
extern "C" void ble_backend_close(struct ble_backend* b, ble_link_id id) { b->state->close(id); }
|
|
extern "C" int ble_backend_send(struct ble_backend* b, ble_link_id id, const uint8_t* data, size_t len) {
|
|
auto& s = *b->state; auto* l = s.link(id);
|
|
if (!l || !l->opened || l->sending) {
|
|
DEBUG_ERROR(CAT, "BLE invalid WinRT send link=%llu exists=%d opened=%d sending=%d bytes=%zu", (unsigned long long)id,
|
|
l != nullptr, l && l->opened, l && l->sending, len);
|
|
return BLE_ERROR;
|
|
}
|
|
try {
|
|
DEBUG_TRACE(CAT, "BLE WinRT TX fragment link=%llu outgoing=%d bytes=%zu", (unsigned long long)id, l->outgoing, len);
|
|
l->sending = true; auto buffer = WinState::buffer(data, len);
|
|
if (l->outgoing) s.await("write frame", l->rx.WriteValueWithResultAsync(buffer, GattWriteOption::WriteWithResponse), id,
|
|
[id, len](WinState& s, GattWriteResult const& result) {
|
|
auto* l = s.link(id); l->sending = false;
|
|
auto protocol = result.ProtocolError();
|
|
if (result.Status() != GattCommunicationStatus::Success)
|
|
DEBUG_WARN(CAT, "BLE WinRT write failed link=%llu bytes=%zu status=%d ATT_error=%d", (unsigned long long)id,
|
|
len, (int)result.Status(), protocol ? int(protocol.Value()) : -1);
|
|
else DEBUG_TRACE(CAT, "BLE WinRT write complete link=%llu bytes=%zu", (unsigned long long)id, len);
|
|
ble_backend_sent(s.owner, id, result.Status() == GattCommunicationStatus::Success);
|
|
});
|
|
else s.await("indicate frame", s.local_tx.NotifyValueAsync(buffer, l->subscriber), id, [id, len](WinState& s, GattClientNotificationResult const& result) {
|
|
auto* l = s.link(id); l->sending = false;
|
|
if (result.Status() != GattCommunicationStatus::Success || result.BytesSent() != len)
|
|
DEBUG_WARN(CAT, "BLE WinRT indication failed link=%llu expected=%zu sent=%u status=%d", (unsigned long long)id,
|
|
len, result.BytesSent(), (int)result.Status());
|
|
else DEBUG_TRACE(CAT, "BLE WinRT indication complete link=%llu bytes=%zu", (unsigned long long)id, len);
|
|
ble_backend_sent(s.owner, id, result.Status() == GattCommunicationStatus::Success && result.BytesSent() == len);
|
|
});
|
|
return BLE_OK;
|
|
} catch (...) { l->sending = false; DEBUG_ERROR(CAT, "%s", exception_reason("send", id).c_str()); return BLE_ERROR; }
|
|
}
|