mirror of
https://github.com/kidfromjupiter/nearby.git
synced 2026-09-16 15:36:12 -04:00
426 lines
16 KiB
C++
426 lines
16 KiB
C++
// 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 "fastpair/internal/mediums/robust_gatt_client.h"
|
|
|
|
#include <algorithm>
|
|
#include <atomic>
|
|
#include <memory>
|
|
#include <optional>
|
|
#include <string>
|
|
#include <utility>
|
|
#include <vector>
|
|
|
|
#include "absl/base/thread_annotations.h"
|
|
#include "absl/functional/any_invocable.h"
|
|
#include "absl/status/status.h"
|
|
#include "absl/time/clock.h"
|
|
#include "absl/time/time.h"
|
|
#include "internal/platform/single_thread_executor.h"
|
|
|
|
namespace nearby {
|
|
namespace fastpair {
|
|
namespace {} // namespace
|
|
|
|
RobustGattClient::~RobustGattClient() {
|
|
Stop();
|
|
executor_.Shutdown();
|
|
}
|
|
|
|
void RobustGattClient::Stop() {
|
|
NEARBY_LOGS(INFO) << "Stopping gatt client";
|
|
stopped_ = true;
|
|
executor_.Execute("cleanup", [this]() ABSL_EXCLUSIVE_LOCKS_REQUIRED(
|
|
executor_) { Cleanup(); });
|
|
}
|
|
|
|
void RobustGattClient::Connect() {
|
|
executor_.Execute("connect-gatt",
|
|
[this]() ABSL_EXCLUSIVE_LOCKS_REQUIRED(executor_) {
|
|
NEARBY_LOGS(INFO) << "Connecting to Gatt server";
|
|
status_ = TryConnect();
|
|
if (!status_.ok()) {
|
|
NEARBY_LOGS(INFO) << status_;
|
|
NotifyClient(status_);
|
|
return;
|
|
}
|
|
NEARBY_LOGS(INFO) << "Discovering services";
|
|
status_ = TryDiscoverServices();
|
|
if (!status_.ok()) {
|
|
NEARBY_LOGS(INFO) << status_;
|
|
NotifyClient(status_);
|
|
return;
|
|
}
|
|
NEARBY_LOGS(INFO) << "Gatt connection ready";
|
|
NotifyClient(absl::OkStatus());
|
|
});
|
|
}
|
|
|
|
absl::Status RobustGattClient::TryConnect() {
|
|
ExpBackOff back_off(params_);
|
|
absl::Time start_time = SystemClock().ElapsedRealtime();
|
|
while (!stopped_ && SystemClock().ElapsedRealtime() - start_time <
|
|
params_.connect_timeout) {
|
|
gatt_client_ = medium_.ConnectToGattServer(
|
|
peripheral_, params_.tx_power_level, {.disconnected_cb = [this]() {
|
|
if (stopped_) return;
|
|
NEARBY_LOGS(INFO) << "Gatt server disconnected. Reconnecting...";
|
|
Connect();
|
|
}});
|
|
if (gatt_client_ != nullptr && gatt_client_->IsValid()) {
|
|
return absl::OkStatus();
|
|
}
|
|
SystemClock().Sleep(back_off.NextBackOff());
|
|
}
|
|
return absl::DeadlineExceededError("gatt connection time-out");
|
|
}
|
|
|
|
absl::Status RobustGattClient::TryDiscoverServices() {
|
|
ExpBackOff back_off(params_);
|
|
absl::Time start_time = SystemClock().ElapsedRealtime();
|
|
while (!stopped_ && SystemClock().ElapsedRealtime() - start_time <
|
|
params_.discovery_timeout) {
|
|
if (DiscoverServices(params_.service_uuid,
|
|
GetPrimaryCharacteristicList()) ||
|
|
DiscoverServices(params_.service_uuid,
|
|
GetFallbackCharacteristicList())) {
|
|
return absl::OkStatus();
|
|
}
|
|
SystemClock().Sleep(back_off.NextBackOff());
|
|
}
|
|
return absl::DeadlineExceededError("gatt discovery time-out");
|
|
}
|
|
|
|
bool RobustGattClient::DiscoverServices(
|
|
const Uuid& service_uuid, const std::vector<Uuid>& characteristic_uuids) {
|
|
if (service_uuid.IsEmpty() || characteristic_uuids.empty()) {
|
|
return false;
|
|
}
|
|
return gatt_client_->DiscoverServiceAndCharacteristics(service_uuid,
|
|
characteristic_uuids);
|
|
}
|
|
|
|
std::optional<RobustGattClient::GattCharacteristic>
|
|
RobustGattClient::GetCharacteristic(const Uuid& service_id,
|
|
const UuidPair& characteristic_uuid_pair) {
|
|
if (stopped_) return std::nullopt;
|
|
std::optional<GattCharacteristic> characteristic =
|
|
gatt_client_->GetCharacteristic(service_id,
|
|
characteristic_uuid_pair.primary_uuid);
|
|
if (characteristic.has_value()) {
|
|
return characteristic;
|
|
}
|
|
if (characteristic_uuid_pair.fallback_uuid.IsEmpty() || stopped_) {
|
|
return std::nullopt;
|
|
}
|
|
return gatt_client_->GetCharacteristic(
|
|
service_id, characteristic_uuid_pair.fallback_uuid);
|
|
}
|
|
|
|
const RobustGattClient::GattCharacteristic* RobustGattClient::GetCharacteristic(
|
|
int uuid_pair_index) {
|
|
auto it = characteristics_.find(uuid_pair_index);
|
|
if (it != characteristics_.end()) {
|
|
return &it->second;
|
|
}
|
|
const UuidPair& uuid_pair = params_.characteristic_uuids[uuid_pair_index];
|
|
std::optional<GattCharacteristic> characteristic =
|
|
GetCharacteristic(params_.service_uuid, uuid_pair);
|
|
if (!characteristic.has_value()) {
|
|
NEARBY_LOGS(WARNING) << absl::StrFormat(
|
|
"Characteristic (%s, %s) not found on service %s",
|
|
std::string(uuid_pair.primary_uuid),
|
|
std::string(uuid_pair.fallback_uuid),
|
|
std::string(params_.service_uuid));
|
|
}
|
|
characteristics_[uuid_pair_index] = *characteristic;
|
|
return &characteristics_[uuid_pair_index];
|
|
}
|
|
|
|
std::vector<Uuid> RobustGattClient::GetPrimaryCharacteristicList() {
|
|
std::vector<Uuid> result;
|
|
result.reserve(params_.characteristic_uuids.size());
|
|
for (auto& uuid_pair : params_.characteristic_uuids) {
|
|
result.push_back(uuid_pair.primary_uuid);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
std::vector<Uuid> RobustGattClient::GetFallbackCharacteristicList() {
|
|
bool has_fallbacks = false;
|
|
std::vector<Uuid> result;
|
|
result.reserve(params_.characteristic_uuids.size());
|
|
for (auto& uuid_pair : params_.characteristic_uuids) {
|
|
if (uuid_pair.fallback_uuid.IsEmpty()) {
|
|
// Some characteristics may have only one, primary UUID.
|
|
result.push_back(uuid_pair.primary_uuid);
|
|
} else {
|
|
has_fallbacks = true;
|
|
result.push_back(uuid_pair.fallback_uuid);
|
|
}
|
|
}
|
|
if (!has_fallbacks) result.clear();
|
|
return result;
|
|
}
|
|
|
|
void RobustGattClient::WriteCharacteristic(
|
|
int uuid_pair_index, absl::string_view value,
|
|
api::ble_v2::GattClient::WriteType write_type, WriteCallback callback) {
|
|
CHECK_LT(uuid_pair_index, params_.characteristic_uuids.size());
|
|
Write({.uuid_pair_index = uuid_pair_index,
|
|
.value = std::string(value),
|
|
.write_type = write_type,
|
|
.callback = std::move(callback),
|
|
.time_left = params_.gatt_operation_timeout,
|
|
.back_off = ExpBackOff(params_)});
|
|
}
|
|
|
|
void RobustGattClient::CallRemoteFunction(int uuid_pair_index,
|
|
absl::string_view request,
|
|
NotifyCallback response) {
|
|
CHECK_LT(uuid_pair_index, params_.characteristic_uuids.size());
|
|
Subscribe(uuid_pair_index, std::move(response), /*call_once=*/true);
|
|
WriteCharacteristic(uuid_pair_index, request,
|
|
api::ble_v2::GattClient::WriteType::kWithResponse,
|
|
[this, uuid_pair_index](absl::Status result) {
|
|
if (!result.ok()) {
|
|
NotifySubscriber(uuid_pair_index, result);
|
|
} else {
|
|
StartNotifyTimer(uuid_pair_index);
|
|
}
|
|
});
|
|
}
|
|
|
|
void RobustGattClient::StartNotifyTimer(int uuid_pair_index) {
|
|
if (stopped_) return;
|
|
MutexLock lock(&mutex_);
|
|
auto it = notify_callbacks_.find(uuid_pair_index);
|
|
if (it == notify_callbacks_.end()) return;
|
|
it->second.timer = std::make_unique<TimerImpl>();
|
|
it->second.timer->Start(
|
|
absl::ToInt64Milliseconds(params_.gatt_operation_timeout), 0,
|
|
[this, uuid_pair_index]() {
|
|
NotifySubscriber(uuid_pair_index,
|
|
absl::DeadlineExceededError("gatt operation timeout"));
|
|
});
|
|
}
|
|
|
|
void RobustGattClient::Write(WriteRequest request) {
|
|
executor_.Execute(
|
|
"write-gatt",
|
|
[this, request = std::move(request)]()
|
|
ABSL_EXCLUSIVE_LOCKS_REQUIRED(executor_) mutable {
|
|
absl::Time start_time = SystemClock::ElapsedRealtime();
|
|
if (stopped_) return;
|
|
if (!status_.ok()) {
|
|
NEARBY_LOGS(WARNING)
|
|
<< "Cannot write due to connection error " << status_;
|
|
std::move(request.callback)(status_);
|
|
return;
|
|
}
|
|
SystemClock().Sleep(request.back_off.NextBackOff());
|
|
const GattCharacteristic* characteristic =
|
|
GetCharacteristic(request.uuid_pair_index);
|
|
bool result = false;
|
|
if (characteristic != nullptr) {
|
|
result = gatt_client_->WriteCharacteristic(
|
|
*characteristic, request.value, request.write_type);
|
|
}
|
|
if (stopped_) return;
|
|
if (result) {
|
|
std::move(request.callback)(absl::OkStatus());
|
|
} else {
|
|
request.time_left -= SystemClock::ElapsedRealtime() - start_time;
|
|
if (request.time_left > absl::ZeroDuration()) {
|
|
Write(std::move(request));
|
|
} else {
|
|
std::move(request.callback)(
|
|
absl::UnavailableError("gatt write failed"));
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
void RobustGattClient::Subscribe(int uuid_pair_index, NotifyCallback callback,
|
|
bool call_once) {
|
|
CHECK_LT(uuid_pair_index, params_.characteristic_uuids.size());
|
|
MutexLock lock(&mutex_);
|
|
notify_callbacks_[uuid_pair_index] = NotifyCallbackInfo{
|
|
.callback = std::move(callback),
|
|
.call_once = call_once,
|
|
};
|
|
NEARBY_LOGS(INFO) << "Subscribe to characteristic no: " << uuid_pair_index;
|
|
Subscribe(SubscribeRequest{
|
|
.uuid_pair_index = uuid_pair_index,
|
|
.time_left = params_.gatt_operation_timeout,
|
|
.back_off = ExpBackOff(params_),
|
|
});
|
|
}
|
|
|
|
void RobustGattClient::Subscribe(SubscribeRequest request) {
|
|
executor_.Execute(
|
|
"subscribe",
|
|
[this, request = std::move(request)]()
|
|
ABSL_EXCLUSIVE_LOCKS_REQUIRED(executor_) mutable {
|
|
if (stopped_) return;
|
|
if (!HasSubsriberCallback(request.uuid_pair_index)) return;
|
|
if (!status_.ok()) {
|
|
NotifySubscriber(request.uuid_pair_index, status_);
|
|
return;
|
|
}
|
|
absl::Time start_time = SystemClock::ElapsedRealtime();
|
|
SystemClock().Sleep(request.back_off.NextBackOff());
|
|
const GattCharacteristic* characteristic =
|
|
GetCharacteristic(request.uuid_pair_index);
|
|
bool result = false;
|
|
if (characteristic != nullptr) {
|
|
result = gatt_client_->SetCharacteristicSubscription(
|
|
*characteristic, true,
|
|
[this, uuid_pair_index =
|
|
request.uuid_pair_index](absl::string_view value) {
|
|
NotifySubscriber(uuid_pair_index, value);
|
|
});
|
|
}
|
|
if (stopped_) return;
|
|
if (!result) {
|
|
request.time_left -= SystemClock::ElapsedRealtime() - start_time;
|
|
if (request.time_left > absl::ZeroDuration()) {
|
|
Subscribe(std::move(request));
|
|
} else {
|
|
NotifySubscriber(
|
|
request.uuid_pair_index,
|
|
absl::UnavailableError("gatt subscription failed"));
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
void RobustGattClient::Unsubscribe(int uuid_pair_index) {
|
|
CHECK_LT(uuid_pair_index, params_.characteristic_uuids.size());
|
|
MutexLock lock(&mutex_);
|
|
int removed = notify_callbacks_.erase(uuid_pair_index);
|
|
if (removed == 0) {
|
|
NEARBY_LOGS(VERBOSE) << "Not subscribed for characteristic no: "
|
|
<< uuid_pair_index;
|
|
}
|
|
UnsubscribeInternal(uuid_pair_index);
|
|
}
|
|
|
|
void RobustGattClient::UnsubscribeInternal(int uuid_pair_index) {
|
|
executor_.Execute(
|
|
"unsubscribe",
|
|
[this, uuid_pair_index]() ABSL_EXCLUSIVE_LOCKS_REQUIRED(executor_) {
|
|
if (stopped_) return;
|
|
if (!status_.ok()) {
|
|
return;
|
|
}
|
|
NEARBY_LOGS(INFO) << "Unsubscribe from characteristic no: "
|
|
<< uuid_pair_index;
|
|
const GattCharacteristic* characteristic =
|
|
GetCharacteristic(uuid_pair_index);
|
|
if (characteristic != nullptr) {
|
|
gatt_client_->SetCharacteristicSubscription(*characteristic, false,
|
|
[](absl::string_view) {});
|
|
}
|
|
});
|
|
}
|
|
|
|
bool RobustGattClient::HasSubsriberCallback(int uuid_pair_index) {
|
|
MutexLock lock(&mutex_);
|
|
return notify_callbacks_.find(uuid_pair_index) != notify_callbacks_.end();
|
|
}
|
|
|
|
void RobustGattClient::NotifySubscriber(
|
|
int uuid_pair_index, absl::StatusOr<absl::string_view> value) {
|
|
if (stopped_) return;
|
|
MutexLock lock(&mutex_);
|
|
auto it = notify_callbacks_.find(uuid_pair_index);
|
|
if (it != notify_callbacks_.end()) {
|
|
it->second.callback(value);
|
|
if (it->second.timer) {
|
|
it->second.timer->Stop();
|
|
// NotifySubscriber could be called from the timer. We can't destroy the
|
|
// timer directly from the timer callback.
|
|
DestroyOnExecutor(std::move(it->second.timer), &executor_);
|
|
}
|
|
if (it->second.call_once) {
|
|
notify_callbacks_.erase(it);
|
|
UnsubscribeInternal(uuid_pair_index);
|
|
}
|
|
}
|
|
}
|
|
|
|
void RobustGattClient::ReadCharacteristic(int uuid_pair_index,
|
|
ReadCallback callback) {
|
|
CHECK_LT(uuid_pair_index, params_.characteristic_uuids.size());
|
|
Read(ReadRequest{
|
|
.uuid_pair_index = uuid_pair_index,
|
|
.callback = std::move(callback),
|
|
.time_left = params_.gatt_operation_timeout,
|
|
.back_off = ExpBackOff(params_),
|
|
});
|
|
}
|
|
void RobustGattClient::Read(ReadRequest request) {
|
|
executor_.Execute(
|
|
"read-gatt",
|
|
[this, request = std::move(request)]()
|
|
ABSL_EXCLUSIVE_LOCKS_REQUIRED(executor_) mutable {
|
|
absl::Time start_time = SystemClock::ElapsedRealtime();
|
|
if (stopped_) return;
|
|
if (!status_.ok()) {
|
|
std::move(request.callback)(status_);
|
|
return;
|
|
}
|
|
SystemClock().Sleep(request.back_off.NextBackOff());
|
|
const GattCharacteristic* characteristic =
|
|
GetCharacteristic(request.uuid_pair_index);
|
|
absl::optional<std::string> value;
|
|
if (characteristic != nullptr) {
|
|
value = gatt_client_->ReadCharacteristic(*characteristic);
|
|
}
|
|
|
|
if (stopped_) return;
|
|
if (value.has_value()) {
|
|
std::move(request.callback)(value.value());
|
|
} else {
|
|
request.time_left -= SystemClock::ElapsedRealtime() - start_time;
|
|
if (request.time_left > absl::ZeroDuration()) {
|
|
Read(std::move(request));
|
|
} else {
|
|
std::move(request.callback)(
|
|
absl::UnavailableError("gatt read failed"));
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
void RobustGattClient::Cleanup() {
|
|
if (gatt_client_ != nullptr && gatt_client_->IsValid()) {
|
|
gatt_client_->Disconnect();
|
|
}
|
|
gatt_client_.reset();
|
|
characteristics_.clear();
|
|
MutexLock lock(&mutex_);
|
|
notify_callbacks_.clear();
|
|
}
|
|
|
|
void RobustGattClient::NotifyClient(absl::Status status) {
|
|
if (stopped_) return;
|
|
if (connection_status_callback_ != nullptr)
|
|
connection_status_callback_(status);
|
|
}
|
|
|
|
} // namespace fastpair
|
|
} // namespace nearby
|