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:
@@ -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;
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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';
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user