You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
135 lines
4.0 KiB
135 lines
4.0 KiB
use serde::{Deserialize, Serialize};
|
|
use crate::device::{Device, DeviceType, get_device_types};
|
|
|
|
#[derive(Debug, Serialize, Deserialize, Clone)]
|
|
pub struct Repo {
|
|
#[serde(default)]
|
|
hr_sensors: Vec<Device>,
|
|
#[serde(default)]
|
|
cad_sensors: Vec<Device>,
|
|
#[serde(default)]
|
|
trainers: Vec<Device>,
|
|
}
|
|
|
|
impl Repo {
|
|
pub fn new() -> Self {
|
|
Self {
|
|
hr_sensors: Vec::new(),
|
|
cad_sensors: Vec::new(),
|
|
trainers: Vec::new(),
|
|
}
|
|
}
|
|
}
|
|
|
|
pub fn repo_from_devices(devices: Vec<Device>) -> Repo {
|
|
let mut repo = Repo::new();
|
|
|
|
for dev in devices.into_iter() {
|
|
let types = get_device_types(&dev);
|
|
|
|
for t in types {
|
|
match t {
|
|
DeviceType::HrSensor => repo.hr_sensors.push(dev.clone()),
|
|
DeviceType::CadSensor => repo.cad_sensors.push(dev.clone()),
|
|
DeviceType::Trainer => repo.trainers.push(dev.clone()),
|
|
}
|
|
}
|
|
}
|
|
|
|
fn sort_by_rssi_desc(v: &mut [Device]) {
|
|
v.sort_by(|a, b| {
|
|
let ra = a.rssi;
|
|
let rb = b.rssi;
|
|
match (ra, rb) {
|
|
(Some(va), Some(vb)) => vb.cmp(&va),
|
|
(Some(_), None) => std::cmp::Ordering::Less,
|
|
(None, Some(_)) => std::cmp::Ordering::Greater,
|
|
(None, None) => std::cmp::Ordering::Equal,
|
|
}
|
|
});
|
|
}
|
|
|
|
sort_by_rssi_desc(&mut repo.hr_sensors);
|
|
sort_by_rssi_desc(&mut repo.cad_sensors);
|
|
sort_by_rssi_desc(&mut repo.trainers);
|
|
|
|
repo
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
|
|
/// Helper: a cadence device (CSC service 0x1816) with the given RSSI.
|
|
fn cadence_device(rssi: Option<i16>) -> Device {
|
|
Device {
|
|
id: format!("cad-{rssi:?}"),
|
|
address: "00:00:00:00:00:00".into(),
|
|
display_name: None,
|
|
rssi,
|
|
service_uuids: vec!["1816".into()],
|
|
characteristics: vec![],
|
|
manufacturer_data: None,
|
|
metadata: None,
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn test_repo_classifies_magene_as_cadence_sensor() {
|
|
let magene = Device {
|
|
id: "hci0/dev_F1_F2_7C_77_14_BE".into(),
|
|
address: "F1:F2:7C:77:14:BE".into(),
|
|
display_name: Some("10911-17".into()),
|
|
rssi: Some(-42),
|
|
service_uuids: vec!["00001816-0000-1000-8000-00805f9b34fb".into()],
|
|
characteristics: vec![],
|
|
manufacturer_data: Some("6b009f2a110300030072".into()),
|
|
metadata: None,
|
|
};
|
|
|
|
let repo = repo_from_devices(vec![magene]);
|
|
|
|
assert_eq!(repo.cad_sensors.len(), 1);
|
|
assert!(repo.hr_sensors.is_empty());
|
|
assert!(repo.trainers.is_empty());
|
|
}
|
|
|
|
#[test]
|
|
fn test_sorts_devices_by_rssi_descending() {
|
|
let devices = vec![
|
|
cadence_device(Some(-80)),
|
|
cadence_device(Some(-42)),
|
|
cadence_device(Some(-60)),
|
|
];
|
|
let repo = repo_from_devices(devices);
|
|
let rssis: Vec<_> = repo.cad_sensors.iter().map(|d| d.rssi).collect();
|
|
assert_eq!(rssis, vec![Some(-42), Some(-60), Some(-80)]);
|
|
}
|
|
|
|
#[test]
|
|
fn test_devices_without_rssi_go_last() {
|
|
let devices = vec![cadence_device(None), cadence_device(Some(-50))];
|
|
let repo = repo_from_devices(devices);
|
|
assert_eq!(repo.cad_sensors[0].rssi, Some(-50));
|
|
assert_eq!(repo.cad_sensors[1].rssi, None);
|
|
}
|
|
|
|
#[test]
|
|
fn test_indoor_bike_lands_in_trainer_and_cadence_buckets() {
|
|
let indoor_bike = Device {
|
|
id: "indoor-bike".into(),
|
|
address: "00:00:00:00:00:00".into(),
|
|
display_name: None,
|
|
rssi: Some(-70),
|
|
service_uuids: vec![],
|
|
characteristics: vec!["2ad2".into()],
|
|
manufacturer_data: None,
|
|
metadata: None,
|
|
};
|
|
let repo = repo_from_devices(vec![indoor_bike]);
|
|
assert_eq!(repo.trainers.len(), 1);
|
|
assert_eq!(repo.cad_sensors.len(), 1);
|
|
assert!(repo.hr_sensors.is_empty());
|
|
}
|
|
}
|