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

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());
}
}