Implement characteristic descriptor discovery (#196)

* Implement characteristic descriptor discovery

* Improve Darwin services discovery

* Minor mac fix

* Implement Windows support

* discover descriptors in DiscoverServicesAsync api on windows

* Add withDescriptors in discoverServices api

* Implement withDescriptors for Apple and Android

* Fix ai comment and format pigeon generated file

* Resolve Ai comment

* Capitalize API

* Handle empty services scenario

---------

Co-authored-by: Foti Dim <foti@navideck.com>
This commit is contained in:
Rohit Sangwan
2025-12-10 11:36:04 +05:30
committed by GitHub
parent 09bb22750a
commit 3116962e8c
25 changed files with 718 additions and 201 deletions
+64 -10
View File
@@ -214,9 +214,11 @@ UniversalBleService UniversalBleService::FromEncodableList(const EncodableList&
UniversalBleCharacteristic::UniversalBleCharacteristic(
const std::string& uuid,
const EncodableList& properties)
const EncodableList& properties,
const EncodableList& descriptors)
: uuid_(uuid),
properties_(properties) {}
properties_(properties),
descriptors_(descriptors) {}
const std::string& UniversalBleCharacteristic::uuid() const {
return uuid_;
@@ -236,18 +238,56 @@ void UniversalBleCharacteristic::set_properties(const EncodableList& value_arg)
}
const EncodableList& UniversalBleCharacteristic::descriptors() const {
return descriptors_;
}
void UniversalBleCharacteristic::set_descriptors(const EncodableList& value_arg) {
descriptors_ = value_arg;
}
EncodableList UniversalBleCharacteristic::ToEncodableList() const {
EncodableList list;
list.reserve(2);
list.reserve(3);
list.push_back(EncodableValue(uuid_));
list.push_back(EncodableValue(properties_));
list.push_back(EncodableValue(descriptors_));
return list;
}
UniversalBleCharacteristic UniversalBleCharacteristic::FromEncodableList(const EncodableList& list) {
UniversalBleCharacteristic decoded(
std::get<std::string>(list[0]),
std::get<EncodableList>(list[1]));
std::get<EncodableList>(list[1]),
std::get<EncodableList>(list[2]));
return decoded;
}
// UniversalBleDescriptor
UniversalBleDescriptor::UniversalBleDescriptor(const std::string& uuid)
: uuid_(uuid) {}
const std::string& UniversalBleDescriptor::uuid() const {
return uuid_;
}
void UniversalBleDescriptor::set_uuid(std::string_view value_arg) {
uuid_ = value_arg;
}
EncodableList UniversalBleDescriptor::ToEncodableList() const {
EncodableList list;
list.reserve(1);
list.push_back(EncodableValue(uuid_));
return list;
}
UniversalBleDescriptor UniversalBleDescriptor::FromEncodableList(const EncodableList& list) {
UniversalBleDescriptor decoded(
std::get<std::string>(list[0]));
return decoded;
}
@@ -439,12 +479,15 @@ EncodableValue PigeonInternalCodecSerializer::ReadValueOfType(
return CustomEncodableValue(UniversalBleCharacteristic::FromEncodableList(std::get<EncodableList>(ReadValue(stream))));
}
case 133: {
return CustomEncodableValue(UniversalScanFilter::FromEncodableList(std::get<EncodableList>(ReadValue(stream))));
return CustomEncodableValue(UniversalBleDescriptor::FromEncodableList(std::get<EncodableList>(ReadValue(stream))));
}
case 134: {
return CustomEncodableValue(UniversalManufacturerDataFilter::FromEncodableList(std::get<EncodableList>(ReadValue(stream))));
return CustomEncodableValue(UniversalScanFilter::FromEncodableList(std::get<EncodableList>(ReadValue(stream))));
}
case 135: {
return CustomEncodableValue(UniversalManufacturerDataFilter::FromEncodableList(std::get<EncodableList>(ReadValue(stream))));
}
case 136: {
return CustomEncodableValue(UniversalManufacturerData::FromEncodableList(std::get<EncodableList>(ReadValue(stream))));
}
default:
@@ -476,18 +519,23 @@ void PigeonInternalCodecSerializer::WriteValue(
WriteValue(EncodableValue(std::any_cast<UniversalBleCharacteristic>(*custom_value).ToEncodableList()), stream);
return;
}
if (custom_value->type() == typeid(UniversalScanFilter)) {
if (custom_value->type() == typeid(UniversalBleDescriptor)) {
stream->WriteByte(133);
WriteValue(EncodableValue(std::any_cast<UniversalBleDescriptor>(*custom_value).ToEncodableList()), stream);
return;
}
if (custom_value->type() == typeid(UniversalScanFilter)) {
stream->WriteByte(134);
WriteValue(EncodableValue(std::any_cast<UniversalScanFilter>(*custom_value).ToEncodableList()), stream);
return;
}
if (custom_value->type() == typeid(UniversalManufacturerDataFilter)) {
stream->WriteByte(134);
stream->WriteByte(135);
WriteValue(EncodableValue(std::any_cast<UniversalManufacturerDataFilter>(*custom_value).ToEncodableList()), stream);
return;
}
if (custom_value->type() == typeid(UniversalManufacturerData)) {
stream->WriteByte(135);
stream->WriteByte(136);
WriteValue(EncodableValue(std::any_cast<UniversalManufacturerData>(*custom_value).ToEncodableList()), stream);
return;
}
@@ -788,7 +836,13 @@ void UniversalBlePlatformChannel::SetUp(
return;
}
const auto& device_id_arg = std::get<std::string>(encodable_device_id_arg);
api->DiscoverServices(device_id_arg, [reply](ErrorOr<EncodableList>&& output) {
const auto& encodable_with_descriptors_arg = args.at(1);
if (encodable_with_descriptors_arg.IsNull()) {
reply(WrapError("with_descriptors_arg unexpectedly null."));
return;
}
const auto& with_descriptors_arg = std::get<bool>(encodable_with_descriptors_arg);
api->DiscoverServices(device_id_arg, with_descriptors_arg, [reply](ErrorOr<EncodableList>&& output) {
if (output.has_error()) {
reply(WrapError(output.error()));
return;
+26 -1
View File
@@ -211,7 +211,8 @@ class UniversalBleCharacteristic {
// Constructs an object setting all fields.
explicit UniversalBleCharacteristic(
const std::string& uuid,
const flutter::EncodableList& properties);
const flutter::EncodableList& properties,
const flutter::EncodableList& descriptors);
const std::string& uuid() const;
void set_uuid(std::string_view value_arg);
@@ -219,6 +220,9 @@ class UniversalBleCharacteristic {
const flutter::EncodableList& properties() const;
void set_properties(const flutter::EncodableList& value_arg);
const flutter::EncodableList& descriptors() const;
void set_descriptors(const flutter::EncodableList& value_arg);
private:
static UniversalBleCharacteristic FromEncodableList(const flutter::EncodableList& list);
flutter::EncodableList ToEncodableList() const;
@@ -227,6 +231,26 @@ class UniversalBleCharacteristic {
friend class PigeonInternalCodecSerializer;
std::string uuid_;
flutter::EncodableList properties_;
flutter::EncodableList descriptors_;
};
// Generated class from Pigeon that represents data sent in messages.
class UniversalBleDescriptor {
public:
// Constructs an object setting all fields.
explicit UniversalBleDescriptor(const std::string& uuid);
const std::string& uuid() const;
void set_uuid(std::string_view value_arg);
private:
static UniversalBleDescriptor FromEncodableList(const flutter::EncodableList& list);
flutter::EncodableList ToEncodableList() const;
friend class UniversalBlePlatformChannel;
friend class UniversalBleCallbackChannel;
friend class PigeonInternalCodecSerializer;
std::string uuid_;
};
@@ -366,6 +390,7 @@ class UniversalBlePlatformChannel {
std::function<void(std::optional<FlutterError> reply)> result) = 0;
virtual void DiscoverServices(
const std::string& device_id,
bool with_descriptors,
std::function<void(ErrorOr<flutter::EncodableList> reply)> result) = 0;
virtual void ReadValue(
const std::string& device_id,
+82 -59
View File
@@ -121,9 +121,9 @@ void UniversalBlePlugin::DisableBluetooth(
void UniversalBlePlugin::RequestPermissions(
bool with_android_fine_location,
std::function<void(std::optional<FlutterError> reply)> result) {
// Windows does not require runtime permissions for Bluetooth
result(std::nullopt);
return;
// Windows does not require runtime permissions for Bluetooth
result(std::nullopt);
return;
}
std::optional<FlutterError>
@@ -223,8 +223,10 @@ std::optional<FlutterError> UniversalBlePlugin::StopScan() {
ErrorOr<bool> UniversalBlePlugin::IsScanning() {
if (bluetooth_le_watcher_ != nullptr) {
try {
return bluetooth_le_watcher_.Status() == BluetoothLEAdvertisementWatcherStatus::Started;
} catch (...) {}
return bluetooth_le_watcher_.Status() ==
BluetoothLEAdvertisementWatcherStatus::Started;
} catch (...) {
}
}
return false;
}
@@ -269,24 +271,9 @@ UniversalBlePlugin::Disconnect(const std::string &device_id) {
}
void UniversalBlePlugin::DiscoverServices(
const std::string &device_id,
const std::string &device_id, bool with_descriptors,
std::function<void(ErrorOr<flutter::EncodableList> reply)> result) {
try {
const auto it = connected_devices_.find(str_to_mac_address(device_id));
if (it == connected_devices_.end()) {
result(create_flutter_error(UniversalBleErrorCode::kDeviceNotFound,
"Unknown devicesId:" + device_id));
return;
}
auto device_agent = *it->second;
DiscoverServicesAsync(device_agent, result);
} catch (const FlutterError &err) {
return result(err);
} catch (...) {
std::cout << "DiscoverServicesLog: Unknown error" << std::endl;
return result(create_flutter_error(UniversalBleErrorCode::kUnknownError,
"Unknown error"));
}
DiscoverServicesAsync(device_id, with_descriptors, result);
}
void UniversalBlePlugin::SetNotifiable(
@@ -369,7 +356,8 @@ void UniversalBlePlugin::WriteValue(
if ((properties & GattCharacteristicProperties::WriteWithoutResponse) ==
GattCharacteristicProperties::None) {
result(create_flutter_error(
UniversalBleErrorCode::kCharacteristicDoesNotSupportWriteWithoutResponse,
UniversalBleErrorCode::
kCharacteristicDoesNotSupportWriteWithoutResponse,
"Characteristic does not support WriteWithoutResponse"));
return;
}
@@ -384,22 +372,22 @@ void UniversalBlePlugin::WriteValue(
}
gatt_characteristic.WriteValueAsync(from_bytevc(value), write_option)
.Completed(
[&, result](IAsyncOperation<GattCommunicationStatus> const &sender,
AsyncStatus const args) {
if (args == AsyncStatus::Error) {
result(create_flutter_error(UniversalBleErrorCode::kFailed,
"Encountered an error."));
return;
}
.Completed([&, result](
IAsyncOperation<GattCommunicationStatus> const &sender,
AsyncStatus const args) {
if (args == AsyncStatus::Error) {
result(create_flutter_error(UniversalBleErrorCode::kFailed,
"Encountered an error."));
return;
}
const auto status = sender.GetResults();
if (status != GattCommunicationStatus::Success) {
result(create_flutter_error_from_gatt_communication_status(status));
} else {
result(std::nullopt);
}
});
const auto status = sender.GetResults();
if (status != GattCommunicationStatus::Success) {
result(create_flutter_error_from_gatt_communication_status(status));
} else {
result(std::nullopt);
}
});
} catch (const FlutterError &err) {
result(err);
} catch (...) {
@@ -548,7 +536,8 @@ fire_and_forget UniversalBlePlugin::PairAsync(
result(is_paired);
const std::string *error_msg = nullptr;
const auto error_str = device_pairing_result_to_string(pair_result.Status());
const auto error_str =
device_pairing_result_to_string(pair_result.Status());
if (error_str.has_value()) {
error_msg = &error_str.value();
}
@@ -1039,18 +1028,18 @@ void UniversalBlePlugin::CleanConnection(const uint64_t bluetooth_address) {
}
}
void UniversalBlePlugin::DisposeServices(const std::unique_ptr<BluetoothDeviceAgent> &device_agent)
{
for (auto& [service_id, service] : device_agent->gatt_map) {
for (auto& [char_id, characteristic] : service.characteristics) {
if (characteristic.subscription_token.has_value()) {
characteristic.obj.ValueChanged(
characteristic.subscription_token.value());
characteristic.subscription_token = std::nullopt;
}
}
void UniversalBlePlugin::DisposeServices(
const std::unique_ptr<BluetoothDeviceAgent> &device_agent) {
for (auto &[service_id, service] : device_agent->gatt_map) {
for (auto &[char_id, characteristic] : service.characteristics) {
if (characteristic.subscription_token.has_value()) {
characteristic.obj.ValueChanged(
characteristic.subscription_token.value());
characteristic.subscription_token = std::nullopt;
}
}
device_agent->gatt_map.clear();
}
device_agent->gatt_map.clear();
}
fire_and_forget UniversalBlePlugin::GetSystemDevicesAsync(
@@ -1108,21 +1097,49 @@ fire_and_forget UniversalBlePlugin::GetSystemDevicesAsync(
}
}
void UniversalBlePlugin::DiscoverServicesAsync(
BluetoothDeviceAgent &bluetooth_device_agent,
const std::function<void(ErrorOr<flutter::EncodableList> reply)> &result) {
fire_and_forget UniversalBlePlugin::DiscoverServicesAsync(
const std::string &device_id, bool with_descriptors,
std::function<void(ErrorOr<flutter::EncodableList> reply)> result) {
try {
const auto it = connected_devices_.find(str_to_mac_address(device_id));
if (it == connected_devices_.end()) {
result(create_flutter_error(UniversalBleErrorCode::kDeviceNotFound,
"Unknown devicesId:" + device_id));
co_return;
}
auto universal_services = flutter::EncodableList();
for (auto &[service_id, service] : bluetooth_device_agent.gatt_map) {
for (auto &[service_id, service] : it->second->gatt_map) {
flutter::EncodableList universal_characteristics;
for (auto [char_id, characteristic] : service.characteristics) {
auto &c = characteristic.obj;
const auto properties_value = c.CharacteristicProperties();
auto properties = properties_to_flutter_encodable(properties_value);
universal_characteristics.push_back(flutter::CustomEncodableValue(
UniversalBleCharacteristic(to_uuidstr(c.Uuid()), properties)));
auto descriptors = flutter::EncodableList();
if (with_descriptors) {
try {
// move continuation to background and execute in safe thread
// context
co_await winrt::resume_background();
auto descriptor_result =
co_await c.GetDescriptorsAsync(BluetoothCacheMode::Cached);
if (descriptor_result.Status() ==
GattCommunicationStatus::Success) {
auto descriptors_list = descriptor_result.Descriptors();
for (auto &&descriptor : descriptors_list) {
descriptors.push_back(flutter::CustomEncodableValue(
UniversalBleDescriptor(to_uuidstr(descriptor.Uuid()))));
}
}
} catch (...) {
std::cout << "DiscoverServicesAsync: failed to get descriptors for "
"characteristic: "
<< std::endl;
}
}
universal_characteristics.push_back(
flutter::CustomEncodableValue(UniversalBleCharacteristic(
to_uuidstr(c.Uuid()), properties, descriptors)));
}
auto universal_ble_service =
@@ -1132,10 +1149,16 @@ void UniversalBlePlugin::DiscoverServicesAsync(
flutter::CustomEncodableValue(universal_ble_service));
}
result(universal_services);
} catch (const hresult_error &err) {
const int error_code = err.code();
result(create_flutter_error(UniversalBleErrorCode::kFailed,
"DiscoverServicesAsync failed",
std::to_string(error_code)));
} catch (const FlutterError &err) {
result(err);
} catch (...) {
result(create_flutter_error(UniversalBleErrorCode::kUnknownError,
"Unknown error"));
std::cout << "DiscoverServiceError: Unknown error" << '\n';
}
}
+4 -1
View File
@@ -134,8 +134,10 @@ namespace universal_ble
std::vector<std::string> with_services,
std::function<void(ErrorOr<flutter::EncodableList> reply)> result);
static fire_and_forget IsPairedAsync(const std::string& device_id, std::function<void(ErrorOr<bool> reply)> result);
fire_and_forget DiscoverServicesAsync(const std::string &device_id,
bool with_descriptors,
std::function<void(ErrorOr<flutter::EncodableList> reply)> result);
static void DiscoverServicesAsync(BluetoothDeviceAgent& bluetooth_device_agent, const std::function<void(ErrorOr<flutter::EncodableList> reply)>&);
void PairingRequestedHandler(DeviceInformationCustomPairing sender, const DevicePairingRequestedEventArgs& event_args);
void RadioStateChanged(const Radio& sender, const IInspectable&);
@@ -167,6 +169,7 @@ namespace universal_ble
std::function<void(std::optional<FlutterError> reply)> result) override;
void DiscoverServices(
const std::string &device_id,
bool with_descriptors,
std::function<void(ErrorOr<flutter::EncodableList> reply)> result) override;
void SetNotifiable(
const std::string &device_id,