Implement BLE peripheral support for Windows; add necessary structures and methods
This commit is contained in:
@@ -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);
|
||||||
|
|
||||||
|
|||||||
@@ -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> ¤t_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 ¬ification_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 {
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
Reference in New Issue
Block a user