mirror of
https://github.com/kidfromjupiter/nearby.git
synced 2026-09-14 14:46:12 -04:00
[fp-rs] Now using model ID instead of BLE address for keeping track of unique devices.
This commit is contained in:
@@ -13,4 +13,6 @@ anyhow = "1.0"
|
||||
bluetooth = { version = "0.1", path = "../../bluetooth" }
|
||||
flutter_rust_bridge = "1"
|
||||
futures = { version = "0.3", features = ["executor"] }
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
serde_json = "1.0"
|
||||
tracing = "0.1.37"
|
||||
|
||||
@@ -12,7 +12,12 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
use bluetooth::{BleAddress, BleAdvertisement};
|
||||
use bluetooth::{BleAddress, BleAdvertisement, ServiceData};
|
||||
|
||||
use crate::decoder::FpDecoder;
|
||||
|
||||
/// Represents a FP device model ID.
|
||||
pub(crate) type ModelId = String;
|
||||
|
||||
/// Holds information required to make decisions about an incoming Fast Pair
|
||||
/// advertisement.
|
||||
@@ -21,9 +26,59 @@ pub(crate) struct FpPairingAdvertisement {
|
||||
inner: BleAdvertisement,
|
||||
/// Estimated distance in meters of device from BLE adapter.
|
||||
distance: f64,
|
||||
model_id: ModelId,
|
||||
}
|
||||
|
||||
impl FpPairingAdvertisement {
|
||||
/// Create a new Fast Pair advertisement instance.
|
||||
pub(crate) fn new(
|
||||
adv: BleAdvertisement,
|
||||
service_data: &ServiceData<u16>,
|
||||
) -> Result<Self, anyhow::Error> {
|
||||
let rssi = adv.rssi().ok_or(anyhow::anyhow!(
|
||||
"Windows advertisements should contain RSSI information."
|
||||
))?;
|
||||
let tx_power = adv.tx_power().ok_or(anyhow::anyhow!(
|
||||
"Fast Pair advertisements should advertise their transmit power."
|
||||
))?;
|
||||
|
||||
let distance = distance_from_rssi_and_tx_power(rssi, tx_power);
|
||||
|
||||
// Extract model ID from service data. We don't need to store service
|
||||
// data in the `FpPairingAdvertisement` since it's easily accessible from
|
||||
// `FpPairingAdvertisement.inner`, but it's convenient to save the parsed
|
||||
// model ID.
|
||||
let mut model_id =
|
||||
FpDecoder::get_model_id_from_service_data(service_data).or_else(|err| {
|
||||
// Some FP advertisements can be GATT non-discoverable
|
||||
// advertisements containing service data that isn't
|
||||
// the device model ID. In this case, simply ignore
|
||||
// advertisements with errors extracting the model ID.
|
||||
// See: developers.google.com/nearby/fast-pair/specifications/service/provider
|
||||
Err(anyhow::anyhow!("error extracting model ID: {}", err))
|
||||
})?;
|
||||
|
||||
if model_id.len() != 3 {
|
||||
// In this demo of Fast Pair Rust, only model ID's
|
||||
// of length 3 bytes are supported. Therefore, if a
|
||||
// larger model ID makes it this far, log an error.
|
||||
// TODO b/294453912
|
||||
return Err(anyhow::anyhow!("Error: model ID of unsupported length"));
|
||||
}
|
||||
|
||||
// Pad with 0 at the beginning to successfully call `from_be_bytes`.
|
||||
// Assumes `model_id.len() == 3` before the call to `insert`, otherwise
|
||||
// this will panic.
|
||||
model_id.insert(0, 0);
|
||||
let model_id = format!("{}", u32::from_be_bytes(model_id.try_into().unwrap()));
|
||||
|
||||
Ok(FpPairingAdvertisement {
|
||||
inner: adv,
|
||||
distance,
|
||||
model_id,
|
||||
})
|
||||
}
|
||||
|
||||
/// Retrieve estimated distance the BLE advertisement travelled between
|
||||
/// the sending device and this receiver.
|
||||
pub(crate) fn distance(&self) -> f64 {
|
||||
@@ -34,25 +89,11 @@ impl FpPairingAdvertisement {
|
||||
pub(crate) fn address(&self) -> BleAddress {
|
||||
self.inner.address()
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<BleAdvertisement> for FpPairingAdvertisement {
|
||||
type Error = anyhow::Error;
|
||||
|
||||
fn try_from(adv: BleAdvertisement) -> Result<Self, Self::Error> {
|
||||
let rssi = adv.rssi().ok_or(anyhow::anyhow!(
|
||||
"Windows advertisements should contain RSSI information."
|
||||
))?;
|
||||
let tx_power = adv.tx_power().ok_or(anyhow::anyhow!(
|
||||
"Fast Pair advertisements should advertise their transmit power."
|
||||
))?;
|
||||
|
||||
let distance = distance_from_rssi_and_tx_power(rssi, tx_power);
|
||||
|
||||
Ok(FpPairingAdvertisement {
|
||||
inner: adv,
|
||||
distance,
|
||||
})
|
||||
/// Retrieve the Model ID advertised by this device, parsed from the
|
||||
/// 16-bit UUID service data.
|
||||
pub(crate) fn model_id(&self) -> &ModelId {
|
||||
&self.model_id
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -2,8 +2,7 @@ use std::{collections::HashMap, sync::RwLock};
|
||||
|
||||
use bluetooth::{
|
||||
api::{BleAdapter, BleDevice, ClassicDevice},
|
||||
BleAddress, BleAdvertisement, BleDataTypeId, ClassicAddress, PairingResult, Platform,
|
||||
ServiceData,
|
||||
BleAdvertisement, BleDataTypeId, ClassicAddress, PairingResult, Platform, ServiceData,
|
||||
};
|
||||
use flutter_rust_bridge::StreamSink;
|
||||
use futures::executor;
|
||||
@@ -42,7 +41,7 @@ async fn update_best_device(best_adv: FpPairingAdvertisement) {
|
||||
fn new_best_fp_advertisement(
|
||||
advertisement: BleAdvertisement,
|
||||
service_data: &ServiceData<u16>,
|
||||
latest_advertisement_map: &mut HashMap<BleAddress, FpPairingAdvertisement>,
|
||||
latest_advertisement_map: &mut HashMap<String, FpPairingAdvertisement>,
|
||||
) -> Option<FpPairingAdvertisement> {
|
||||
// Analyze service data sections.
|
||||
let uuid = service_data.uuid();
|
||||
@@ -52,7 +51,7 @@ fn new_best_fp_advertisement(
|
||||
return None;
|
||||
}
|
||||
|
||||
let fp_adv = match FpPairingAdvertisement::try_from(advertisement) {
|
||||
let fp_adv = match FpPairingAdvertisement::new(advertisement, service_data) {
|
||||
Ok(fp_adv) => fp_adv,
|
||||
Err(err) => {
|
||||
// If error during construction (e.g. non-discoverable
|
||||
@@ -64,13 +63,13 @@ fn new_best_fp_advertisement(
|
||||
}
|
||||
};
|
||||
|
||||
latest_advertisement_map.insert(fp_adv.address(), fp_adv.clone());
|
||||
latest_advertisement_map.insert(fp_adv.model_id().to_owned(), fp_adv.clone());
|
||||
|
||||
if let Some(best_adv) = CURR_DEVICE_ADV.read().unwrap().as_ref() {
|
||||
if best_adv.distance() >= fp_adv.distance() {
|
||||
// New advertised distance is closer.
|
||||
Some(fp_adv)
|
||||
} else if best_adv.address() == fp_adv.address() {
|
||||
} else if best_adv.model_id() == fp_adv.model_id() {
|
||||
// New advertised distance by the previous best device has
|
||||
// increased, so select new closest device.
|
||||
let next_best_adv_ref =
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
// 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.
|
||||
|
||||
use bluetooth::ServiceData;
|
||||
|
||||
/// Unit struct providing parsing operations for Fast Pair advertisements.
|
||||
pub(crate) struct FpDecoder;
|
||||
|
||||
impl FpDecoder {
|
||||
/// Retrieve the Fast Pair device model ID from a service data payload.
|
||||
/// https://developers.google.com/nearby/fast-pair/specifications/service/provider.
|
||||
/// * Length < 3: invalid payload
|
||||
/// * Length == 3: entire payload is the model ID
|
||||
/// * Length > 3: first byte specifies the length of the model ID, in bytes.
|
||||
/// Currently unavailable in Fast Pair devices and not supported.
|
||||
pub(crate) fn get_model_id_from_service_data<U: Copy>(
|
||||
service_data: &ServiceData<U>,
|
||||
) -> Result<Vec<u8>, anyhow::Error> {
|
||||
static MIN_MODEL_ID_LENGTH: usize = 3;
|
||||
let data = service_data.data();
|
||||
|
||||
if data.len() < MIN_MODEL_ID_LENGTH {
|
||||
// If service data too small, invalid payload.
|
||||
Err(anyhow::anyhow!(format!(
|
||||
"Invalid model ID for Fast Pair advertisement of length {}.",
|
||||
data.len()
|
||||
)))
|
||||
} else if data.len() == MIN_MODEL_ID_LENGTH {
|
||||
// Else if service data length is exactly 3, all bytes are the ID.
|
||||
Ok(data.clone())
|
||||
} else {
|
||||
// Else, this Fast Pair advertisement is currently unsupported.
|
||||
// b/294453912
|
||||
Err(anyhow::anyhow!(
|
||||
"This Fast Pair device is currently unsupported."
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
mod advertisement;
|
||||
mod api;
|
||||
mod bridge_generated; /* AUTO INJECTED BY flutter_rust_bridge. This line may not be accurate, and you can change it according to your needs. */
|
||||
mod bridge_generated;
|
||||
mod decoder; /* AUTO INJECTED BY flutter_rust_bridge. This line may not be accurate, and you can change it according to your needs. */
|
||||
|
||||
Reference in New Issue
Block a user