From 5665bbbb39b3bdbc05dc71e95b77ecb1e1fc2d5b Mon Sep 17 00:00:00 2001 From: Lasan Mahaliyana Date: Wed, 11 Mar 2026 22:52:45 +0530 Subject: [PATCH] added nearby presence advertiser and scanner demo --- presence/BUILD | 41 +++ presence/presence_advertiser_demo.cc | 236 ++++++++++++++ presence/presence_scanner_demo.cc | 444 +++++++++++++++++++++++++++ 3 files changed, 721 insertions(+) create mode 100644 presence/presence_advertiser_demo.cc create mode 100644 presence/presence_scanner_demo.cc diff --git a/presence/BUILD b/presence/BUILD index 858a5ec9..5aaf4501 100644 --- a/presence/BUILD +++ b/presence/BUILD @@ -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", diff --git a/presence/presence_advertiser_demo.cc b/presence/presence_advertiser_demo.cc new file mode 100644 index 00000000..19302e73 --- /dev/null +++ b/presence/presence_advertiser_demo.cc @@ -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 + +#include +#include +#include +#include + +#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 ] [--tx_power ]\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&, + 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::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&, + 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; +} diff --git a/presence/presence_scanner_demo.cc b/presence/presence_scanner_demo.cc new file mode 100644 index 00000000..3d6a755d --- /dev/null +++ b/presence/presence_scanner_demo.cc @@ -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 + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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(fspl - kFsplAtOneMeterDb) / 20.0); +} + +std::string FormatProximity(int rssi, std::optional 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(*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 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(&adapter.GetImpl()); + if (linux_adapter == nullptr) { + return; + } + shared_devices_ = nearby::linux::GetSharedBluetoothDevices( + linux_adapter->GetConnection(), linux_adapter->GetObjectPath()); +#endif + } + + std::optional 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(*rssi); +#else + (void)device_id; + return std::nullopt; +#endif + } + + std::optional 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::min() || + *tx_power > std::numeric_limits::max()) { + return std::nullopt; + } + return static_cast(*tx_power); +#else + (void)device_id; + return std::nullopt; +#endif + } + + private: +#if defined(__linux__) + std::shared_ptr 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 ] [--account-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& 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& 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& ids, + nearby::internal::IdentityType identity_type) { + for (auto id : ids) { + if (id == identity_type) return true; + } + return false; +} + +std::string IdentitySelectionToString( + const std::vector& 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 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 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 rssi = signal_provider.GetRssi(*device_id); + std::optional 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 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 rssi = signal_provider.GetRssi(*device_id); + std::optional 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() ? "" + : request.manager_app_id) + << "\n" + << " account_name: " + << (request.account_name.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; +}