[fp-rs] Now using model ID instead of BLE address for keeping track of unique devices.

This commit is contained in:
Lucas Silva Shepard
2023-08-14 13:32:22 -07:00
parent f8fb8d4b52
commit a57b55ad98
5 changed files with 119 additions and 26 deletions
+2
View File
@@ -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"
+60 -19
View File
@@ -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
}
}
+5 -6
View File
@@ -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 =
+50
View File
@@ -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."
))
}
}
}
+2 -1
View File
@@ -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. */