bcms 0.1.12

Bluetooth Client Manager Service for IoT device management
Documentation
pub mod store;

use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use std::collections::HashMap;

pub use store::DataStore;

/// Generic data type for IoT device measurements
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct DataType {
    pub data: HashMap<String, serde_json::Value>,
    pub address: String,
    pub timestamp: i64,
    pub name: String,
}

impl DataType {
    pub fn new(data: HashMap<String, serde_json::Value>, address: String, timestamp: i64, name: String) -> Self {
        Self {
            data,
            address,
            timestamp,
            name,
        }
    }

    pub fn from_timestamp_f64(data: HashMap<String, serde_json::Value>, address: String, timestamp: f64, name: String) -> Self {
        Self::new(data, address, timestamp.round() as i64, name)
    }
}

/// Battery level data type
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct BatteryLevelData {
    #[serde(flatten)]
    pub base: DataType,
    pub level: i32,
}

impl BatteryLevelData {
    pub fn new(data: HashMap<String, serde_json::Value>, address: String, timestamp: i64) -> Self {
        let level = data.get("level")
            .and_then(|v| v.as_i64())
            .unwrap_or(0) as i32;
        
        Self {
            base: DataType::new(data, address, timestamp, "battery_level".to_string()),
            level,
        }
    }

    pub fn from_timestamp_f64(data: HashMap<String, serde_json::Value>, address: String, timestamp: f64) -> Self {
        Self::new(data, address, timestamp.round() as i64)
    }
}

/// Heart rate data type
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct HeartRateData {
    #[serde(flatten)]
    pub base: DataType,
    pub rate: i32,
}

impl HeartRateData {
    pub fn new(data: HashMap<String, serde_json::Value>, address: String, timestamp: i64) -> Self {
        let rate = data.get("rate")
            .and_then(|v| v.as_i64())
            .unwrap_or(0) as i32;
        
        Self {
            base: DataType::new(data, address, timestamp, "heart_rate".to_string()),
            rate,
        }
    }

    pub fn from_timestamp_f64(data: HashMap<String, serde_json::Value>, address: String, timestamp: f64) -> Self {
        Self::new(data, address, timestamp.round() as i64)
    }
}

/// Temperature data type
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct TemperatureData {
    #[serde(flatten)]
    pub base: DataType,
    pub level: f64,
}

impl TemperatureData {
    pub fn new(data: HashMap<String, serde_json::Value>, address: String, timestamp: i64) -> Self {
        let level = data.get("level")
            .and_then(|v| v.as_f64())
            .unwrap_or(0.0);
        
        Self {
            base: DataType::new(data, address, timestamp, "temperature".to_string()),
            level,
        }
    }

    pub fn from_timestamp_f64(data: HashMap<String, serde_json::Value>, address: String, timestamp: f64) -> Self {
        Self::new(data, address, timestamp.round() as i64)
    }
}

/// Pressure data type
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct PressureData {
    #[serde(flatten)]
    pub base: DataType,
    pub level: f64,
}

impl PressureData {
    pub fn new(data: HashMap<String, serde_json::Value>, address: String, timestamp: i64) -> Self {
        let level = data.get("level")
            .and_then(|v| v.as_f64())
            .unwrap_or(0.0);
        
        Self {
            base: DataType::new(data, address, timestamp, "pressure".to_string()),
            level,
        }
    }

    pub fn from_timestamp_f64(data: HashMap<String, serde_json::Value>, address: String, timestamp: f64) -> Self {
        Self::new(data, address, timestamp.round() as i64)
    }
}

/// Blood pressure data type
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct BloodPressureData {
    #[serde(flatten)]
    pub base: DataType,
    pub sys: i32,
    pub dias: i32,
}

impl BloodPressureData {
    pub fn new(data: HashMap<String, serde_json::Value>, address: String, timestamp: i64) -> Self {
        let sys = data.get("sys")
            .and_then(|v| v.as_i64())
            .unwrap_or(0) as i32;
        let dias = data.get("dias")
            .and_then(|v| v.as_i64())
            .unwrap_or(0) as i32;
        
        Self {
            base: DataType::new(data, address, timestamp, "blood_pressure".to_string()),
            sys,
            dias,
        }
    }

