Implement BLE peripheral support for Windows; add necessary structures and methods
Pull Request / test (push) Has been cancelled
Pull Request / pana (push) Has been cancelled

This commit is contained in:
Tony
2026-05-08 07:50:32 +08:00
parent c8e0c60c24
commit 784c6f3929
3 changed files with 899 additions and 13 deletions
+4 -2
View File
@@ -58,7 +58,7 @@ A cross-platform (Android/iOS/macOS/Windows/Linux/Web) Bluetooth Low Energy (BLE
| requestConnectionPriority | ✔️ | ❌ | ❌ | ❌ | ❌ | ❌ | | requestConnectionPriority | ✔️ | ❌ | ❌ | ❌ | ❌ | ❌ |
| readRssi | ✔️ | ✔️ | ✔️ | ❌ | 🚧 | ❌ | | readRssi | ✔️ | ✔️ | ✔️ | ❌ | 🚧 | ❌ |
| requestPermissions | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | | requestPermissions | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ |
| peripheral/GATT server | ✔️ | ✔️ | ✔️ | 🚧 | ✔️ | ❌ | | peripheral/GATT server | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ❌ |
## Getting Started ## Getting Started
@@ -342,12 +342,14 @@ await characteristic.unsubscribe();
## Peripheral Mode ## Peripheral Mode
`UniversalBlePeripheral` exposes a local GATT-server API for apps that need to advertise services and accept central/client writes. Android, iOS, macOS, and Linux are implemented; Windows currently reports `notSupported` until its platform server implementation is added. Web browsers do not expose a standard GATT-server API. `UniversalBlePeripheral` exposes a local GATT-server API for apps that need to advertise services and accept central/client writes. Android, iOS, macOS, Linux, and Windows are implemented. Web browsers do not expose a standard GATT-server API.
On iOS and macOS, peripheral mode uses CoreBluetooth `CBPeripheralManager`. CoreBluetooth manages CCCD subscription state internally, and central connection events are reported when a central reads, writes, subscribes, or unsubscribes from a local characteristic. On iOS and macOS, peripheral mode uses CoreBluetooth `CBPeripheralManager`. CoreBluetooth manages CCCD subscription state internally, and central connection events are reported when a central reads, writes, subscribes, or unsubscribes from a local characteristic.
On Linux, peripheral mode uses BlueZ `GattManager1` and `LEAdvertisingManager1`. The Bluetooth adapter must be powered, support the peripheral role, and the app process must be allowed to register GATT applications and LE advertisements on the system bus. On Linux, peripheral mode uses BlueZ `GattManager1` and `LEAdvertisingManager1`. The Bluetooth adapter must be powered, support the peripheral role, and the app process must be allowed to register GATT applications and LE advertisements on the system bus.
On Windows, peripheral mode uses WinRT `GattServiceProvider` and `GattLocalCharacteristic`. The Bluetooth adapter must support the LE peripheral role and Bluetooth must be turned on. Windows auto-generates CCCD for notify/indicate characteristics, uses the system Bluetooth device name instead of the requested `advertisedName`, and requires the `bluetooth` device capability when the app is packaged as MSIX/UWP.
```dart ```dart
await UniversalBle.requestPermissions(withAndroidBluetoothAdvertise: true); await UniversalBle.requestPermissions(withAndroidBluetoothAdvertise: true);
+844 -8
View File
@@ -35,6 +35,189 @@ const auto device_address_key = L"System.Devices.Aep.DeviceAddress";
const auto signal_strength_key = L"System.Devices.Aep.SignalStrength"; const auto signal_strength_key = L"System.Devices.Aep.SignalStrength";
static std::unique_ptr<UniversalBleCallbackChannel> callback_channel; static std::unique_ptr<UniversalBleCallbackChannel> callback_channel;
namespace {
constexpr auto kPeripheralExtendedPropertiesDescriptorUuid =
"00002900-0000-1000-8000-00805f9b34fb";
constexpr auto kPeripheralUserDescriptionDescriptorUuid =
"00002901-0000-1000-8000-00805f9b34fb";
constexpr auto kPeripheralClientConfigurationDescriptorUuid =
"00002902-0000-1000-8000-00805f9b34fb";
constexpr auto kPeripheralPresentationFormatDescriptorUuid =
"00002904-0000-1000-8000-00805f9b34fb";
constexpr auto kPeripheralAggregateFormatDescriptorUuid =
"00002905-0000-1000-8000-00805f9b34fb";
std::string normalize_uuid_string(const std::string &uuid) {
return guid_to_uuid(uuid_to_guid(uuid));
}
std::optional<int64_t>
encodable_to_int64(const flutter::EncodableValue &value) {
if (const auto *v = std::get_if<int32_t>(&value)) {
return static_cast<int64_t>(*v);
}
if (const auto *v = std::get_if<int64_t>(&value)) {
return *v;
}
return std::nullopt;
}
bool encodable_list_contains_int(const flutter::EncodableList &list,
const int64_t target) {
return std::any_of(list.begin(), list.end(), [target](const auto &value) {
const auto parsed_value = encodable_to_int64(value);
return parsed_value.has_value() && parsed_value.value() == target;
});
}
GattCharacteristicProperties
to_peripheral_properties(const flutter::EncodableList &properties) {
auto flags = GattCharacteristicProperties::None;
for (const auto &value : properties) {
const auto parsed_value = encodable_to_int64(value);
if (!parsed_value.has_value()) {
continue;
}
switch (static_cast<CharacteristicProperty>(parsed_value.value())) {
case CharacteristicProperty::broadcast:
throw create_flutter_error(
UniversalBleErrorCode::kNotSupported,
"Broadcast characteristic property is not supported on Windows");
case CharacteristicProperty::read:
flags = flags | GattCharacteristicProperties::Read;
break;
case CharacteristicProperty::writeWithoutResponse:
flags = flags | GattCharacteristicProperties::WriteWithoutResponse;
break;
case CharacteristicProperty::write:
flags = flags | GattCharacteristicProperties::Write;
break;
case CharacteristicProperty::notify:
flags = flags | GattCharacteristicProperties::Notify;
break;
case CharacteristicProperty::indicate:
flags = flags | GattCharacteristicProperties::Indicate;
break;
case CharacteristicProperty::authenticatedSignedWrites:
flags = flags | GattCharacteristicProperties::AuthenticatedSignedWrites;
break;
case CharacteristicProperty::extendedProperties:
flags = flags | GattCharacteristicProperties::ExtendedProperties;
break;
}
}
return flags;
}
bool is_generated_descriptor_uuid(const std::string &uuid) {
const auto normalized_uuid = normalize_uuid_string(uuid);
return normalized_uuid == kPeripheralExtendedPropertiesDescriptorUuid ||
normalized_uuid == kPeripheralUserDescriptionDescriptorUuid ||
normalized_uuid == kPeripheralClientConfigurationDescriptorUuid ||
normalized_uuid == kPeripheralPresentationFormatDescriptorUuid ||
normalized_uuid == kPeripheralAggregateFormatDescriptorUuid;
}
bool advertisement_status_is_started(
const GattServiceProviderAdvertisementStatus status) {
return status == GattServiceProviderAdvertisementStatus::Started ||
status ==
GattServiceProviderAdvertisementStatus::StartedWithoutAllAdvertisementData;
}
std::string bluetooth_error_to_string(const 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::OtherError:
return "OtherError";
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";
}
return "UnknownBluetoothError";
}
FlutterError create_flutter_error_from_bluetooth_error(
const BluetoothError error, const std::string &message) {
UniversalBleErrorCode error_code = UniversalBleErrorCode::kFailed;
switch (error) {
case BluetoothError::RadioNotAvailable:
case BluetoothError::DisabledByUser:
error_code = UniversalBleErrorCode::kBluetoothNotEnabled;
break;
case BluetoothError::ResourceInUse:
error_code = UniversalBleErrorCode::kOperationInProgress;
break;
case BluetoothError::DeviceNotConnected:
error_code = UniversalBleErrorCode::kDeviceDisconnected;
break;
case BluetoothError::DisabledByPolicy:
case BluetoothError::ConsentRequired:
error_code = UniversalBleErrorCode::kAccessDenied;
break;
case BluetoothError::NotSupported:
case BluetoothError::TransportNotSupported:
error_code = UniversalBleErrorCode::kNotSupported;
break;
case BluetoothError::OtherError:
case BluetoothError::Success:
error_code = UniversalBleErrorCode::kFailed;
break;
}
auto final_message = message;
if (final_message.empty()) {
final_message = bluetooth_error_to_string(error);
}
return create_flutter_error(error_code, final_message,
bluetooth_error_to_string(error));
}
std::string peripheral_session_device_id(const GattSession &session) {
return to_string(session.DeviceId().Id());
}
std::vector<uint8_t>
merge_peripheral_write_value(const std::vector<uint8_t> &current_value,
const std::vector<uint8_t> &incoming_value,
const uint32_t offset) {
if (offset == 0) {
return incoming_value;
}
auto updated_value = current_value;
if (updated_value.size() < offset) {
updated_value.resize(offset);
}
const auto new_size = static_cast<size_t>(offset) + incoming_value.size();
if (updated_value.size() < new_size) {
updated_value.resize(new_size);
}
std::copy(incoming_value.begin(), incoming_value.end(),
updated_value.begin() + offset);
return updated_value;
}
} // namespace
void UniversalBlePlugin::RegisterWithRegistrar( void UniversalBlePlugin::RegisterWithRegistrar(
flutter::PluginRegistrarWindows *registrar) { flutter::PluginRegistrarWindows *registrar) {
auto plugin = std::make_unique<UniversalBlePlugin>(registrar); auto plugin = std::make_unique<UniversalBlePlugin>(registrar);
@@ -50,7 +233,7 @@ UniversalBlePlugin::UniversalBlePlugin(
InitializeAsync(); InitializeAsync();
} }
UniversalBlePlugin::~UniversalBlePlugin() = default; UniversalBlePlugin::~UniversalBlePlugin() { ResetState(); }
// UniversalBlePlatformChannel implementation. // UniversalBlePlatformChannel implementation.
void UniversalBlePlugin::GetBluetoothAvailabilityState( void UniversalBlePlugin::GetBluetoothAvailabilityState(
@@ -445,18 +628,461 @@ void UniversalBlePlugin::RequestConnectionPriority(
"requestConnectionPriority is not supported on Windows platform")); "requestConnectionPriority is not supported on Windows platform"));
} }
ErrorOr<bool> UniversalBlePlugin::IsPeripheralSupported() { return false; } ErrorOr<bool> UniversalBlePlugin::IsPeripheralSupported() {
try {
if (!bluetooth_adapter_) {
bluetooth_adapter_ = async_get(BluetoothAdapter::GetDefaultAsync());
}
if (!bluetooth_adapter_ || !bluetooth_adapter_.IsLowEnergySupported() ||
!bluetooth_adapter_.IsPeripheralRoleSupported()) {
return false;
}
if (!bluetooth_radio_) {
return false;
}
return bluetooth_radio_.State() == RadioState::On;
} catch (...) {
return false;
}
}
void UniversalBlePlugin::StartPeripheral( void UniversalBlePlugin::StartPeripheral(
const UniversalBlePeripheralConfig &config, const UniversalBlePeripheralConfig &config,
std::function<void(std::optional<FlutterError> reply)> result) { std::function<void(std::optional<FlutterError> reply)> result) {
if (!bluetooth_radio_ || bluetooth_radio_.State() != RadioState::On) {
result(create_flutter_error(UniversalBleErrorCode::kBluetoothNotEnabled,
"Bluetooth not enabled"));
return;
}
try {
if (!bluetooth_adapter_) {
bluetooth_adapter_ = async_get(BluetoothAdapter::GetDefaultAsync());
}
if (!bluetooth_adapter_ || !bluetooth_adapter_.IsLowEnergySupported() ||
!bluetooth_adapter_.IsPeripheralRoleSupported()) {
result(create_flutter_error( result(create_flutter_error(
UniversalBleErrorCode::kNotSupported, UniversalBleErrorCode::kNotSupported,
"BLE peripheral mode is not implemented on Windows platform yet")); "BLE peripheral mode is not supported by this Windows adapter"));
return;
}
} catch (const FlutterError &err) {
result(err);
return;
}
if (peripheral_start_in_progress_) {
result(create_flutter_error(UniversalBleErrorCode::kOperationInProgress,
"Peripheral start already in progress"));
return;
}
peripheral_start_in_progress_ = true;
StopPeripheralInternal();
[this, config, result = std::move(result)]() mutable -> fire_and_forget {
try {
if (!config.advertised_name().empty()) {
UniversalBleLogger::LogWarning(
"Windows GATT server ignores advertisedName and uses the system Bluetooth name");
}
for (const auto &service_value : config.services()) {
const auto &service_config = std::any_cast<const UniversalBlePeripheralService &>(
std::get<flutter::CustomEncodableValue>(service_value));
const auto service_uuid = normalize_uuid_string(service_config.uuid());
auto service_provider_result =
co_await GattServiceProvider::CreateAsync(uuid_to_guid(service_uuid));
if (service_provider_result.Error() != BluetoothError::Success) {
throw create_flutter_error_from_bluetooth_error(
service_provider_result.Error(),
"Failed to create peripheral service " + service_uuid);
}
PeripheralServiceObject service_object;
service_object.uuid = service_uuid;
service_object.provider = service_provider_result.ServiceProvider();
service_object.service = service_object.provider.Service();
service_object.advertisement_status_changed_token =
service_object.provider.AdvertisementStatusChanged(
[service_uuid](const GattServiceProvider &,
const GattServiceProviderAdvertisementStatusChangedEventArgs &args) {
UniversalBleLogger::LogInfo(
"PERIPHERAL_ADV_STATUS <- " + service_uuid +
" status=" +
std::to_string(static_cast<int>(args.Status())) +
" error=" +
bluetooth_error_to_string(args.Error()));
});
for (const auto &characteristic_value : service_config.characteristics()) {
const auto &characteristic_config =
std::any_cast<const UniversalBlePeripheralCharacteristic &>(
std::get<flutter::CustomEncodableValue>(characteristic_value));
const auto characteristic_uuid =
normalize_uuid_string(characteristic_config.uuid());
const auto properties =
to_peripheral_properties(characteristic_config.properties());
const bool read_permitted =
encodable_list_contains_int(characteristic_config.permissions(), 0);
const bool write_permitted =
encodable_list_contains_int(characteristic_config.permissions(), 1);
GattLocalCharacteristicParameters parameters;
parameters.CharacteristicProperties(properties);
if (characteristic_config.initial_value() != nullptr) {
parameters.StaticValue(from_bytevc(*characteristic_config.initial_value()));
}
if (read_permitted) {
parameters.ReadProtectionLevel(GattProtectionLevel::Plain);
}
if (write_permitted) {
parameters.WriteProtectionLevel(GattProtectionLevel::Plain);
}
auto characteristic_result = co_await service_object.service
.CreateCharacteristicAsync(
uuid_to_guid(characteristic_uuid),
parameters);
if (characteristic_result.Error() != BluetoothError::Success) {
throw create_flutter_error_from_bluetooth_error(
characteristic_result.Error(),
"Failed to create peripheral characteristic " +
characteristic_uuid);
}
PeripheralCharacteristicObject characteristic_object;
characteristic_object.service_uuid = service_uuid;
characteristic_object.uuid = characteristic_uuid;
characteristic_object.obj = characteristic_result.Characteristic();
characteristic_object.properties = properties;
characteristic_object.read_permitted = read_permitted;
characteristic_object.write_permitted = write_permitted;
characteristic_object.value =
characteristic_config.initial_value() != nullptr
? *characteristic_config.initial_value()
: std::vector<uint8_t>{};
if ((properties & GattCharacteristicProperties::Read) !=
GattCharacteristicProperties::None) {
characteristic_object.read_requested_token =
characteristic_object.obj.ReadRequested(
[this, service_uuid, characteristic_uuid](
const GattLocalCharacteristic &sender,
const GattReadRequestedEventArgs &args) {
[this, service_uuid, characteristic_uuid, sender,
args]() -> fire_and_forget {
auto deferral = args.GetDeferral();
try {
const auto device_id =
peripheral_session_device_id(args.Session());
MarkPeripheralClientConnected(device_id);
auto request = co_await args.GetRequestAsync();
if (!request ||
request.State() == GattRequestState::Canceled) {
deferral.Complete();
co_return;
}
auto *characteristic = FindPeripheralCharacteristic(
service_uuid, characteristic_uuid);
if (characteristic == nullptr) {
request.RespondWithProtocolError(
GattProtocolError::AttributeNotFound());
deferral.Complete();
co_return;
}
if (!characteristic->read_permitted) {
request.RespondWithProtocolError(
GattProtocolError::ReadNotPermitted());
deferral.Complete();
co_return;
}
if (request.Offset() > characteristic->value.size()) {
request.RespondWithProtocolError(
GattProtocolError::InvalidOffset());
deferral.Complete();
co_return;
}
const auto begin = characteristic->value.begin() +
static_cast<std::ptrdiff_t>(
request.Offset());
const auto response_bytes = std::vector<uint8_t>(
begin, characteristic->value.end());
request.RespondWithValue(from_bytevc(response_bytes));
} catch (const hresult_error &err) {
UniversalBleLogger::LogError(
"PERIPHERAL_READ_FAILED <- " + service_uuid +
" " + characteristic_uuid +
" hr=" + std::to_string(err.code()) +
" msg=" + to_string(err.message()));
} catch (const std::exception &ex) {
UniversalBleLogger::LogError(
std::string("PERIPHERAL_READ_FAILED <- ") +
service_uuid + " " + characteristic_uuid +
" error=" + ex.what());
} catch (...) {
UniversalBleLogger::LogError(
"PERIPHERAL_READ_FAILED <- unknown error");
}
deferral.Complete();
}();
});
}
if ((properties & (GattCharacteristicProperties::Write |
GattCharacteristicProperties::WriteWithoutResponse)) !=
GattCharacteristicProperties::None) {
characteristic_object.write_requested_token =
characteristic_object.obj.WriteRequested(
[this, service_uuid, characteristic_uuid](
const GattLocalCharacteristic &sender,
const GattWriteRequestedEventArgs &args) {
[this, service_uuid, characteristic_uuid, sender,
args]() -> fire_and_forget {
auto deferral = args.GetDeferral();
try {
const auto device_id =
peripheral_session_device_id(args.Session());
MarkPeripheralClientConnected(device_id);
auto request = co_await args.GetRequestAsync();
if (!request ||
request.State() == GattRequestState::Canceled) {
deferral.Complete();
co_return;
}
auto *characteristic = FindPeripheralCharacteristic(
service_uuid, characteristic_uuid);
if (characteristic == nullptr) {
if (request.Option() ==
GattWriteOption::WriteWithResponse) {
request.RespondWithProtocolError(
GattProtocolError::AttributeNotFound());
}
deferral.Complete();
co_return;
}
if (!characteristic->write_permitted) {
if (request.Option() ==
GattWriteOption::WriteWithResponse) {
request.RespondWithProtocolError(
GattProtocolError::WriteNotPermitted());
}
deferral.Complete();
co_return;
}
const auto request_value = to_bytevc(request.Value());
if (request.Offset() > characteristic->value.size()) {
if (request.Option() ==
GattWriteOption::WriteWithResponse) {
request.RespondWithProtocolError(
GattProtocolError::InvalidOffset());
}
deferral.Complete();
co_return;
}
characteristic->value = merge_peripheral_write_value(
characteristic->value, request_value,
static_cast<uint32_t>(request.Offset()));
if (request.Option() ==
GattWriteOption::WriteWithResponse) {
request.Respond();
}
NotifyPeripheralWriteEvent(
UniversalBlePeripheralWriteEvent(
device_id, characteristic->service_uuid,
characteristic->uuid, request_value));
} catch (const hresult_error &err) {
UniversalBleLogger::LogError(
"PERIPHERAL_WRITE_FAILED <- " + service_uuid +
" " + characteristic_uuid +
" hr=" + std::to_string(err.code()) +
" msg=" + to_string(err.message()));
} catch (const std::exception &ex) {
UniversalBleLogger::LogError(
std::string("PERIPHERAL_WRITE_FAILED <- ") +
service_uuid + " " + characteristic_uuid +
" error=" + ex.what());
} catch (...) {
UniversalBleLogger::LogError(
"PERIPHERAL_WRITE_FAILED <- unknown error");
}
deferral.Complete();
}();
});
}
if ((properties & (GattCharacteristicProperties::Notify |
GattCharacteristicProperties::Indicate)) !=
GattCharacteristicProperties::None) {
characteristic_object.subscribed_clients_changed_token =
characteristic_object.obj.SubscribedClientsChanged(
[this, service_uuid, characteristic_uuid](
const GattLocalCharacteristic &sender,
const IInspectable &) {
try {
auto *characteristic = FindPeripheralCharacteristic(
service_uuid, characteristic_uuid);
if (characteristic == nullptr) {
return;
}
std::unordered_set<std::string> next_client_ids;
for (const auto &client : sender.SubscribedClients()) {
const auto device_id =
peripheral_session_device_id(client.Session());
next_client_ids.insert(device_id);
if (characteristic->subscribed_client_ids.insert(device_id)
.second) {
MarkPeripheralClientConnected(device_id);
NotifyPeripheralSubscriptionChanged(
device_id, characteristic->service_uuid,
characteristic->uuid, true);
}
}
std::vector<std::string> removed_client_ids;
for (const auto &device_id :
characteristic->subscribed_client_ids) {
if (next_client_ids.count(device_id) == 0) {
removed_client_ids.push_back(device_id);
}
}
for (const auto &device_id : removed_client_ids) {
characteristic->subscribed_client_ids.erase(device_id);
NotifyPeripheralSubscriptionChanged(
device_id, characteristic->service_uuid,
characteristic->uuid, false);
MaybeMarkPeripheralClientDisconnected(device_id);
}
} catch (const hresult_error &err) {
UniversalBleLogger::LogError(
"PERIPHERAL_SUBSCRIBE_FAILED <- " + service_uuid +
" " + characteristic_uuid +
" hr=" + std::to_string(err.code()) +
" msg=" + to_string(err.message()));
} catch (const std::exception &ex) {
UniversalBleLogger::LogError(
std::string("PERIPHERAL_SUBSCRIBE_FAILED <- ") +
service_uuid + " " + characteristic_uuid +
" error=" + ex.what());
} catch (...) {
UniversalBleLogger::LogError(
"PERIPHERAL_SUBSCRIBE_FAILED <- unknown error");
}
});
}
for (const auto &descriptor_value :
characteristic_config.descriptors()) {
const auto &descriptor_config =
std::any_cast<const UniversalBlePeripheralDescriptor &>(
std::get<flutter::CustomEncodableValue>(descriptor_value));
const auto descriptor_uuid =
normalize_uuid_string(descriptor_config.uuid());
if (is_generated_descriptor_uuid(descriptor_uuid)) {
continue;
}
GattLocalDescriptorParameters descriptor_parameters;
if (descriptor_config.initial_value() != nullptr) {
descriptor_parameters.StaticValue(
from_bytevc(*descriptor_config.initial_value()));
}
if (encodable_list_contains_int(descriptor_config.permissions(), 0)) {
descriptor_parameters.ReadProtectionLevel(
GattProtectionLevel::Plain);
}
if (encodable_list_contains_int(descriptor_config.permissions(), 1)) {
descriptor_parameters.WriteProtectionLevel(
GattProtectionLevel::Plain);
}
auto descriptor_result =
co_await characteristic_object.obj.CreateDescriptorAsync(
uuid_to_guid(descriptor_uuid), descriptor_parameters);
if (descriptor_result.Error() != BluetoothError::Success) {
throw create_flutter_error_from_bluetooth_error(
descriptor_result.Error(),
"Failed to create peripheral descriptor " + descriptor_uuid);
}
PeripheralDescriptorObject descriptor_object;
descriptor_object.obj = descriptor_result.Descriptor();
descriptor_object.value =
descriptor_config.initial_value() != nullptr
? *descriptor_config.initial_value()
: std::vector<uint8_t>{};
characteristic_object.descriptors.insert_or_assign(descriptor_uuid,
descriptor_object);
}
service_object.characteristics.insert_or_assign(characteristic_uuid,
std::move(characteristic_object));
}
peripheral_services_.insert_or_assign(service_uuid, std::move(service_object));
auto &inserted_service = peripheral_services_.at(service_uuid);
GattServiceProviderAdvertisingParameters advertising_parameters;
advertising_parameters.IsConnectable(true);
advertising_parameters.IsDiscoverable(true);
inserted_service.provider.StartAdvertising(advertising_parameters);
const auto advertisement_status =
inserted_service.provider.AdvertisementStatus();
if (!advertisement_status_is_started(advertisement_status)) {
throw create_flutter_error(
UniversalBleErrorCode::kFailed,
"Failed to start peripheral advertising for service " +
service_uuid,
std::to_string(static_cast<int>(advertisement_status)));
}
}
peripheral_start_in_progress_ = false;
result(std::nullopt);
} catch (const FlutterError &err) {
StopPeripheralInternal();
peripheral_start_in_progress_ = false;
result(err);
} catch (const hresult_error &err) {
StopPeripheralInternal();
peripheral_start_in_progress_ = false;
result(create_flutter_error(UniversalBleErrorCode::kFailed,
to_string(err.message()),
std::to_string(err.code())));
} catch (const std::exception &ex) {
StopPeripheralInternal();
peripheral_start_in_progress_ = false;
result(create_flutter_error(UniversalBleErrorCode::kFailed, ex.what()));
} catch (...) {
StopPeripheralInternal();
peripheral_start_in_progress_ = false;
result(create_flutter_unknown_error());
}
}();
} }
void UniversalBlePlugin::StopPeripheral( void UniversalBlePlugin::StopPeripheral(
std::function<void(std::optional<FlutterError> reply)> result) { std::function<void(std::optional<FlutterError> reply)> result) {
StopPeripheralInternal();
result(std::nullopt); result(std::nullopt);
} }
@@ -464,18 +1090,80 @@ void UniversalBlePlugin::UpdatePeripheralCharacteristicValue(
const std::string &service, const std::string &characteristic, const std::string &service, const std::string &characteristic,
const std::vector<uint8_t> &value, const std::vector<uint8_t> &value,
std::function<void(std::optional<FlutterError> reply)> result) { std::function<void(std::optional<FlutterError> reply)> result) {
result(create_flutter_error( auto *peripheral_characteristic =
UniversalBleErrorCode::kNotSupported, FindPeripheralCharacteristic(service, characteristic);
"BLE peripheral mode is not implemented on Windows platform yet")); if (peripheral_characteristic == nullptr) {
result(create_flutter_error(UniversalBleErrorCode::kCharacteristicNotFound,
"Unknown peripheral characteristic " +
characteristic));
return;
}
peripheral_characteristic->value = value;
result(std::nullopt);
} }
void UniversalBlePlugin::NotifyPeripheralCharacteristic( void UniversalBlePlugin::NotifyPeripheralCharacteristic(
const std::string &service, const std::string &characteristic, const std::string &service, const std::string &characteristic,
const std::vector<uint8_t> &value, bool indicate, const std::vector<uint8_t> &value, bool indicate,
std::function<void(std::optional<FlutterError> reply)> result) { std::function<void(std::optional<FlutterError> reply)> result) {
auto *peripheral_characteristic =
FindPeripheralCharacteristic(service, characteristic);
if (peripheral_characteristic == nullptr) {
result(create_flutter_error(UniversalBleErrorCode::kCharacteristicNotFound,
"Unknown peripheral characteristic " +
characteristic));
return;
}
const auto supports_notify =
(peripheral_characteristic->properties &
GattCharacteristicProperties::Notify) !=
GattCharacteristicProperties::None;
const auto supports_indicate =
(peripheral_characteristic->properties &
GattCharacteristicProperties::Indicate) !=
GattCharacteristicProperties::None;
if (indicate && !supports_indicate) {
result(create_flutter_error( result(create_flutter_error(
UniversalBleErrorCode::kNotSupported, UniversalBleErrorCode::kCharacteristicDoesNotSupportIndicate,
"BLE peripheral mode is not implemented on Windows platform yet")); "Characteristic does not support indicate"));
return;
}
if (!indicate && !supports_notify) {
result(create_flutter_error(
UniversalBleErrorCode::kCharacteristicDoesNotSupportNotify,
"Characteristic does not support notify"));
return;
}
peripheral_characteristic->value = value;
const auto gatt_characteristic = peripheral_characteristic->obj;
[gatt_characteristic, value,
result = std::move(result)]() mutable -> fire_and_forget {
try {
const auto notification_results =
co_await gatt_characteristic.NotifyValueAsync(from_bytevc(value));
for (const auto &notification_result : notification_results) {
if (notification_result.Status() != GattCommunicationStatus::Success) {
result(create_flutter_error_from_gatt_communication_status(
notification_result.Status()));
co_return;
}
}
result(std::nullopt);
} catch (const FlutterError &err) {
result(err);
} catch (const hresult_error &err) {
result(create_flutter_error(UniversalBleErrorCode::kFailed,
to_string(err.message()),
std::to_string(err.code())));
} catch (...) {
result(create_flutter_unknown_error());
}
}();
} }
void UniversalBlePlugin::ReadRssi( void UniversalBlePlugin::ReadRssi(
@@ -548,6 +1236,11 @@ void UniversalBlePlugin::GetSystemDevices(
/// Helper Methods /// Helper Methods
fire_and_forget UniversalBlePlugin::InitializeAsync() { fire_and_forget UniversalBlePlugin::InitializeAsync() {
try {
bluetooth_adapter_ = co_await BluetoothAdapter::GetDefaultAsync();
} catch (...) {
bluetooth_adapter_ = nullptr;
}
const auto radios = co_await Radio::GetRadiosAsync(); const auto radios = co_await Radio::GetRadiosAsync();
for (auto &&radio : radios) { for (auto &&radio : radios) {
if (radio.Kind() == RadioKind::Bluetooth) { if (radio.Kind() == RadioKind::Bluetooth) {
@@ -1309,12 +2002,155 @@ void UniversalBlePlugin::DisposeServices(
device_agent->gatt_map.clear(); device_agent->gatt_map.clear();
} }
PeripheralCharacteristicObject *UniversalBlePlugin::FindPeripheralCharacteristic(
const std::string &service_uuid, const std::string &characteristic_uuid) {
const auto normalized_service_uuid = normalize_uuid_string(service_uuid);
const auto service_it = peripheral_services_.find(normalized_service_uuid);
if (service_it == peripheral_services_.end()) {
return nullptr;
}
const auto normalized_characteristic_uuid =
normalize_uuid_string(characteristic_uuid);
const auto characteristic_it =
service_it->second.characteristics.find(normalized_characteristic_uuid);
if (characteristic_it == service_it->second.characteristics.end()) {
return nullptr;
}
return &characteristic_it->second;
}
bool UniversalBlePlugin::IsPeripheralClientSubscribed(
const std::string &device_id) const {
for (const auto &[service_uuid, service] : peripheral_services_) {
for (const auto &[characteristic_uuid, characteristic] :
service.characteristics) {
if (characteristic.subscribed_client_ids.count(device_id) != 0) {
return true;
}
}
}
return false;
}
void UniversalBlePlugin::MarkPeripheralClientConnected(
const std::string &device_id) {
if (peripheral_connected_client_ids_.insert(device_id).second) {
NotifyPeripheralConnectionChanged(device_id, true);
}
}
void UniversalBlePlugin::MaybeMarkPeripheralClientDisconnected(
const std::string &device_id) {
if (IsPeripheralClientSubscribed(device_id)) {
return;
}
if (peripheral_connected_client_ids_.erase(device_id) != 0) {
NotifyPeripheralConnectionChanged(device_id, false);
}
}
void UniversalBlePlugin::StopPeripheralInternal() {
std::vector<std::string> disconnected_client_ids(
peripheral_connected_client_ids_.begin(),
peripheral_connected_client_ids_.end());
for (auto &[service_uuid, service] : peripheral_services_) {
for (auto &[characteristic_uuid, characteristic] : service.characteristics) {
if (characteristic.read_requested_token.has_value()) {
try {
characteristic.obj.ReadRequested(
characteristic.read_requested_token.value());
} catch (...) {
}
characteristic.read_requested_token = std::nullopt;
}
if (characteristic.write_requested_token.has_value()) {
try {
characteristic.obj.WriteRequested(
characteristic.write_requested_token.value());
} catch (...) {
}
characteristic.write_requested_token = std::nullopt;
}
if (characteristic.subscribed_clients_changed_token.has_value()) {
try {
characteristic.obj.SubscribedClientsChanged(
characteristic.subscribed_clients_changed_token.value());
} catch (...) {
}
characteristic.subscribed_clients_changed_token = std::nullopt;
}
characteristic.subscribed_client_ids.clear();
characteristic.descriptors.clear();
characteristic.obj = nullptr;
}
service.characteristics.clear();
if (service.advertisement_status_changed_token.has_value()) {
try {
service.provider.AdvertisementStatusChanged(
service.advertisement_status_changed_token.value());
} catch (...) {
}
service.advertisement_status_changed_token = std::nullopt;
}
try {
service.provider.StopAdvertising();
} catch (...) {
}
service.service = nullptr;
service.provider = nullptr;
}
peripheral_services_.clear();
peripheral_connected_client_ids_.clear();
peripheral_start_in_progress_ = false;
for (const auto &device_id : disconnected_client_ids) {
NotifyPeripheralConnectionChanged(device_id, false);
}
}
void UniversalBlePlugin::NotifyPeripheralConnectionChanged(
const std::string &device_id, const bool connected) {
ui_thread_handler_.Post([device_id, connected] {
callback_channel->OnPeripheralConnectionChanged(device_id, connected,
SuccessCallback,
ErrorCallback);
});
}
void UniversalBlePlugin::NotifyPeripheralWriteEvent(
const UniversalBlePeripheralWriteEvent &event) {
ui_thread_handler_.Post([event] {
callback_channel->OnPeripheralWrite(event, SuccessCallback, ErrorCallback);
});
}
void UniversalBlePlugin::NotifyPeripheralSubscriptionChanged(
const std::string &device_id, const std::string &service_uuid,
const std::string &characteristic_uuid, const bool subscribed) {
ui_thread_handler_.Post([device_id, service_uuid, characteristic_uuid,
subscribed] {
callback_channel->OnPeripheralSubscriptionChanged(
device_id, service_uuid, characteristic_uuid, subscribed,
SuccessCallback, ErrorCallback);
});
}
/** /**
* @brief In some cases, it helps to reset the whole Bluetooth state to get * @brief In some cases, it helps to reset the whole Bluetooth state to get
* rid of any dangling connections, before scanning or connecting. * rid of any dangling connections, before scanning or connecting.
*/ */
void UniversalBlePlugin::ResetState() { void UniversalBlePlugin::ResetState() {
try { try {
StopPeripheralInternal();
// Stop and detach advertisement watcher // Stop and detach advertisement watcher
if (bluetooth_le_watcher_ != nullptr) { if (bluetooth_le_watcher_ != nullptr) {
try { try {
+48
View File
@@ -22,6 +22,8 @@
#include "ui_thread_handler.hpp" #include "ui_thread_handler.hpp"
#include "universal_ble_thread_safe.h" #include "universal_ble_thread_safe.h"
#include <memory> #include <memory>
#include <unordered_set>
#include <vector>
namespace universal_ble { namespace universal_ble {
struct GattCharacteristicObject { struct GattCharacteristicObject {
@@ -65,6 +67,34 @@ struct BluetoothDeviceAgent {
} }
}; };
struct PeripheralDescriptorObject {
GattLocalDescriptor obj = nullptr;
std::vector<uint8_t> value;
};
struct PeripheralCharacteristicObject {
std::string service_uuid;
std::string uuid;
GattLocalCharacteristic obj = nullptr;
GattCharacteristicProperties properties = GattCharacteristicProperties::None;
bool read_permitted = false;
bool write_permitted = false;
std::vector<uint8_t> value;
std::unordered_map<std::string, PeripheralDescriptorObject> descriptors;
std::unordered_set<std::string> subscribed_client_ids;
std::optional<event_token> read_requested_token;
std::optional<event_token> write_requested_token;
std::optional<event_token> subscribed_clients_changed_token;
};
struct PeripheralServiceObject {
std::string uuid;
GattServiceProvider provider = nullptr;
GattLocalService service = nullptr;
std::unordered_map<std::string, PeripheralCharacteristicObject> characteristics;
std::optional<event_token> advertisement_status_changed_token;
};
class UniversalBlePlugin : public flutter::Plugin, class UniversalBlePlugin : public flutter::Plugin,
public UniversalBlePlatformChannel { public UniversalBlePlatformChannel {
public: public:
@@ -101,9 +131,12 @@ private:
RadioState old_radio_state_ = RadioState::Unknown; RadioState old_radio_state_ = RadioState::Unknown;
BluetoothLEAdvertisementWatcher bluetooth_le_watcher_{nullptr}; BluetoothLEAdvertisementWatcher bluetooth_le_watcher_{nullptr};
DeviceWatcher device_watcher_{nullptr}; DeviceWatcher device_watcher_{nullptr};
BluetoothAdapter bluetooth_adapter_{nullptr};
std::unordered_map<uint64_t, std::unique_ptr<BluetoothDeviceAgent>> std::unordered_map<uint64_t, std::unique_ptr<BluetoothDeviceAgent>>
connected_devices_{}; connected_devices_{};
std::unordered_map<std::string, PeripheralServiceObject> peripheral_services_{};
std::unordered_set<std::string> peripheral_connected_client_ids_{};
ThreadSafeMap<std::string, DeviceInformation> device_watcher_devices_{}; ThreadSafeMap<std::string, DeviceInformation> device_watcher_devices_{};
ThreadSafeMap<std::string, UniversalBleScanResult> scan_results_{}; ThreadSafeMap<std::string, UniversalBleScanResult> scan_results_{};
// Maps DeviceInformation.Id() -> MAC address string used as key in // Maps DeviceInformation.Id() -> MAC address string used as key in
@@ -117,6 +150,7 @@ private:
event_token device_watcher_enumeration_completed_token_; event_token device_watcher_enumeration_completed_token_;
event_token device_watcher_stopped_token_; event_token device_watcher_stopped_token_;
event_revoker<IRadio> radio_state_changed_revoker_; event_revoker<IRadio> radio_state_changed_revoker_;
bool peripheral_start_in_progress_ = false;
fire_and_forget InitializeAsync(); fire_and_forget InitializeAsync();
fire_and_forget ConnectAsync(uint64_t bluetooth_address); fire_and_forget ConnectAsync(uint64_t bluetooth_address);
@@ -164,6 +198,20 @@ private:
void ResetState(); void ResetState();
void void
DisposeServices(const std::unique_ptr<BluetoothDeviceAgent> &device_agent); DisposeServices(const std::unique_ptr<BluetoothDeviceAgent> &device_agent);
PeripheralCharacteristicObject *
FindPeripheralCharacteristic(const std::string &service_uuid,
const std::string &characteristic_uuid);
bool IsPeripheralClientSubscribed(const std::string &device_id) const;
void MarkPeripheralClientConnected(const std::string &device_id);
void MaybeMarkPeripheralClientDisconnected(const std::string &device_id);
void StopPeripheralInternal();
void NotifyPeripheralConnectionChanged(const std::string &device_id,
bool connected);
void NotifyPeripheralWriteEvent(const UniversalBlePeripheralWriteEvent &event);
void NotifyPeripheralSubscriptionChanged(const std::string &device_id,
const std::string &service_uuid,
const std::string &characteristic_uuid,
bool subscribed);
void GattCharacteristicValueChanged(const GattCharacteristic &sender, void GattCharacteristicValueChanged(const GattCharacteristic &sender,
const GattValueChangedEventArgs &args); const GattValueChangedEventArgs &args);