added nearby presence advertiser and scanner demo

This commit is contained in:
Lasan Mahaliyana
2026-03-11 22:52:45 +05:30
parent 7256c56d34
commit 5665bbbb39
3 changed files with 721 additions and 0 deletions
+41
View File
@@ -13,6 +13,7 @@
# limitations under the License.
load("@rules_cc//cc:cc_library.bzl", "cc_library")
load("@rules_cc//cc:cc_binary.bzl", "cc_binary")
load("@rules_cc//cc:cc_test.bzl", "cc_test")
package(default_visibility = ["//:__subpackages__"])
@@ -156,6 +157,46 @@ cc_test(
}),
)
cc_binary(
name = "presence_advertiser_demo",
srcs = ["presence_advertiser_demo.cc"],
deps = [
":types",
":presence_advertiser_demo_platform_impl",
"//internal/platform:types",
"//presence/implementation:internal",
"@com_google_absl//absl/status",
"@com_google_absl//absl/synchronization",
],
)
cc_binary(
name = "presence_scanner_demo",
srcs = ["presence_scanner_demo.cc"],
deps = [
":presence",
":presence_advertiser_demo_platform_impl",
"//internal/platform:types",
"@com_google_absl//absl/synchronization",
],
)
cc_library(
name = "presence_advertiser_demo_platform_impl",
alwayslink = 1,
deps = select({
"@platforms//os:windows": [
"//internal/platform/implementation/windows",
],
"@platforms//os:linux": [
"//internal/platform/implementation/linux:linux",
],
"//conditions:default": [
"//internal/platform/implementation/g3",
],
}),
)
cc_test(
name = "credential_test",
size = "small",
+236
View File
@@ -0,0 +1,236 @@
// Copyright 2026 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 <signal.h>
#include <cstdlib>
#include <iostream>
#include <string>
#include <vector>
#include "absl/status/status.h"
#include "absl/synchronization/notification.h"
#include "internal/platform/system_clock.h"
#include "internal/platform/single_thread_executor.h"
#include "presence/broadcast_request.h"
#include "presence/data_element.h"
#include "presence/implementation/broadcast_manager.h"
#include "presence/implementation/credential_manager.h"
#include "presence/implementation/mediums/mediums.h"
#include "presence/power_mode.h"
namespace {
absl::Notification g_shutdown;
void QuitHandler(int, siginfo_t*, void*) {
if (!g_shutdown.HasBeenNotified()) g_shutdown.Notify();
}
void PrintUsage(const char* program) {
std::cerr
<< "Usage: " << program << " [--name <device_name>] [--tx_power <int>]\n"
<< "Example:\n"
<< " " << program << " --name \"Linux Presence Demo\" --tx_power 20\n";
}
// For a public-identity broadcast demo, credentials are never requested.
class DemoCredentialManager : public nearby::presence::CredentialManager {
public:
void GenerateCredentials(
const nearby::internal::DeviceIdentityMetaData&,
absl::string_view,
const std::vector<nearby::internal::IdentityType>&,
int,
int,
nearby::presence::GenerateCredentialsResultCallback callback) override {
callback.credentials_generated_cb(
absl::UnimplementedError("Not used in public broadcast demo"));
}
void UpdateRemotePublicCredentials(
absl::string_view,
absl::string_view,
const std::vector<nearby::internal::SharedCredential>&,
nearby::presence::UpdateRemotePublicCredentialsCallback callback)
override {
callback.credentials_updated_cb(
absl::UnimplementedError("Not used in public broadcast demo"));
}
void UpdateLocalCredential(
const nearby::presence::CredentialSelector&,
nearby::internal::LocalCredential,
nearby::presence::SaveCredentialsResultCallback callback) override {
callback.credentials_saved_cb(
absl::UnimplementedError("Not used in public broadcast demo"));
}
void GetLocalCredentials(
const nearby::presence::CredentialSelector&,
nearby::presence::GetLocalCredentialsResultCallback callback) override {
callback.credentials_fetched_cb(
absl::NotFoundError("No local credentials in demo manager"));
}
void GetPublicCredentials(
const nearby::presence::CredentialSelector&,
nearby::presence::PublicCredentialType,
nearby::presence::GetPublicCredentialsResultCallback callback) override {
callback.credentials_fetched_cb(
absl::NotFoundError("No public credentials in demo manager"));
}
nearby::presence::SubscriberId SubscribeForPublicCredentials(
const nearby::presence::CredentialSelector&,
nearby::presence::PublicCredentialType,
nearby::presence::GetPublicCredentialsResultCallback callback) override {
callback.credentials_fetched_cb(
absl::NotFoundError("No subscriptions in demo manager"));
return 0;
}
void UnsubscribeFromPublicCredentials(nearby::presence::SubscriberId) override {
}
std::string DecryptDeviceIdentityMetaData(
absl::string_view,
absl::string_view,
absl::string_view) override {
return "";
}
void SetDeviceIdentityMetaData(
const nearby::internal::DeviceIdentityMetaData& device_identity_metadata,
bool,
absl::string_view,
const std::vector<nearby::internal::IdentityType>&,
int,
int,
nearby::presence::GenerateCredentialsResultCallback) override {
metadata_ = device_identity_metadata;
}
nearby::internal::DeviceIdentityMetaData GetDeviceIdentityMetaData()
override {
return metadata_;
}
private:
nearby::internal::DeviceIdentityMetaData metadata_;
};
} // namespace
int main(int argc, char** argv) {
// Force-link the platform SystemClock implementation from static archives.
(void)nearby::SystemClock::ElapsedRealtime();
std::string device_name = "Nearby Presence Demo";
int tx_power = 20;
for (int i = 1; i < argc; ++i) {
std::string arg = argv[i];
if ((arg == "-h") || (arg == "--help")) {
PrintUsage(argv[0]);
return 0;
}
if (arg == "--name") {
if (i + 1 >= argc) {
std::cerr << "Missing value for --name\n";
return 2;
}
device_name = argv[++i];
continue;
}
if (arg == "--tx_power") {
if (i + 1 >= argc) {
std::cerr << "Missing value for --tx_power\n";
return 2;
}
tx_power = std::atoi(argv[++i]);
continue;
}
std::cerr << "Unknown argument: " << arg << "\n";
PrintUsage(argv[0]);
return 2;
}
struct sigaction action {};
action.sa_sigaction = QuitHandler;
action.sa_flags = SA_SIGINFO;
sigaction(SIGINT, &action, nullptr);
sigaction(SIGTERM, &action, nullptr);
nearby::SingleThreadExecutor executor;
nearby::presence::Mediums mediums;
DemoCredentialManager credential_manager;
nearby::presence::BroadcastManager manager(mediums, credential_manager,
executor);
nearby::presence::PresenceBroadcast::BroadcastSection section = {
.identity = nearby::internal::IdentityType::IDENTITY_TYPE_PUBLIC,
.extended_properties = {
nearby::presence::DataElement(
nearby::presence::ActionBit::kNearbyShareAction),
nearby::presence::DataElement(
nearby::presence::ActionBit::kPresenceManagerAction),
},
.account_name = "",
.manager_app_id = "",
};
nearby::presence::PresenceBroadcast presence_broadcast = {
.sections = {section},
};
nearby::presence::BroadcastRequest request = {
.tx_power = tx_power,
.power_mode = nearby::presence::PowerMode::kLowLatency,
.variant = presence_broadcast,
};
absl::Status broadcast_status;
absl::Notification started;
auto session = manager.StartBroadcast(
request,
nearby::presence::BroadcastCallback{
.start_broadcast_cb =
[&broadcast_status, &started](absl::Status status) {
broadcast_status = status;
started.Notify();
},
});
if (!session.ok()) {
std::cerr << "Failed to initiate broadcast: " << session.status() << "\n";
return 1;
}
started.WaitForNotification();
if (!broadcast_status.ok()) {
std::cerr << "Broadcast start failed: " << broadcast_status << "\n";
manager.StopBroadcast(*session);
executor.Shutdown();
return 1;
}
std::cout << "Nearby Presence advertising started.\n"
<< " device_name: " << device_name << "\n"
<< " session_id: " << *session << "\n"
<< "Press Ctrl+C to stop.\n";
g_shutdown.WaitForNotification();
manager.StopBroadcast(*session);
executor.Shutdown();
std::cout << "Stopped Presence advertising.\n";
return 0;
}
+444
View File
@@ -0,0 +1,444 @@
// Copyright 2026 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 <signal.h>
#include <cstdint>
#include <cstdlib>
#include <cmath>
#include <iomanip>
#include <iostream>
#include <limits>
#include <memory>
#include <optional>
#include <sstream>
#include <string>
#include <vector>
#include "absl/synchronization/notification.h"
#include "internal/platform/bluetooth_adapter.h"
#include "internal/platform/system_clock.h"
#include "internal/proto/credential.pb.h"
#include "presence/power_mode.h"
#include "presence/presence_device.h"
#include "presence/presence_service_impl.h"
#include "presence/scan_request.h"
#if defined(__linux__)
#include "internal/platform/implementation/linux/bluetooth_adapter.h"
#include "internal/platform/implementation/linux/bluetooth_devices.h"
#endif
namespace {
absl::Notification g_shutdown;
void QuitHandler(int, siginfo_t*, void*) {
if (!g_shutdown.HasBeenNotified()) g_shutdown.Notify();
}
std::string RangeTypeToString(
const std::string& range) {
return range;
}
std::string EstimateZone(double distance_meters) {
constexpr double kTapDistanceThresholdMeters = 0.08;
constexpr double kReachDistanceThresholdMeters = 0.56;
constexpr double kShortRangeDistanceThresholdMeters = 1.26;
constexpr double kLongRangeDistanceThresholdMeters = 3.06;
if (distance_meters <= kTapDistanceThresholdMeters) return "tap";
if (distance_meters <= kReachDistanceThresholdMeters) return "reach";
if (distance_meters <= kShortRangeDistanceThresholdMeters) return "short";
if (distance_meters <= kLongRangeDistanceThresholdMeters) return "long";
return "far";
}
double EstimateDistanceMeters(int adjusted_rssi) {
// Mirrors presence/fpp/fpp/src/fspl_converter.rs.
constexpr int kTxPowerAtZeroMetersDb = -20;
constexpr int kFsplAtOneMeterDb = 40;
const int fspl = kTxPowerAtZeroMetersDb - adjusted_rssi;
return std::pow(10.0, static_cast<double>(fspl - kFsplAtOneMeterDb) / 20.0);
}
std::string FormatProximity(int rssi, std::optional<int8_t> tx_power) {
const int adjusted_rssi = rssi + tx_power.value_or(0);
const double distance_meters = EstimateDistanceMeters(adjusted_rssi);
const std::string zone = EstimateZone(distance_meters);
std::ostringstream stream;
stream << std::fixed << std::setprecision(2) << "distance_m="
<< distance_meters << " zone=" << RangeTypeToString(zone)
<< " rssi=" << rssi;
if (tx_power.has_value()) {
stream << " tx_power=" << static_cast<int>(*tx_power);
}
return stream.str();
}
std::string FormatProximityUnavailable() { return "proximity=unavailable"; }
std::string IdentityTypeToString(nearby::internal::IdentityType identity_type) {
switch (identity_type) {
case nearby::internal::IDENTITY_TYPE_PUBLIC:
return "public";
case nearby::internal::IDENTITY_TYPE_PRIVATE_GROUP:
return "private_group";
case nearby::internal::IDENTITY_TYPE_CONTACTS_GROUP:
return "contacts_group";
default:
return "unspecified";
}
}
std::optional<uint64_t> ParseHexId(const std::string& hex_id) {
if (hex_id.empty()) return std::nullopt;
try {
size_t parsed_size = 0;
uint64_t id = std::stoull(hex_id, &parsed_size, 16);
if (parsed_size != hex_id.size()) return std::nullopt;
return id;
} catch (...) {
return std::nullopt;
}
}
class SignalProvider {
public:
SignalProvider() {
#if defined(__linux__)
nearby::BluetoothAdapter adapter;
auto* linux_adapter =
dynamic_cast<nearby::linux::BluetoothAdapter*>(&adapter.GetImpl());
if (linux_adapter == nullptr) {
return;
}
shared_devices_ = nearby::linux::GetSharedBluetoothDevices(
linux_adapter->GetConnection(), linux_adapter->GetObjectPath());
#endif
}
std::optional<int> GetRssi(uint64_t device_id) const {
#if defined(__linux__)
if (!shared_devices_) return std::nullopt;
auto device = shared_devices_->devices->get_device_by_unique_id(device_id);
if (!device) return std::nullopt;
auto rssi = device->GetRssi();
if (!rssi.has_value()) return std::nullopt;
return static_cast<int>(*rssi);
#else
(void)device_id;
return std::nullopt;
#endif
}
std::optional<int8_t> GetTxPower(uint64_t device_id) const {
#if defined(__linux__)
if (!shared_devices_) return std::nullopt;
auto device = shared_devices_->devices->get_device_by_unique_id(device_id);
if (!device) return std::nullopt;
auto tx_power = device->GetTxPower();
if (!tx_power.has_value()) return std::nullopt;
if (*tx_power < std::numeric_limits<int8_t>::min() ||
*tx_power > std::numeric_limits<int8_t>::max()) {
return std::nullopt;
}
return static_cast<int8_t>(*tx_power);
#else
(void)device_id;
return std::nullopt;
#endif
}
private:
#if defined(__linux__)
std::shared_ptr<nearby::linux::SharedBluetoothDevices> shared_devices_;
#endif
};
void PrintUsage(const char* program) {
std::cerr << "Usage: " << program
<< " [--power low|balanced|low-latency] [--screen-on-only]"
<< " [--identities all|public|private|contacts|csv]"
<< " [--manager-app-id <id>] [--account-name <name>]\n";
}
nearby::presence::PowerMode ParsePowerMode(const std::string& arg) {
if (arg == "low") return nearby::presence::PowerMode::kLowPower;
if (arg == "low-latency") return nearby::presence::PowerMode::kLowLatency;
return nearby::presence::PowerMode::kBalanced;
}
bool AddIdentityTypeFromToken(
const std::string& token,
std::vector<nearby::internal::IdentityType>& identity_types) {
using IdentityType = nearby::internal::IdentityType;
IdentityType identity = IdentityType::IDENTITY_TYPE_UNSPECIFIED;
if (token == "public") {
identity = IdentityType::IDENTITY_TYPE_PUBLIC;
} else if (token == "private") {
identity = IdentityType::IDENTITY_TYPE_PRIVATE_GROUP;
} else if (token == "contacts") {
identity = IdentityType::IDENTITY_TYPE_CONTACTS_GROUP;
} else {
return false;
}
for (auto existing : identity_types) {
if (existing == identity) return true;
}
identity_types.push_back(identity);
return true;
}
bool ParseIdentityTypes(
const std::string& arg,
std::vector<nearby::internal::IdentityType>& identity_types) {
identity_types.clear();
if (arg == "all") {
return true;
}
size_t start = 0;
while (start < arg.size()) {
size_t comma = arg.find(',', start);
std::string token = arg.substr(
start, comma == std::string::npos ? std::string::npos : comma - start);
if (!AddIdentityTypeFromToken(token, identity_types)) return false;
if (comma == std::string::npos) break;
start = comma + 1;
}
return !identity_types.empty();
}
bool ContainsIdentityType(const std::vector<nearby::internal::IdentityType>& ids,
nearby::internal::IdentityType identity_type) {
for (auto id : ids) {
if (id == identity_type) return true;
}
return false;
}
std::string IdentitySelectionToString(
const std::vector<nearby::internal::IdentityType>& identity_types) {
if (identity_types.empty()) {
return "all";
}
std::string result;
for (size_t i = 0; i < identity_types.size(); ++i) {
if (i > 0) result += ",";
result += IdentityTypeToString(identity_types[i]);
}
return result;
}
} // namespace
int main(int argc, char** argv) {
(void)nearby::SystemClock::ElapsedRealtime();
nearby::presence::PowerMode power_mode = nearby::presence::PowerMode::kBalanced;
bool screen_on_only = false;
std::string identities_arg = "all";
std::string manager_app_id;
std::string account_name;
for (int i = 1; i < argc; ++i) {
std::string arg = argv[i];
if ((arg == "-h") || (arg == "--help")) {
PrintUsage(argv[0]);
return 0;
}
if (arg == "--power") {
if (i + 1 >= argc) {
std::cerr << "Missing value for --power\n";
return 2;
}
power_mode = ParsePowerMode(argv[++i]);
continue;
}
if (arg == "--screen-on-only") {
screen_on_only = true;
continue;
}
if (arg == "--identities") {
if (i + 1 >= argc) {
std::cerr << "Missing value for --identities\n";
return 2;
}
identities_arg = argv[++i];
continue;
}
if (arg == "--manager-app-id") {
if (i + 1 >= argc) {
std::cerr << "Missing value for --manager-app-id\n";
return 2;
}
manager_app_id = argv[++i];
continue;
}
if (arg == "--account-name") {
if (i + 1 >= argc) {
std::cerr << "Missing value for --account-name\n";
return 2;
}
account_name = argv[++i];
continue;
}
std::cerr << "Unknown argument: " << arg << "\n";
PrintUsage(argv[0]);
return 2;
}
std::vector<nearby::internal::IdentityType> identity_types;
if (!ParseIdentityTypes(identities_arg, identity_types)) {
std::cerr << "Invalid --identities value: " << identities_arg << "\n";
PrintUsage(argv[0]);
return 2;
}
struct sigaction action {};
action.sa_sigaction = QuitHandler;
action.sa_flags = SA_SIGINFO;
sigaction(SIGINT, &action, nullptr);
sigaction(SIGTERM, &action, nullptr);
SignalProvider signal_provider;
nearby::presence::PresenceServiceImpl service;
auto client = service.CreatePresenceClient();
nearby::presence::ScanRequest request;
request.use_ble = true;
request.scan_type = nearby::presence::ScanType::kPresenceScan;
request.power_mode = power_mode;
request.scan_only_when_screen_on = screen_on_only;
request.manager_app_id = manager_app_id;
request.account_name = account_name;
request.identity_types = identity_types;
const bool scans_non_public =
request.identity_types.empty() ||
ContainsIdentityType(request.identity_types,
nearby::internal::IdentityType::
IDENTITY_TYPE_PRIVATE_GROUP) ||
ContainsIdentityType(request.identity_types,
nearby::internal::IdentityType::
IDENTITY_TYPE_CONTACTS_GROUP);
if (scans_non_public && request.manager_app_id.empty()) {
std::cerr << "Warning: scanning private/contact identities without "
"--manager-app-id usually results in empty remote credentials.\n";
}
absl::Status start_status;
absl::Notification started;
auto scan_session = client->StartScan(
request,
nearby::presence::ScanCallback{
.start_scan_cb =
[&start_status, &started](absl::Status status) {
start_status = status;
started.Notify();
},
.on_discovered_cb =
[&signal_provider](nearby::presence::PresenceDevice device) {
std::string remote_hex =
device.GetDeviceIdentityMetadata().bluetooth_mac_address();
std::optional<uint64_t> device_id = ParseHexId(remote_hex);
if (!device_id.has_value()) {
std::cout << "[DISCOVERED] endpoint=" << device.GetEndpointId()
<< " identity="
<< IdentityTypeToString(device.GetIdentityType())
<< " remote_hex=" << remote_hex
<< " " << FormatProximityUnavailable() << "\n";
return;
}
std::optional<int> rssi = signal_provider.GetRssi(*device_id);
std::optional<int8_t> tx_power =
signal_provider.GetTxPower(*device_id);
std::string proximity_desc = FormatProximityUnavailable();
if (rssi.has_value()) {
proximity_desc = FormatProximity(*rssi, tx_power);
}
std::cout << "[DISCOVERED] endpoint=" << device.GetEndpointId()
<< " identity="
<< IdentityTypeToString(device.GetIdentityType())
<< " remote_hex=" << remote_hex << " "
<< proximity_desc << "\n";
},
.on_updated_cb =
[&signal_provider](nearby::presence::PresenceDevice device) {
std::string remote_hex =
device.GetDeviceIdentityMetadata().bluetooth_mac_address();
std::optional<uint64_t> device_id = ParseHexId(remote_hex);
if (!device_id.has_value()) {
std::cout << "[UPDATED] endpoint=" << device.GetEndpointId()
<< " identity="
<< IdentityTypeToString(device.GetIdentityType())
<< " remote_hex=" << remote_hex
<< " " << FormatProximityUnavailable() << "\n";
return;
}
std::optional<int> rssi = signal_provider.GetRssi(*device_id);
std::optional<int8_t> tx_power =
signal_provider.GetTxPower(*device_id);
std::string proximity_desc = FormatProximityUnavailable();
if (rssi.has_value()) {
proximity_desc = FormatProximity(*rssi, tx_power);
}
std::cout << "[UPDATED] endpoint=" << device.GetEndpointId()
<< " identity="
<< IdentityTypeToString(device.GetIdentityType())
<< " remote_hex=" << remote_hex << " "
<< proximity_desc << "\n";
},
.on_lost_cb =
[](nearby::presence::PresenceDevice device) {
std::cout << "[LOST] endpoint=" << device.GetEndpointId()
<< " remote_hex="
<< device.GetDeviceIdentityMetadata()
.bluetooth_mac_address()
<< "\n";
}});
if (!scan_session.ok()) {
std::cerr << "Failed to initiate scan: " << scan_session.status() << "\n";
return 1;
}
started.WaitForNotification();
if (!start_status.ok()) {
std::cerr << "Scan start failed: " << start_status << "\n";
client->StopScan(*scan_session);
return 1;
}
std::cout << "Presence scan started.\n"
<< " session_id: " << *scan_session << "\n"
<< " identities: "
<< IdentitySelectionToString(request.identity_types) << "\n"
<< " manager_app_id: "
<< (request.manager_app_id.empty() ? "<empty>"
: request.manager_app_id)
<< "\n"
<< " account_name: "
<< (request.account_name.empty() ? "<empty>" : request.account_name)
<< "\n"
<< " mode: printing discovered/updated/lost events with proximity "
"estimates when RSSI is available\n"
<< "Press Ctrl+C to stop.\n";
g_shutdown.WaitForNotification();
client->StopScan(*scan_session);
std::cout << "Stopped Presence scan.\n";
return 0;
}