Add support for GATT clients.

This commit is contained in:
Vibhav Pant
2023-09-13 14:32:44 +05:30
parent 0231dc656a
commit e61a3aa677
10 changed files with 819 additions and 7 deletions
@@ -56,6 +56,7 @@ cc_library(
hdrs = [
"avahi.h",
"ble_gatt_server.h",
"ble_gatt_client.h",
"ble_medium.h",
"ble_v2_medium.h",
"ble_v2_server_socket.h",
@@ -70,8 +71,10 @@ cc_library(
"bluez.h",
"bluez_advertisement_monitor.h",
"bluez_advertisement_monitor_manager.h",
"bluez_gatt_characteristic_client.h",
"bluez_gatt_characteristic_server.h",
"bluez_gatt_manager.h",
"bluez_gatt_service_client.h",
"bluez_gatt_service_server.h",
"bluez_le_advertisement.h",
"dbus.h",
@@ -133,6 +136,7 @@ cc_library(
name = "linux",
srcs = [
"avahi.cc",
"ble_gatt_client.cc",
"ble_gatt_server.cc",
"ble_v2_medium.cc",
"bluetooth_adapter.cc",
@@ -145,6 +149,7 @@ cc_library(
"bluetooth_pairing.cc",
"bluez.cc",
"bluez_advertisement_monitor.cc",
"bluez_gatt_characteristic_client.cc",
"bluez_gatt_characteristic_server.cc",
"bluez_gatt_service_server.cc",
"bluez_le_advertisement.cc",
@@ -0,0 +1,428 @@
// 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 <algorithm>
#include <map>
#include <string>
#include <variant>
#include <sdbus-c++/Types.h>
#include "absl/strings/substitute.h"
#include "absl/synchronization/mutex.h"
#include "internal/platform/cancellation_flag_listener.h"
#include "internal/platform/implementation/ble_v2.h"
#include "internal/platform/implementation/linux/ble_gatt_client.h"
#include "internal/platform/implementation/linux/bluez_gatt_characteristic_client.h"
#include "internal/platform/implementation/linux/bluez_gatt_service_client.h"
#include "internal/platform/implementation/linux/dbus.h"
#include "internal/platform/implementation/linux/generated/dbus/bluez/gatt_characteristic_client.h"
#include "internal/platform/implementation/linux/generated/dbus/bluez/gatt_service_client.h"
#include "internal/platform/implementation/linux/utils.h"
#include "internal/platform/logging.h"
namespace nearby {
namespace linux {
bool GattClient::DiscoverServiceAndCharacteristics(
const Uuid &service_uuid, const std::vector<Uuid> &characteristic_uuids) {
return gatt_discovery_->DiscoverServiceAndCharacteristics(
peripheral_object_path_, service_uuid, characteristic_uuids,
discovery_cancel_);
}
absl::optional<api::ble_v2::GattCharacteristic> GattClient::GetCharacteristic(
const Uuid &service_uuid, const Uuid &characteristic_uuid) {
auto chr_proxy = gatt_discovery_->GetCharacteristic(
peripheral_object_path_, service_uuid, characteristic_uuid);
if (chr_proxy == nullptr) return std::nullopt;
api::ble_v2::GattCharacteristic chr;
chr.service_uuid = service_uuid;
chr.uuid = characteristic_uuid;
chr.property = api::ble_v2::GattCharacteristic::Property::kNone;
chr.permission = api::ble_v2::GattCharacteristic::Permission::kNone;
std::vector<std::string> flags;
try {
flags = chr_proxy->Flags();
} catch (const sdbus::Error &e) {
DBUS_LOG_PROPERTY_GET_ERROR(chr_proxy, "Flags", e);
return std::nullopt;
}
for (const auto &flag : flags) {
if (flag == "read") {
chr.property |= api::ble_v2::GattCharacteristic::Property::kRead;
chr.permission |= api::ble_v2::GattCharacteristic::Permission::kRead;
} else if (flag == "write") {
chr.property |= api::ble_v2::GattCharacteristic::Property::kWrite;
chr.permission |= api::ble_v2::GattCharacteristic::Permission::kWrite;
} else if (flag == "notify") {
chr.property |= api::ble_v2::GattCharacteristic::Property::kNotify;
} else if (flag == "indicate") {
chr.property |= api::ble_v2::GattCharacteristic::Property::kIndicate;
}
}
absl::MutexLock lock(&characteristics_mutex_);
characteristics_.emplace(chr, std::move(chr_proxy));
return chr;
}
absl::optional<std::string> GattClient::ReadCharacteristic(
const api::ble_v2::GattCharacteristic &characteristic) {
absl::ReaderMutexLock lock(&characteristics_mutex_);
if (characteristics_.count(characteristic) == 0) {
NEARBY_LOGS(ERROR) << __func__ << ": Unknown characteristic '"
<< absl::Substitute("$0", characteristic) << "'";
return std::nullopt;
}
return std::visit(
[](auto &&chr) {
try {
auto value_bytes = chr->ReadValue({});
return std::optional<std::string>{
std::string(value_bytes.begin(), value_bytes.end())};
} catch (const sdbus::Error &e) {
DBUS_LOG_METHOD_CALL_ERROR(chr, "ReadValue", e);
return std::optional<std::string>();
}
},
characteristics_[characteristic]);
}
bool GattClient::WriteCharacteristic(
const api::ble_v2::GattCharacteristic &characteristic,
absl::string_view value, WriteType type) {
absl::ReaderMutexLock lock(&characteristics_mutex_);
if (characteristics_.count(characteristic) == 0) {
NEARBY_LOGS(ERROR) << __func__ << ": Unknown characteristic '"
<< absl::Substitute("$0", characteristic) << "'";
return false;
}
return std::visit(
[value, type](auto &&chr) {
std::vector<uint8_t> value_bytes(value.begin(), value.end());
try {
chr->WriteValue(
value_bytes,
{{"type",
type == api::ble_v2::GattClient::WriteType::kWithResponse
? "request"
: "command"}});
return true;
} catch (const sdbus::Error &e) {
DBUS_LOG_METHOD_CALL_ERROR(chr, "WriteValue", e);
return false;
}
},
characteristics_[characteristic]);
}
bool GattClient::SetCharacteristicSubscription(
const api::ble_v2::GattCharacteristic &characteristic, bool enable,
absl::AnyInvocable<void(absl::string_view value)>
on_characteristic_changed_cb) {
absl::MutexLock lock(&characteristics_mutex_);
if (characteristics_.count(characteristic) == 0) {
NEARBY_LOGS(ERROR) << __func__ << ": Unknown characteristic '"
<< absl::Substitute("$0", characteristic) << "'";
return false;
}
if (enable) {
auto subbed_chr = gatt_discovery_->GetSubscribedCharacteristic(
peripheral_object_path_, characteristic.service_uuid,
characteristic.uuid, std::move(on_characteristic_changed_cb));
if (subbed_chr == nullptr) return false;
try {
subbed_chr->StartNotify();
} catch (const sdbus::Error &e) {
DBUS_LOG_METHOD_CALL_ERROR(subbed_chr, "StartNotify", e);
return false;
}
characteristics_[characteristic] = std::move(subbed_chr);
} else if (std::holds_alternative<
std::unique_ptr<bluez::SubscribedGattCharacteristicClient>>(
characteristics_[characteristic])) {
auto chr = gatt_discovery_->GetCharacteristic(peripheral_object_path_,
characteristic.service_uuid,
characteristic.uuid);
if (chr == nullptr) return false;
try {
chr->StopNotify();
} catch (const sdbus::Error &e) {
DBUS_LOG_METHOD_CALL_ERROR(chr, "StopNotify", e);
return false;
}
characteristics_[characteristic] = std::move(chr);
}
return true;
}
void GattClient::Disconnect() {
absl::MutexLock lock(&disconnected_callback_mutex_);
if (!discovery_cancel_.Cancelled()) {
discovery_cancel_.Cancel();
if (*disconnected_callback_it_ != nullptr) (*disconnected_callback_it_)();
gatt_discovery_->RemovePeripheralConnection(peripheral_object_path_,
disconnected_callback_it_);
}
}
void BluezGattDiscovery::Shutdown() {
auto no_discovery = [&]() {
mutex_.AssertReaderHeld();
return pending_discovery_ == 0;
};
mutex_.Lock();
shutdown_ = true;
mutex_.Await(absl::Condition(&no_discovery));
mutex_.Unlock();
}
bool BluezGattDiscovery::InitializeKnownServices() {
std::map<sdbus::ObjectPath,
std::map<std::string, std::map<std::string, sdbus::Variant>>>
objects;
try {
objects = GetManagedObjects();
} catch (const sdbus::Error &e) {
DBUS_LOG_METHOD_CALL_ERROR(this, "GetManagedObjects", e);
return false;
}
absl::flat_hash_map<sdbus::ObjectPath, GattServiceClient> cached_services;
absl::MutexLock lock(&mutex_);
auto chr_it = std::find_if(
objects.cbegin(), objects.cend(),
[](std::pair<sdbus::ObjectPath,
std::map<std::string, std::map<std::string, sdbus::Variant>>>
object) {
return object.second.count(
org::bluez::GattCharacteristic1_proxy::INTERFACE_NAME) == 1;
});
for (; chr_it != objects.cend(); chr_it++) {
const auto &[path, ifaces] = *chr_it;
const auto &properties =
ifaces.at(org::bluez::GattCharacteristic1_proxy::INTERFACE_NAME);
auto maybe_props = characteristicProperties(path, properties);
if (!maybe_props.has_value()) continue;
auto [chr_uuid, service_uuid, device_path] = *maybe_props;
discovered_characteristics_.emplace(
std::make_tuple(chr_uuid, service_uuid, device_path), path);
characteristics_properties_.emplace(
path, std::make_tuple(chr_uuid, service_uuid, device_path));
}
return true;
}
BluezGattDiscovery::CallbackIter BluezGattDiscovery::AddPeripheralConnection(
const sdbus::ObjectPath &device_object_path,
absl::AnyInvocable<void()> disconnected_callback_) {
absl::MutexLock lock(&peripheral_disconnected_callbacks_mutex_);
if (peripheral_disconnected_callbacks_.count(device_object_path) == 0)
peripheral_disconnected_callbacks_.emplace(
device_object_path, std::list<absl::AnyInvocable<void()>>{});
auto &list = peripheral_disconnected_callbacks_[device_object_path];
list.push_back(std::move(disconnected_callback_));
return list.begin();
}
void BluezGattDiscovery::RemovePeripheralConnection(
const sdbus::ObjectPath &device_object_path,
BluezGattDiscovery::CallbackIter cb) {
absl::MutexLock lock(&peripheral_disconnected_callbacks_mutex_);
auto it = peripheral_disconnected_callbacks_.find(device_object_path);
if (it != peripheral_disconnected_callbacks_.end()) {
it->second.erase(cb);
if (it->second.empty())
peripheral_disconnected_callbacks_.erase(device_object_path);
}
}
bool BluezGattDiscovery::DiscoverServiceAndCharacteristics(
const sdbus::ObjectPath &device_object_path, const Uuid &service_uuid,
const std::vector<Uuid> &characteristic_uuids, CancellationFlag &cancel) {
CancellationFlagListener cancel_listen(&cancel, [&]() {
mutex_.Lock();
mutex_.Unlock();
});
auto discovered = [this, device_object_path, service_uuid,
characteristic_uuids, &cancel]() {
mutex_.AssertReaderHeld();
return cancel.Cancelled() ||
std::all_of(
characteristic_uuids.cbegin(), characteristic_uuids.cend(),
[this, service_uuid, device_object_path](auto &chr_uuid) {
mutex_.AssertReaderHeld();
return discovered_characteristics_.count(
{service_uuid, chr_uuid, device_object_path}) == 1;
});
};
absl::ReaderMutexLock lock(&mutex_, absl::Condition(&discovered));
return !cancel.Cancelled();
}
std::unique_ptr<bluez::GattCharacteristicClient>
BluezGattDiscovery::GetCharacteristic(
const sdbus::ObjectPath &device_object_path, const Uuid &service_uuid,
const Uuid &characteristic_uuid) {
auto key =
std::make_tuple(service_uuid, characteristic_uuid, device_object_path);
absl::ReaderMutexLock lock(&mutex_);
auto path_it = discovered_characteristics_.find(key);
if (path_it == discovered_characteristics_.end()) {
NEARBY_LOGS(ERROR) << __func__ << ": No characteristic known for device "
<< device_object_path << " with service "
<< std::string{service_uuid} << " and UUID "
<< std::string{characteristic_uuid};
return nullptr;
}
return std::make_unique<bluez::GattCharacteristicClient>(system_bus_,
path_it->second);
}
std::unique_ptr<bluez::GattCharacteristicClient>
BluezGattDiscovery::GetSubscribedCharacteristic(
const sdbus::ObjectPath &device_object_path, const Uuid &service_uuid,
const Uuid &characteristic_uuid,
absl::AnyInvocable<void(absl::string_view value)>
on_characteristic_changed_cb) {
auto key =
std::make_tuple(service_uuid, characteristic_uuid, device_object_path);
absl::ReaderMutexLock lock(&mutex_);
auto path_it = discovered_characteristics_.find(key);
if (path_it == discovered_characteristics_.end()) {
NEARBY_LOGS(ERROR) << __func__ << ": No characteristic known for device "
<< device_object_path << " with service "
<< std::string{service_uuid} << " and UUID "
<< std::string{characteristic_uuid};
return nullptr;
}
return std::make_unique<bluez::SubscribedGattCharacteristicClient>(
system_bus_, device_object_path, std::move(on_characteristic_changed_cb));
}
std::optional<std::tuple<Uuid, Uuid, sdbus::ObjectPath>>
BluezGattDiscovery::characteristicProperties(
const sdbus::ObjectPath &path,
const std::map<std::string, sdbus::Variant> &properties) {
mutex_.AssertHeld();
const std::string &chr_uuid_str = properties.at("UUID");
auto chr_uuid = UuidFromString(chr_uuid_str);
if (!chr_uuid.has_value()) {
NEARBY_LOGS(ERROR) << ": Couldn't parse UUID '" << chr_uuid_str
<< "' in characteristic " << path;
return std::nullopt;
}
const sdbus::ObjectPath &service_path = properties.at("Service");
if (cached_services_.count(service_path) == 0) {
cached_services_.emplace(
path, std::make_unique<GattServiceClient>(system_bus_, path));
}
auto &service = cached_services_.at(service_path);
nearby::Uuid service_uuid;
try {
const std::string &service_uuid_str = service->UUID();
auto service_uuid_maybe = UuidFromString(service_uuid_str);
if (!service_uuid_maybe.has_value()) {
NEARBY_LOGS(ERROR) << ": Couldn't parse UUID '" << service_uuid_str
<< "' in service " << service_path;
return std::nullopt;
}
service_uuid = *service_uuid_maybe;
} catch (const sdbus::Error &e) {
DBUS_LOG_PROPERTY_GET_ERROR(service, "UUID", e);
return std::nullopt;
}
sdbus::ObjectPath device_path;
try {
device_path = service->Device();
} catch (const sdbus::Error &e) {
DBUS_LOG_PROPERTY_GET_ERROR(service, "Device", e);
return std::nullopt;
}
return std::make_tuple(*chr_uuid, service_uuid, device_path);
}
void BluezGattDiscovery::onInterfacesAdded(
const sdbus::ObjectPath &objectPath,
const std::map<std::string, std::map<std::string, sdbus::Variant>>
&interfacesAndProperties) {
if (interfacesAndProperties.count(
org::bluez::GattCharacteristic1_proxy::INTERFACE_NAME) == 0)
return;
const auto &properties = interfacesAndProperties.at(
org::bluez::GattCharacteristic1_proxy::INTERFACE_NAME);
absl::MutexLock lock(&mutex_);
auto maybe_props = characteristicProperties(objectPath, properties);
if (!maybe_props.has_value()) return;
auto [chr_uuid, service_uuid, device_path] = *maybe_props;
discovered_characteristics_.emplace(
std::make_tuple(chr_uuid, service_uuid, device_path), objectPath);
characteristics_properties_.emplace(
objectPath, std::make_tuple(chr_uuid, service_uuid, device_path));
}
void BluezGattDiscovery::onInterfacesRemoved(
const sdbus::ObjectPath &objectPath,
const std::vector<std::string> &interfaces) {
auto begin = interfaces.cbegin();
auto end = interfaces.cend();
auto service_it =
std::find(begin, end, org::bluez::GattService1_proxy::INTERFACE_NAME);
if (service_it != end) {
absl::MutexLock lock(&mutex_);
cached_services_.erase(objectPath);
return;
}
auto chr_it = std::find(
begin, end, org::bluez::GattCharacteristic1_proxy::INTERFACE_NAME);
if (chr_it != end) {
absl::MutexLock lock(&mutex_);
{
auto &props = characteristics_properties_.at(objectPath);
discovered_characteristics_.erase(props);
}
characteristics_properties_.erase(objectPath);
}
}
} // namespace linux
} // namespace nearby
@@ -0,0 +1,210 @@
// 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 PLATFORM_IMPL_LINUX_API_BLE_GATT_CLIENT_H_
#define PLATFORM_IMPL_LINUX_API_BLE_GATT_CLIENT_H_
#include <list>
#include <sdbus-c++/IConnection.h>
#include <sdbus-c++/Types.h>
#include "absl/container/flat_hash_map.h"
#include "absl/synchronization/mutex.h"
#include "internal/platform/cancellation_flag.h"
#include "internal/platform/implementation/ble_v2.h"
#include "internal/platform/implementation/linux/bluez.h"
#include "internal/platform/implementation/linux/bluez_gatt_characteristic_client.h"
#include "internal/platform/implementation/linux/bluez_gatt_service_client.h"
namespace nearby {
namespace linux {
class BluezGattDiscovery final : public bluez::BluezObjectManager {
public:
explicit BluezGattDiscovery(std::shared_ptr<sdbus::IConnection> system_bus)
: bluez::BluezObjectManager(*system_bus),
system_bus_(system_bus),
shutdown_(false),
pending_discovery_(0) {}
~BluezGattDiscovery() override { Shutdown(); }
bool InitializeKnownServices() ABSL_LOCKS_EXCLUDED(mutex_);
using CallbackIter = typename std::list<absl::AnyInvocable<void()>>::iterator;
CallbackIter AddPeripheralConnection(
const sdbus::ObjectPath &device_object_path,
absl::AnyInvocable<void()> disconnected_callback_)
ABSL_LOCKS_EXCLUDED(peripheral_disconnected_callbacks_mutex_);
void RemovePeripheralConnection(const sdbus::ObjectPath &device_object_path,
BluezGattDiscovery::CallbackIter cb)
ABSL_LOCKS_EXCLUDED(peripheral_disconnected_callbacks_mutex_);
bool DiscoverServiceAndCharacteristics(
const sdbus::ObjectPath &device_object_path, const Uuid &service_uuid,
const std::vector<Uuid> &characteristic_uuids, CancellationFlag &cancel)
ABSL_LOCKS_EXCLUDED(mutex_);
std::unique_ptr<bluez::GattCharacteristicClient> GetCharacteristic(
const sdbus::ObjectPath &device_object_path, const Uuid &service_uuid,
const Uuid &characteristic_uuid) ABSL_LOCKS_EXCLUDED(mutex_);
std::unique_ptr<bluez::GattCharacteristicClient> GetSubscribedCharacteristic(
const sdbus::ObjectPath &device_object_path, const Uuid &service_uuid,
const Uuid &characteristic_uuid,
absl::AnyInvocable<void(absl::string_view value)>
on_characteristic_changed_cb) ABSL_LOCKS_EXCLUDED(mutex_);
protected:
void onInterfacesAdded(
const sdbus::ObjectPath &objectPath,
const std::map<std::string, std::map<std::string, sdbus::Variant>>
&interfacesAndProperties) override ABSL_LOCKS_EXCLUDED(mutex_);
void onInterfacesRemoved(const sdbus::ObjectPath &objectPath,
const std::vector<std::string> &interfaces) override
ABSL_LOCKS_EXCLUDED(mutex_);
private:
std::optional<std::tuple<Uuid, Uuid, sdbus::ObjectPath>>
characteristicProperties(
const sdbus::ObjectPath &path,
const std::map<std::string, sdbus::Variant> &properties)
ABSL_EXCLUSIVE_LOCKS_REQUIRED(mutex_);
void Shutdown() ABSL_LOCKS_EXCLUDED(mutex_);
std::shared_ptr<sdbus::IConnection> system_bus_;
absl::Mutex peripheral_disconnected_callbacks_mutex_;
absl::flat_hash_map<sdbus::ObjectPath, std::list<absl::AnyInvocable<void()>>>
peripheral_disconnected_callbacks_
ABSL_GUARDED_BY(peripheral_disconnected_callbacks_mutex_);
absl::Mutex mutex_;
absl::flat_hash_map<sdbus::ObjectPath, std::unique_ptr<GattServiceClient>>
cached_services_ ABSL_GUARDED_BY(mutex_);
// Tuple order: service uuid, characteristic uuid, device object path
absl::flat_hash_map<std::tuple<Uuid, Uuid, sdbus::ObjectPath>,
sdbus::ObjectPath>
discovered_characteristics_ ABSL_GUARDED_BY(mutex_);
absl::flat_hash_map<sdbus::ObjectPath,
std::tuple<Uuid, Uuid, sdbus::ObjectPath>>
characteristics_properties_ ABSL_GUARDED_BY(mutex_);
bool shutdown_ ABSL_GUARDED_BY(mutex_);
std::size_t pending_discovery_ ABSL_GUARDED_BY(mutex_);
};
// https://developer.android.com/reference/android/bluetooth/BluetoothGatt
//
// Representation of a client GATT connection to a remote GATT server.
class GattClient : public api::ble_v2::GattClient {
public:
GattClient(const GattClient &) = delete;
GattClient(GattClient &&) = delete;
GattClient &operator=(const GattClient &) = delete;
GattClient &operator=(GattClient &&) = delete;
explicit GattClient(std::shared_ptr<sdbus::IConnection> system_bus,
const sdbus::ObjectPath &peripheral_object_path,
std::shared_ptr<BluezGattDiscovery> gatt_discovery,
absl::AnyInvocable<void()> disconnected_callback)
: system_bus_(std::move(system_bus)),
peripheral_object_path_(peripheral_object_path),
gatt_discovery_(std::move(gatt_discovery)),
discovery_cancel_(false) {
disconnected_callback_it_ = gatt_discovery->AddPeripheralConnection(
peripheral_object_path_, std::move(disconnected_callback));
}
~GattClient() override {
absl::MutexLock lock(&disconnected_callback_mutex_);
if (!discovery_cancel_.Cancelled()) {
discovery_cancel_.Cancel();
gatt_discovery_->RemovePeripheralConnection(peripheral_object_path_,
disconnected_callback_it_);
}
}
// https://developer.android.com/reference/android/bluetooth/BluetoothGatt.html#discoverServices()
//
// Discovers available service and characteristics on this connection.
// Returns whether or not discovery finished successfully.
//
// This function should block until discovery has finished.
bool DiscoverServiceAndCharacteristics(
const Uuid &service_uuid,
const std::vector<Uuid> &characteristic_uuids) override;
// https://developer.android.com/reference/android/bluetooth/BluetoothGatt.html#getService(java.util.UUID)
// https://developer.android.com/reference/android/bluetooth/BluetoothGattService.html#getCharacteristic(java.util.UUID)
//
// Retrieves a GATT characteristic. On error, does not return a value.
//
// DiscoverServiceAndCharacteristics() should be called before this method to
// fetch all available services and characteristics first.
//
// It is okay for duplicate services to exist, as long as the specified
// characteristic UUID is unique among all services of the same UUID.
// NOLINTNEXTLINE(google3-legacy-absl-backports)
absl::optional<api::ble_v2::GattCharacteristic> GetCharacteristic(
const Uuid &service_uuid, const Uuid &characteristic_uuid) override
ABSL_LOCKS_EXCLUDED(characteristics_mutex_);
// https://developer.android.com/reference/android/bluetooth/BluetoothGatt.html#readCharacteristic(android.bluetooth.BluetoothGattCharacteristic)
// https://developer.android.com/reference/android/bluetooth/BluetoothGattCharacteristic.html#getValue()
// NOLINTNEXTLINE(google3-legacy-absl-backports)
absl::optional<std::string> ReadCharacteristic(
const api::ble_v2::GattCharacteristic &characteristic) override
ABSL_LOCKS_EXCLUDED(characteristics_mutex_);
// https://developer.android.com/reference/android/bluetooth/BluetoothGattCharacteristic.html#setValue(byte[])
// https://developer.android.com/reference/android/bluetooth/BluetoothGatt.html#writeCharacteristic(android.bluetooth.BluetoothGattCharacteristic)
//
// Sends a remote characteristic write request to the server and returns
// whether or not it was successful.
bool WriteCharacteristic(
const api::ble_v2::GattCharacteristic &characteristic,
absl::string_view value, WriteType type) override
ABSL_LOCKS_EXCLUDED(characteristics_mutex_);
// https://developer.android.com/reference/android/bluetooth/BluetoothGatt.html#setCharacteristicNotification(android.bluetooth.BluetoothGattCharacteristic,%20boolean)
//
// Enable or disable notifications/indications for a given characteristic.
bool SetCharacteristicSubscription(
const api::ble_v2::GattCharacteristic &characteristic, bool enable,
absl::AnyInvocable<void(absl::string_view value)>
on_characteristic_changed_cb) override
ABSL_LOCKS_EXCLUDED(characteristics_mutex_);
// https://developer.android.com/reference/android/bluetooth/BluetoothGatt.html#disconnect()
void Disconnect() override;
private:
std::shared_ptr<sdbus::IConnection> system_bus_;
sdbus::ObjectPath peripheral_object_path_;
std::shared_ptr<BluezGattDiscovery> gatt_discovery_;
absl::Mutex disconnected_callback_mutex_;
BluezGattDiscovery::CallbackIter disconnected_callback_it_
ABSL_GUARDED_BY(disconnected_callback_mutex_);
CancellationFlag discovery_cancel_;
using CharacteristicProxy =
std::variant<std::unique_ptr<bluez::GattCharacteristicClient>,
std::unique_ptr<bluez::SubscribedGattCharacteristicClient>>;
absl::Mutex characteristics_mutex_;
absl::flat_hash_map<api::ble_v2::GattCharacteristic, CharacteristicProxy>
characteristics_ ABSL_GUARDED_BY(characteristics_mutex_);
};
} // namespace linux
} // namespace nearby
#endif
@@ -18,11 +18,12 @@
#include <sdbus-c++/IProxy.h>
#include <sdbus-c++/Types.h>
#include <absl/functional/any_invocable.h>
#include "absl/synchronization/mutex.h"
#include "internal/platform/implementation/ble_v2.h"
#include "internal/platform/implementation/linux/ble_gatt_client.h"
#include "internal/platform/implementation/linux/ble_gatt_server.h"
#include "internal/platform/implementation/linux/ble_v2_medium.h"
#include "internal/platform/implementation/linux/bluetooth_classic_device.h"
#include "internal/platform/implementation/linux/bluetooth_devices.h"
#include "internal/platform/implementation/linux/bluez_advertisement_monitor.h"
#include "internal/platform/implementation/linux/bluez_advertisement_monitor_manager.h"
@@ -38,6 +39,7 @@ BleV2Medium::BleV2Medium(BluetoothAdapter &adapter)
adapter_(adapter),
devices_(std::make_unique<BluetoothDevices>(
*system_bus_, adapter_.GetObjectPath(), observers_)),
gatt_discovery_(std::make_shared<BluezGattDiscovery>(system_bus_)),
root_object_manager_(std::make_unique<RootObjectManager>(*system_bus_)),
adv_monitor_manager_(
bluez::AdvertisementMonitorManager::
@@ -56,6 +58,10 @@ BleV2Medium::BleV2Medium(BluetoothAdapter &adapter)
DBUS_LOG_METHOD_CALL_ERROR(adv_monitor_manager_, "RegisterMonitor", e);
}
}
if (gatt_discovery_->InitializeKnownServices()) {
NEARBY_LOGS(ERROR) << __func__
<< ": Could not initialize known GATT services";
}
}
bool BleV2Medium::StartAdvertising(
@@ -205,6 +211,17 @@ std::unique_ptr<api::ble_v2::GattServer> BleV2Medium::StartGattServer(
std::move(callback));
}
std::unique_ptr<api::ble_v2::GattClient> BleV2Medium::ConnectToGattServer(
api::ble_v2::BlePeripheral &peripheral,
api::ble_v2::TxPowerLevel tx_power_level,
api::ble_v2::ClientGattConnectionCallback callback) {
auto &device = dynamic_cast<BluetoothDevice &>(peripheral);
return std::make_unique<GattClient>(system_bus_, device.getObjectPath(),
gatt_discovery_,
std::move(callback.disconnected_cb));
}
bool BleV2Medium::StartLEDiscovery() {
std::map<std::string, sdbus::Variant> filter;
filter["Transport"] = "auto";
@@ -23,6 +23,7 @@
#include "absl/container/flat_hash_map.h"
#include "absl/synchronization/mutex.h"
#include "internal/platform/implementation/ble_v2.h"
#include "internal/platform/implementation/linux/ble_gatt_client.h"
#include "internal/platform/implementation/linux/ble_v2_server_socket.h"
#include "internal/platform/implementation/linux/bluetooth_adapter.h"
#include "internal/platform/implementation/linux/bluetooth_devices.h"
@@ -70,9 +71,7 @@ class BleV2Medium final : public api::ble_v2::BleMedium {
std::unique_ptr<api::ble_v2::GattClient> ConnectToGattServer(
api::ble_v2::BlePeripheral &peripheral,
api::ble_v2::TxPowerLevel tx_power_level,
api::ble_v2::ClientGattConnectionCallback callback) override {
return nullptr;
}
api::ble_v2::ClientGattConnectionCallback callback) override;
std::unique_ptr<api::ble_v2::BleServerSocket> OpenServerSocket(
const std::string &service_id) override {
@@ -117,6 +116,7 @@ class BleV2Medium final : public api::ble_v2::BleMedium {
BluetoothAdapter adapter_;
ObserverList<api::BluetoothClassicMedium::Observer> observers_ = {};
std::shared_ptr<BluetoothDevices> devices_;
std::shared_ptr<BluezGattDiscovery> gatt_discovery_;
std::unique_ptr<RootObjectManager> root_object_manager_;
std::unique_ptr<bluez::AdvertisementMonitorManager> adv_monitor_manager_;
@@ -63,18 +63,18 @@ int16_t TxPowerLevelDbm(api::ble_v2::TxPowerLevel level);
class BluezObjectManager
: public sdbus::ProxyInterfaces<sdbus::ObjectManager_proxy> {
public:
BluezObjectManager(sdbus::IConnection &system_bus)
explicit BluezObjectManager(sdbus::IConnection &system_bus)
: ProxyInterfaces(system_bus, "org.bluez", "/") {
registerProxy();
}
virtual ~BluezObjectManager() { unregisterProxy(); }
protected:
void onInterfacesAdded(
void onInterfacesAdded(
const sdbus::ObjectPath &objectPath,
const std::map<std::string, std::map<std::string, sdbus::Variant>>
&interfacesAndProperties) override {}
void onInterfacesRemoved(
void onInterfacesRemoved(
const sdbus::ObjectPath &objectPath,
const std::vector<std::string> &interfaces) override {}
};
@@ -0,0 +1,41 @@
// 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 <map>
#include <string>
#include <vector>
#include "internal/platform/implementation/linux/bluez_gatt_characteristic_client.h"
#include "internal/platform/implementation/linux/generated/dbus/bluez/gatt_characteristic_client.h"
namespace nearby {
namespace linux {
namespace bluez {
void SubscribedGattCharacteristicClient::onPropertiesChanged(
const std::string& interfaceName,
const std::map<std::string, sdbus::Variant>& changedProperties,
const std::vector<std::string>& invalidatedProperties) {
if (interfaceName != org::bluez::GattCharacteristic1_proxy::INTERFACE_NAME)
return;
if (changedProperties.count("Value") == 1) {
std::vector<uint8_t> value_bytes = changedProperties.at("Value");
if (notify_callback_ != nullptr) {
auto value = std::string(value_bytes.cbegin(), value_bytes.cend());
notify_callback_(value);
}
}
}
} // namespace bluez
} // namespace linux
} // namespace nearby
@@ -0,0 +1,71 @@
// 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 PLATFORM_IMPL_LINUX_BLUEZ_GATT_CHARACTERISTIC_CLIENT_H_
#define PLATFORM_IMPL_LINUX_BLUEZ_GATT_CHARACTERISTIC_CLIENT_H_
#include <sdbus-c++/IConnection.h>
#include <sdbus-c++/ProxyInterfaces.h>
#include <sdbus-c++/StandardInterfaces.h>
#include "absl/functional/any_invocable.h"
#include "absl/strings/string_view.h"
#include "internal/platform/implementation/linux/dbus.h"
#include "internal/platform/implementation/linux/generated/dbus/bluez/gatt_characteristic_client.h"
namespace nearby {
namespace linux {
namespace bluez {
class GattCharacteristicClient
: public sdbus::ProxyInterfaces<org::bluez::GattCharacteristic1_proxy,
sdbus::Properties_proxy> {
public:
GattCharacteristicClient(std::shared_ptr<sdbus::IConnection> system_bus,
sdbus::ObjectPath path)
: ProxyInterfaces(*system_bus, "org.bluez", std::move(path)),
system_bus_(std::move(system_bus)) {
registerProxy();
}
virtual ~GattCharacteristicClient() { unregisterProxy(); }
protected:
void onPropertiesChanged(
const std::string& interfaceName,
const std::map<std::string, sdbus::Variant>& changedProperties,
const std::vector<std::string>& invalidatedProperties) override {}
std::shared_ptr<sdbus::IConnection> system_bus_;
};
class SubscribedGattCharacteristicClient : public GattCharacteristicClient {
public:
SubscribedGattCharacteristicClient(
std::shared_ptr<sdbus::IConnection> system_bus, sdbus::ObjectPath path,
absl::AnyInvocable<void(absl::string_view value)> notify_callback)
: GattCharacteristicClient(std::move(system_bus), std::move(path)),
notify_callback_(std::move(notify_callback)) {}
protected:
void onPropertiesChanged(
const std::string& interfaceName,
const std::map<std::string, sdbus::Variant>& changedProperties,
const std::vector<std::string>& invalidatedProperties) override;
private:
absl::AnyInvocable<void(absl::string_view value)> notify_callback_;
};
} // namespace bluez
} // namespace linux
} // namespace nearby
#endif
@@ -0,0 +1,40 @@
// 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 PLATFORM_IMPL_LINUX_BLUEZ_GATT_SERVICE_CLIENT_H_
#define PLATFORM_IMPL_LINUX_BLUEZ_GATT_SERVICE_CLIENT_H_
#include <memory>
#include <sdbus-c++/AdaptorInterfaces.h>
#include <sdbus-c++/IConnection.h>
#include "internal/platform/implementation/linux/generated/dbus/bluez/gatt_service_client.h"
namespace nearby {
namespace linux {
class GattServiceClient final
: public sdbus::ProxyInterfaces<org::bluez::GattService1_proxy> {
public:
GattServiceClient(std::shared_ptr<sdbus::IConnection> system_bus,
sdbus::ObjectPath service_object_path)
: ProxyInterfaces(*system_bus, "org.bluez",
std::move(service_object_path)) {
registerProxy();
}
~GattServiceClient() { unregisterProxy(); }
};
} // namespace linux
} // namespace nearby
#endif