Added BLE V2 GATT client implementation for Windows

PiperOrigin-RevId: 511635693
This commit is contained in:
Guogang Li
2023-02-22 17:05:31 -08:00
committed by Copybara-Service
parent 070609883f
commit d260feced5
4 changed files with 512 additions and 0 deletions
@@ -22,6 +22,7 @@
#include <memory>
#include <set>
#include <string>
#include <vector>
#include "absl/container/flat_hash_map.h"
#include "absl/status/status.h"
@@ -56,6 +56,7 @@ cc_library(
name = "comm",
hdrs = [
"ble.h",
"ble_gatt_client.h",
"ble_medium.h",
"ble_peripheral.h",
"ble_socket.h",
@@ -131,6 +132,7 @@ cc_library(
cc_library(
name = "windows",
srcs = [
"ble_gatt_client.cc",
"ble_medium.cc",
"ble_socket.cc",
"ble_v2.cc",
@@ -0,0 +1,440 @@
// Copyright 2023 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "internal/platform/implementation/windows/ble_gatt_client.h"
#include <windows.h>
#include <algorithm>
#include <cctype>
#include <cstring>
#include <exception>
#include <memory>
#include <optional>
#include <string>
#include <vector>
#include "absl/strings/escaping.h"
#include "absl/strings/str_format.h"
#include "absl/strings/str_join.h"
#include "absl/types/optional.h"
#include "internal/platform/implementation/windows/utils.h"
#include "internal/platform/logging.h"
#include "internal/platform/uuid.h"
#include "winrt/Windows.Devices.Bluetooth.GenericAttributeProfile.h"
#include "winrt/Windows.Devices.Bluetooth.h"
#include "winrt/Windows.Foundation.Collections.h"
#include "winrt/Windows.Storage.Streams.h"
namespace nearby {
namespace windows {
namespace {
using ::winrt::Windows::Devices::Bluetooth::BluetoothCacheMode;
using ::winrt::Windows::Devices::Bluetooth::BluetoothLEDevice;
using ::winrt::Windows::Devices::Bluetooth::GenericAttributeProfile::
GattCharacteristic;
using ::winrt::Windows::Devices::Bluetooth::GenericAttributeProfile::
GattCharacteristicProperties;
using ::winrt::Windows::Devices::Bluetooth::GenericAttributeProfile::
GattCharacteristicsResult;
using ::winrt::Windows::Devices::Bluetooth::GenericAttributeProfile::
GattCommunicationStatus;
using ::winrt::Windows::Devices::Bluetooth::GenericAttributeProfile::
GattDeviceService;
using ::winrt::Windows::Devices::Bluetooth::GenericAttributeProfile::
GattDeviceServicesResult;
using ::winrt::Windows::Devices::Bluetooth::GenericAttributeProfile::
GattReadResult;
using ::winrt::Windows::Foundation::Collections::IVectorView;
using ::winrt::Windows::Storage::Streams::Buffer;
using ::winrt::Windows::Storage::Streams::DataReader;
using ::winrt::Windows::Storage::Streams::IBuffer;
} // namespace
BleGattClient::BleGattClient(BluetoothLEDevice ble_device)
: ble_device_(ble_device) {
NEARBY_LOGS(VERBOSE) << __func__ << ": GATT client is created.";
}
BleGattClient::~BleGattClient() {
NEARBY_LOGS(VERBOSE) << __func__ << ": GATT client is released.";
Disconnect();
}
bool BleGattClient::DiscoverServiceAndCharacteristics(
const Uuid& service_uuid, const std::vector<Uuid>& characteristic_uuids) {
std::string flat_characteristics =
absl::StrJoin(characteristic_uuids, ",", [](std::string* out, Uuid uuid) {
absl::StrAppend(out, std::string(uuid));
});
NEARBY_LOGS(VERBOSE) << __func__ << ": Discover service_uuid="
<< std::string(service_uuid)
<< " with characteristic_uuids=" << flat_characteristics;
try {
if (ble_device_ == nullptr) {
NEARBY_LOGS(ERROR) << __func__ << ": BLE device is disconnected.";
return false;
}
// Gets the GATT services on the BLE device.
gatt_devices_services_result_ =
ble_device_.GetGattServicesAsync(BluetoothCacheMode::Uncached).get();
if (gatt_devices_services_result_.Status() !=
GattCommunicationStatus::Success) {
gatt_devices_services_result_ = nullptr;
return false;
}
IVectorView<GattDeviceService> services =
gatt_devices_services_result_.Services();
std::string flat_services = absl::StrJoin(
services, ",", [](std::string* out, GattDeviceService service) {
absl::StrAppend(out,
winrt::to_string(winrt::to_hstring(service.Uuid())));
});
NEARBY_LOGS(VERBOSE) << __func__
<< ": Found GATT services=" << flat_services
<< " from BLE device.";
// Needs to check each service to make sure it includes all characteristic
// uuids. Services may include duplicate service UUID, but each of them may
// have different characteristic.
for (auto&& service : services) {
winrt::guid uuid = service.Uuid();
std::string uuid_string = winrt::to_string(winrt::to_hstring(uuid));
NEARBY_LOGS(VERBOSE) << __func__
<< ": Found service UUID=" << uuid_string;
if (!is_nearby_uuid_equal_to_winrt_guid(service_uuid, uuid)) {
NEARBY_LOGS(WARNING)
<< __func__
<< ": Service uuid not match, continue check other services.";
continue;
}
NEARBY_LOGS(INFO) << __func__
<< ": Found the discovery service UUID=" << uuid_string;
// Try to check the characteristic uuids.
GattCharacteristicsResult gatt_characteristics_result =
service.GetCharacteristicsAsync(BluetoothCacheMode::Uncached).get();
if (gatt_characteristics_result.Status() !=
GattCommunicationStatus::Success) {
NEARBY_LOGS(ERROR) << __func__ << ": Failed to get characteristics.";
continue;
}
std::string flat_characteristics = absl::StrJoin(
gatt_characteristics_result.Characteristics(), ",",
[](std::string* out, GattCharacteristic gatt_characteristic) {
absl::StrAppend(out, winrt::to_string(winrt::to_hstring(
gatt_characteristic.Uuid())));
});
NEARBY_LOGS(VERBOSE) << __func__ << ": Found GATT characteristics="
<< flat_characteristics;
bool found_all = true;
for (const auto& characteristic_uuid : characteristic_uuids) {
bool found = false;
for (const auto& gatt_characteristic :
gatt_characteristics_result.Characteristics()) {
std::string gatt_characteristic_uuid =
winrt::to_string(winrt::to_hstring(gatt_characteristic.Uuid()));
if (is_nearby_uuid_equal_to_winrt_guid(characteristic_uuid,
gatt_characteristic.Uuid())) {
found = true;
break;
}
}
if (found == false) {
NEARBY_LOGS(WARNING) << __func__ << ": Cannot find characteristic: "
<< std::string(characteristic_uuid);
found_all = false;
break;
}
}
if (!found_all) {
continue;
}
// found all characteristics.
NEARBY_LOGS(VERBOSE) << __func__ << ": Found all characteristics.";
break;
}
return true;
} catch (std::exception exception) {
NEARBY_LOGS(ERROR) << __func__
<< ": Failed to get GATT services. exception: "
<< exception.what();
} catch (const winrt::hresult_error& error) {
NEARBY_LOGS(ERROR) << __func__
<< ": Failed to get GATT services. WinRT exception: "
<< error.code() << ": "
<< winrt::to_string(error.message());
}
return false;
}
absl::optional<api::ble_v2::GattCharacteristic>
BleGattClient::GetCharacteristic(const Uuid& service_uuid,
const Uuid& characteristic_uuid) {
NEARBY_LOGS(VERBOSE) << __func__ << ": Stared to get characteristic UUID="
<< std::string(characteristic_uuid)
<< " in service UUID=" << std::string(service_uuid);
try {
std::optional<GattCharacteristic> gatt_characteristic =
GetNativeCharacteristic(service_uuid, characteristic_uuid);
if (!gatt_characteristic.has_value()) {
NEARBY_LOGS(ERROR) << __func__
<< ": Failed to get native GATT characteristic.";
return absl::nullopt;
}
api::ble_v2::GattCharacteristic result;
result.service_uuid = service_uuid;
result.uuid = characteristic_uuid;
// Note: Windows has protection level on GattCharacteristic. cannot
// find a way to map it to api::ble_v2::GattCharacteristic.
GattCharacteristicProperties properties =
gatt_characteristic->CharacteristicProperties();
if (properties == GattCharacteristicProperties::Read) {
result.permissions.push_back(
api::ble_v2::GattCharacteristic::Permission::kRead);
result.properties.push_back(
api::ble_v2::GattCharacteristic::Property::kRead);
} else if (properties == GattCharacteristicProperties::Write) {
result.permissions.push_back(
api::ble_v2::GattCharacteristic::Permission::kWrite);
result.properties.push_back(
api::ble_v2::GattCharacteristic::Property::kWrite);
} else if (properties == GattCharacteristicProperties::Indicate) {
result.permissions.push_back(
api::ble_v2::GattCharacteristic::Permission::kRead);
result.properties.push_back(
api::ble_v2::GattCharacteristic::Property::kIndicate);
}
NEARBY_LOGS(VERBOSE) << __func__ << ": Return Characteristic. uuid="
<< std::string(characteristic_uuid);
return result;
} catch (std::exception exception) {
NEARBY_LOGS(ERROR) << __func__
<< ": Failed to get GATT characteristic. exception: "
<< exception.what();
} catch (const winrt::hresult_error& error) {
NEARBY_LOGS(ERROR)
<< __func__ << ": Failed to get GATT characteristic. WinRT exception: "
<< error.code() << ": " << winrt::to_string(error.message());
}
return absl::nullopt;
}
absl::optional<ByteArray> BleGattClient::ReadCharacteristic(
const api::ble_v2::GattCharacteristic& characteristic) {
NEARBY_LOGS(VERBOSE) << __func__ << ": Read characteristic="
<< std::string(characteristic.uuid);
try {
std::optional<GattCharacteristic> gatt_characteristic =
GetNativeCharacteristic(characteristic.service_uuid,
characteristic.uuid);
if (!gatt_characteristic.has_value()) {
NEARBY_LOGS(ERROR) << __func__
<< ": Failed to get native GATT characteristic.";
return absl::nullopt;
}
GattReadResult result =
gatt_characteristic->ReadValueAsync(BluetoothCacheMode::Uncached).get();
if (result.Status() != GattCommunicationStatus::Success) {
NEARBY_LOGS(ERROR) << __func__
<< ": Failed to read GATT characteristic. status="
<< static_cast<int>(result.Status());
return absl::nullopt;
}
IBuffer buffer = result.Value();
int size = buffer.Length();
if (size == 0) {
NEARBY_LOGS(WARNING) << __func__ << ": No characteristic value.";
return absl::nullopt;
}
DataReader data_reader = DataReader::FromBuffer(buffer);
std::string data;
data.reserve(size);
for (int i = 0; i < size; ++i) {
data.push_back(static_cast<char>(data_reader.ReadByte()));
}
NEARBY_LOGS(VERBOSE) << __func__
<< ": Got characteristic value length=" << data.size();
return ByteArray(data);
} catch (std::exception exception) {
NEARBY_LOGS(ERROR) << __func__
<< ": Failed to read GATT characteristic. exception: "
<< exception.what();
} catch (const winrt::hresult_error& error) {
NEARBY_LOGS(ERROR)
<< __func__ << ": Failed to read GATT characteristic. WinRT exception: "
<< error.code() << ": " << winrt::to_string(error.message());
}
return absl::nullopt;
}
bool BleGattClient::WriteCharacteristic(
const api::ble_v2::GattCharacteristic& characteristic,
const ByteArray& value) {
NEARBY_LOGS(VERBOSE) << __func__ << ": write characteristic: "
<< std::string(characteristic.uuid);
try {
std::optional<GattCharacteristic> gatt_characteristic =
GetNativeCharacteristic(characteristic.service_uuid,
characteristic.uuid);
if (!gatt_characteristic.has_value()) {
NEARBY_LOGS(ERROR) << __func__
<< ": Failed to get native GATT characteristic.";
return false;
}
// Writes data to the characteristic.
Buffer buffer = Buffer(value.size());
std::memcpy(buffer.data(), value.data(), value.size());
buffer.Length(value.size());
GattCommunicationStatus staus =
gatt_characteristic->WriteValueAsync(buffer).get();
if (staus != GattCommunicationStatus::Success) {
NEARBY_LOGS(ERROR) << __func__
<< ": Failed to write data to GATT characteristic: "
<< std::string(characteristic.uuid);
return false;
} else {
NEARBY_LOGS(VERBOSE) << __func__
<< ": Write data to GATT characteristic: "
<< std::string(characteristic.uuid)
<< ", bytes count: " << value.size();
return true;
}
} catch (std::exception exception) {
NEARBY_LOGS(ERROR) << __func__
<< ": Failed to read GATT characteristic. exception: "
<< exception.what();
} catch (const winrt::hresult_error& error) {
NEARBY_LOGS(ERROR)
<< __func__ << ": Failed to read GATT characteristic. WinRT exception: "
<< error.code() << ": " << winrt::to_string(error.message());
}
return false;
}
void BleGattClient::Disconnect() {
try {
NEARBY_LOGS(VERBOSE) << __func__ << ": Disconnect is called.";
if (ble_device_ != nullptr) {
ble_device_.Close();
ble_device_ = nullptr;
}
} catch (std::exception exception) {
NEARBY_LOGS(ERROR) << __func__
<< ": Failed to disconnect GATT device. exception: "
<< exception.what();
} catch (const winrt::hresult_error& error) {
NEARBY_LOGS(ERROR)
<< __func__ << ": Failed to disconnect GATT device. WinRT exception: "
<< error.code() << ": " << winrt::to_string(error.message());
}
}
std::optional<GattCharacteristic> BleGattClient::GetNativeCharacteristic(
const Uuid& service_uuid, const Uuid& characteristic_uuid) {
NEARBY_LOGS(VERBOSE) << __func__
<< ": Stared to get native characteristic UUID="
<< std::string(characteristic_uuid)
<< " in service UUID=" << std::string(service_uuid);
try {
if (ble_device_ == nullptr) {
NEARBY_LOGS(ERROR) << __func__ << ": BLE device is disconnected.";
return absl::nullopt;
}
if (gatt_devices_services_result_ == nullptr) {
NEARBY_LOGS(ERROR) << __func__ << ": No available GATT services.";
return absl::nullopt;
}
for (const auto& service : gatt_devices_services_result_.Services()) {
if (is_nearby_uuid_equal_to_winrt_guid(service_uuid, service.Uuid())) {
GattCharacteristicsResult gatt_characteristics_result =
service.GetCharacteristicsAsync(BluetoothCacheMode::Cached).get();
if (gatt_characteristics_result.Status() !=
GattCommunicationStatus::Success) {
NEARBY_LOGS(ERROR) << __func__ << ": Failed to get characteristics.";
continue;
}
for (const auto& characteristic :
gatt_characteristics_result.Characteristics()) {
if (is_nearby_uuid_equal_to_winrt_guid(characteristic_uuid,
characteristic.Uuid())) {
NEARBY_LOGS(VERBOSE)
<< __func__ << ": Return native Characteristic. uuid="
<< std::string(characteristic_uuid);
return characteristic;
}
}
}
}
NEARBY_LOGS(ERROR) << __func__ << ": Failed to get native characteristic.";
} catch (std::exception exception) {
NEARBY_LOGS(ERROR)
<< __func__ << ": Failed to get native GATT characteristic. exception: "
<< exception.what();
} catch (const winrt::hresult_error& error) {
NEARBY_LOGS(ERROR)
<< __func__
<< ": Failed to get native GATT characteristic. WinRT exception: "
<< error.code() << ": " << winrt::to_string(error.message());
}
return absl::nullopt;
}
} // namespace windows
} // namespace nearby
@@ -0,0 +1,69 @@
// Copyright 2023 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef THIRD_PARTY_NEARBY_INTERNAL_PLATFORM_IMPLEMENTATION_WINDOWS_BLE_GATT_CLIENT_H_
#define THIRD_PARTY_NEARBY_INTERNAL_PLATFORM_IMPLEMENTATION_WINDOWS_BLE_GATT_CLIENT_H_
#include <windows.h>
#include <cstddef>
#include <optional>
#include <vector>
#include "absl/container/flat_hash_map.h"
#include "internal/platform/byte_array.h"
#include "internal/platform/implementation/ble_v2.h"
#include "winrt/Windows.Devices.Bluetooth.GenericAttributeProfile.h"
#include "winrt/Windows.Devices.Bluetooth.h"
namespace nearby {
namespace windows {
class BleGattClient : public api::ble_v2::GattClient {
public:
explicit BleGattClient(
::winrt::Windows::Devices::Bluetooth::BluetoothLEDevice ble_device);
~BleGattClient() override;
bool DiscoverServiceAndCharacteristics(
const Uuid& service_uuid,
const std::vector<Uuid>& characteristic_uuids) override;
absl::optional<api::ble_v2::GattCharacteristic> GetCharacteristic(
const Uuid& service_uuid, const Uuid& characteristic_uuid) override;
absl::optional<ByteArray> ReadCharacteristic(
const api::ble_v2::GattCharacteristic& characteristic) override;
bool WriteCharacteristic(
const api::ble_v2::GattCharacteristic& characteristic,
const ByteArray& value) override;
void Disconnect() override;
private:
std::optional<::winrt::Windows::Devices::Bluetooth::GenericAttributeProfile::
GattCharacteristic>
GetNativeCharacteristic(const Uuid& service_uuid,
const Uuid& characteristic_uuid);
::winrt::Windows::Devices::Bluetooth::BluetoothLEDevice ble_device_;
::winrt::Windows::Devices::Bluetooth::GenericAttributeProfile::
GattDeviceServicesResult gatt_devices_services_result_ = nullptr;
};
} // namespace windows
} // namespace nearby
#endif // THIRD_PARTY_NEARBY_INTERNAL_PLATFORM_IMPLEMENTATION_WINDOWS_BLE_GATT_CLIENT_H_