use crate::accessory::{HapAccessoryType, HapCharacteristic, HapCharacteristicValue};
use crate::mapping::AccessoryMapping;
#[derive(Debug, Clone, Default)]
pub struct EdgeVitals {
pub presence: bool,
pub motion: bool,
pub fall_detected: bool,
pub breathing_present: bool,
pub ambient_temp_c: Option<f64>,
}
pub struct RuViewToHapMapper;
impl RuViewToHapMapper {
pub fn map(vitals: &EdgeVitals) -> Vec<AccessoryMapping> {
let mut out = Vec::with_capacity(4);
out.push(AccessoryMapping {
accessory_type: HapAccessoryType::OccupancySensor,
characteristics: vec![(
HapCharacteristic::OccupancyDetected,
HapCharacteristicValue::UInt8(if vitals.presence || vitals.breathing_present { 1 } else { 0 }),
)],
});
out.push(AccessoryMapping {
accessory_type: HapAccessoryType::MotionSensor,
characteristics: vec![(
HapCharacteristic::MotionDetected,
HapCharacteristicValue::Bool(vitals.motion),
)],
});
out.push(AccessoryMapping {
accessory_type: HapAccessoryType::LeakSensor,
characteristics: vec![(
HapCharacteristic::LeakDetected,
HapCharacteristicValue::UInt8(if vitals.fall_detected { 1 } else { 0 }),
)],
});
if let Some(temp) = vitals.ambient_temp_c {
out.push(AccessoryMapping {
accessory_type: HapAccessoryType::TemperatureSensor,
characteristics: vec![(
HapCharacteristic::CurrentTemperature,
HapCharacteristicValue::Float(temp),
)],
});
}
out
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::accessory::{HapAccessoryType, HapCharacteristic, HapCharacteristicValue};
#[test]
fn presence_true_maps_to_occupancy_detected_1() {
let vitals = EdgeVitals { presence: true, ..Default::default() };
let mappings = RuViewToHapMapper::map(&vitals);
let occ = mappings.iter().find(|m| m.accessory_type == HapAccessoryType::OccupancySensor).unwrap();
assert!(occ.characteristics.contains(&(
HapCharacteristic::OccupancyDetected,
HapCharacteristicValue::UInt8(1)
)));
}
#[test]
fn fall_detected_maps_to_leak_sensor() {
let vitals = EdgeVitals { fall_detected: true, ..Default::default() };
let mappings = RuViewToHapMapper::map(&vitals);
let leak = mappings.iter().find(|m| m.accessory_type == HapAccessoryType::LeakSensor).unwrap();
assert!(leak.characteristics.contains(&(
HapCharacteristic::LeakDetected,
HapCharacteristicValue::UInt8(1)
)));
}
#[test]
fn motion_false_maps_correctly() {
let vitals = EdgeVitals { motion: false, ..Default::default() };
let mappings = RuViewToHapMapper::map(&vitals);
let mot = mappings.iter().find(|m| m.accessory_type == HapAccessoryType::MotionSensor).unwrap();
assert!(mot.characteristics.contains(&(
HapCharacteristic::MotionDetected,
HapCharacteristicValue::Bool(false)
)));
}
#[test]
fn ambient_temp_emits_temperature_mapping() {
let vitals = EdgeVitals { ambient_temp_c: Some(22.5), ..Default::default() };
let mappings = RuViewToHapMapper::map(&vitals);
let temp = mappings.iter().find(|m| m.accessory_type == HapAccessoryType::TemperatureSensor);
assert!(temp.is_some());
}
#[test]
fn no_ambient_temp_omits_temperature_mapping() {
let vitals = EdgeVitals { ambient_temp_c: None, ..Default::default() };
let mappings = RuViewToHapMapper::map(&vitals);
assert!(mappings.iter().all(|m| m.accessory_type != HapAccessoryType::TemperatureSensor));
}
#[test]
fn breathing_present_triggers_occupancy() {
let vitals = EdgeVitals { presence: false, breathing_present: true, ..Default::default() };
let mappings = RuViewToHapMapper::map(&vitals);
let occ = mappings.iter().find(|m| m.accessory_type == HapAccessoryType::OccupancySensor).unwrap();
assert!(occ.characteristics.contains(&(
HapCharacteristic::OccupancyDetected,
HapCharacteristicValue::UInt8(1)
)));
}
}