    pub fn from_timestamp_f64(data: HashMap<String, serde_json::Value>, address: String, timestamp: f64) -> Self {
        Self::new(data, address, timestamp.round() as i64)
    }
}

/// Humidity data type
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct HumidityData {
    #[serde(flatten)]
    pub base: DataType,
    pub level: f64,
}

impl HumidityData {
    pub fn new(data: HashMap<String, serde_json::Value>, address: String, timestamp: i64) -> Self {
        let level = data.get("level")
            .and_then(|v| v.as_f64())
            .unwrap_or(0.0);
        
        Self {
            base: DataType::new(data, address, timestamp, "humidity".to_string()),
            level,
        }
    }

    pub fn from_timestamp_f64(data: HashMap<String, serde_json::Value>, address: String, timestamp: f64) -> Self {
        Self::new(data, address, timestamp.round() as i64)
    }
}

/// Alert data type
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct AlertData {
    #[serde(flatten)]
    pub base: DataType,
    pub id: String,
}

impl AlertData {
    pub fn new(data: HashMap<String, serde_json::Value>, address: String, timestamp: i64) -> Self {
        let id = data.get("id")
            .and_then(|v| v.as_str())
            .unwrap_or("")
            .to_string();
        
        Self {
            base: DataType::new(data, address, timestamp, "alert".to_string()),
            id,
        }
    }

    pub fn from_timestamp_f64(data: HashMap<String, serde_json::Value>, address: String, timestamp: f64) -> Self {
        Self::new(data, address, timestamp.round() as i64)
    }
}

/// Enum for all supported data types
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(tag = "type")]
pub enum IoTData {
    #[serde(rename = "battery_level")]
    BatteryLevel(BatteryLevelData),
    
    #[serde(rename = "heart_rate")]
    HeartRate(HeartRateData),
    
    #[serde(rename = "temperature")]
    Temperature(TemperatureData),
    
    #[serde(rename = "pressure")]
    Pressure(PressureData),
    
    #[serde(rename = "blood_pressure")]
    BloodPressure(BloodPressureData),
    
    #[serde(rename = "humidity")]
    Humidity(HumidityData),
    
    #[serde(rename = "alert")]
    Alert(AlertData),
    
    #[serde(rename = "generic")]
    Generic(DataType),
}

impl IoTData {
    pub fn address(&self) -> &str {
        match self {
            IoTData::BatteryLevel(d) => &d.base.address,
            IoTData::HeartRate(d) => &d.base.address,
            IoTData::Temperature(d) => &d.base.address,
            IoTData::Pressure(d) => &d.base.address,
            IoTData::BloodPressure(d) => &d.base.address,
            IoTData::Humidity(d) => &d.base.address,
            IoTData::Alert(d) => &d.base.address,
            IoTData::Generic(d) => &d.address,
        }
    }

    pub fn timestamp(&self) -> i64 {
        match self {
            IoTData::BatteryLevel(d) => d.base.timestamp,
            IoTData::HeartRate(d) => d.base.timestamp,
            IoTData::Temperature(d) => d.base.timestamp,
            IoTData::Pressure(d) => d.base.timestamp,
            IoTData::BloodPressure(d) => d.base.timestamp,
            IoTData::Humidity(d) => d.base.timestamp,
            IoTData::Alert(d) => d.base.timestamp,
            IoTData::Generic(d) => d.timestamp,
        }
    }

    pub fn name(&self) -> &str {
        match self {
            IoTData::BatteryLevel(d) => &d.base.name,
            IoTData::HeartRate(d) => &d.base.name,
            IoTData::Temperature(d) => &d.base.name,
            IoTData::Pressure(d) => &d.base.name,
            IoTData::BloodPressure(d) => &d.base.name,
            IoTData::Humidity(d) => &d.base.name,
            IoTData::Alert(d) => &d.base.name,
            IoTData::Generic(d) => &d.name,
        }
    }
}