bcms 0.1.12

Bluetooth Client Manager Service for IoT device management
Documentation
use crate::data::IoTData;
use crate::devices::DeviceInfo;
use serde::{Deserialize, Serialize};

/// Device information for RPC submissions
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RpcDeviceInfo {
    pub address: String,
    pub name: String,
    pub connection_type: String,
}

impl RpcDeviceInfo {
    pub fn new(address: String, name: String, connection_type: String) -> Self {
        Self {
            address,
            name,
            connection_type,
        }
    }
}

impl From<&DeviceInfo> for RpcDeviceInfo {
    fn from(device: &DeviceInfo) -> Self {
        Self {
            address: device.address.clone(),
            name: device.name.clone(),
            connection_type: device.connection_type.clone().unwrap_or_else(|| "bluetooth_le".to_string()),
        }
    }
}

/// IoT data batch for RPC submissions
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RpcDataBatch {
    pub device: RpcDeviceInfo,
    pub data: Vec<IoTData>,
}

impl RpcDataBatch {
    pub fn new(device: RpcDeviceInfo, data: Vec<IoTData>) -> Self {
        Self { device, data }
    }

    /// Convert to JSON strings format expected by RPC
    pub fn to_rpc_format(&self) -> crate::Result<(RpcDeviceInfo, Vec<String>)> {
        let json_data: Result<Vec<String>, _> = self
            .data
            .iter()
            .map(|item| serde_json::to_string(item))
            .collect();

        match json_data {
            Ok(data_strings) => Ok((self.device.clone(), data_strings)),
            Err(e) => Err(crate::Error::General(format!("Failed to serialize IoT data: {}", e))),
        }
    }
}

/// Helper functions for creating IoT data
pub mod helpers {
    use crate::data::*;
    use std::collections::HashMap;

    /// Create heart rate data
    pub fn create_heart_rate_data(address: String, rate: i32, timestamp: i64) -> IoTData {
        let mut data = HashMap::new();
        data.insert("rate".to_string(), serde_json::Value::Number(rate.into()));
        
        let heart_rate_data = HeartRateData::new(data, address, timestamp);
        IoTData::HeartRate(heart_rate_data)
    }

    /// Create temperature data
    pub fn create_temperature_data(address: String, level: f64, timestamp: i64) -> IoTData {
        let mut data = HashMap::new();
        data.insert("level".to_string(), serde_json::Value::Number(
            serde_json::Number::from_f64(level).unwrap_or_else(|| serde_json::Number::from(0))
        ));
        
        let temperature_data = TemperatureData::new(data, address, timestamp);
        IoTData::Temperature(temperature_data)
    }

    /// Create battery level data
    pub fn create_battery_level_data(address: String, level: i32, timestamp: i64) -> IoTData {
        let mut data = HashMap::new();
        data.insert("level".to_string(), serde_json::Value::Number(level.into()));
        
        let battery_data = BatteryLevelData::new(data, address, timestamp);
        IoTData::BatteryLevel(battery_data)
    }

    /// Create pressure data
    pub fn create_pressure_data(address: String, level: f64, timestamp: i64) -> IoTData {
        let mut data = HashMap::new();
        data.insert("level".to_string(), serde_json::Value::Number(
            serde_json::Number::from_f64(level).unwrap_or_else(|| serde_json::Number::from(0))
        ));
        
        let pressure_data = PressureData::new(data, address, timestamp);
        IoTData::Pressure(pressure_data)
    }

    /// Create blood pressure data
    pub fn create_blood_pressure_data(address: String, sys: i32, dias: i32, timestamp: i64) -> IoTData {
        let mut data = HashMap::new();
        data.insert("sys".to_string(), serde_json::Value::Number(sys.into()));
        data.insert("dias".to_string(), serde_json::Value::Number(dias.into()));

        let blood_pressure_data = BloodPressureData::new(data, address, timestamp);
        IoTData::BloodPressure(blood_pressure_data)
    }

    /// Create humidity data
    pub fn create_humidity_data(address: String, level: f64, timestamp: i64) -> IoTData {
        let mut data = HashMap::new();
        data.insert("level".to_string(), serde_json::Value::Number(
            serde_json::Number::from_f64(level).unwrap_or_else(|| serde_json::Number::from(0))
        ));
        
        let humidity_data = HumidityData::new(data, address, timestamp);
        IoTData::Humidity(humidity_data)
    }

    /// Create alert data
    pub fn create_alert_data(address: String, id: String, timestamp: i64) -> IoTData {
        let mut data = HashMap::new();
        data.insert("id".to_string(), serde_json::Value::String(id));
        
        let alert_data = AlertData::new(data, address, timestamp);
        IoTData::Alert(alert_data)
    }

    /// Create generic data
    pub fn create_generic_data(address: String, name: String, data: HashMap<String, serde_json::Value>, timestamp: i64) -> IoTData {
        let generic_data = DataType::new(data, address, timestamp, name);
        IoTData::Generic(generic_data)
    }

    /// Get current timestamp in seconds
    pub fn current_timestamp() -> i64 {
        use std::time::{SystemTime, UNIX_EPOCH};
        SystemTime::now()
            .duration_since(UNIX_EPOCH)
            .unwrap_or_default()
            .as_secs() as i64
    }
}

/// RPC client helper for easy data submission
pub struct RpcHelper {
    client: crate::rpc::RpcClient,
}

impl RpcHelper {
    pub fn new(server_address: std::net::SocketAddr) -> Self {
        Self {
            client: crate::rpc::RpcClient::new(server_address),
        }
    }

    /// Submit a batch of IoT data
    pub async fn submit_data_batch(&self, batch: RpcDataBatch) -> crate::Result<(bool, Vec<String>)> {
        let (device_info, data_strings) = batch.to_rpc_format()?;
        
        self.client.submit_data(
            &device_info.address,
            &device_info.name,
            &device_info.connection_type,
            batch.data
        ).await
    }

    /// Submit single IoT data item
    pub async fn submit_single_data(
        &self,
        device: RpcDeviceInfo,
        data: IoTData
    ) -> crate::Result<(bool, Vec<String>)> {
        let batch = RpcDataBatch::new(device, vec![data]);
        self.submit_data_batch(batch).await
    }
}