mirror of
https://github.com/kidfromjupiter/nearby.git
synced 2026-09-14 14:46:12 -04:00
Add support for GATT clients.
This commit is contained in:
@@ -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
|
||||
Reference in New Issue
Block a user