- Implement RoomService for room management and metrics - Add AnalyticsService for sensor data analytics and trends - Extend models with Room, RoomCreate, RoomUpdate, RoomInfo - Add room CRUD endpoints to FastAPI app - Add database connection logic for MongoDB and Redis - Refactor sensor service logic into SensorService class
251 lines
9.0 KiB
Python
251 lines
9.0 KiB
Python
"""
|
|
Sensor service business logic
|
|
"""
|
|
|
|
import logging
|
|
from datetime import datetime, timedelta
|
|
from typing import List, Dict, Any, Optional
|
|
import json
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
class SensorService:
|
|
"""Service for managing sensors and sensor data"""
|
|
|
|
def __init__(self, db, redis_client):
|
|
self.db = db
|
|
self.redis = redis_client
|
|
|
|
async def get_sensors(self, room: Optional[str] = None, sensor_type: Optional[str] = None, status: Optional[str] = None) -> List[Dict[str, Any]]:
|
|
"""Get sensors with optional filtering"""
|
|
try:
|
|
query = {}
|
|
|
|
if room:
|
|
query["room"] = room
|
|
if sensor_type:
|
|
query["sensor_type"] = sensor_type
|
|
if status:
|
|
query["status"] = status
|
|
|
|
cursor = self.db.sensors.find(query)
|
|
sensors = []
|
|
|
|
async for sensor in cursor:
|
|
sensor["_id"] = str(sensor["_id"])
|
|
sensors.append(sensor)
|
|
|
|
return sensors
|
|
|
|
except Exception as e:
|
|
logger.error(f"Error getting sensors: {e}")
|
|
raise
|
|
|
|
async def get_sensor_details(self, sensor_id: str) -> Optional[Dict[str, Any]]:
|
|
"""Get detailed sensor information"""
|
|
try:
|
|
sensor = await self.db.sensors.find_one({"sensor_id": sensor_id})
|
|
|
|
if sensor:
|
|
sensor["_id"] = str(sensor["_id"])
|
|
|
|
# Get recent readings
|
|
recent_readings = await self.get_sensor_data(sensor_id, limit=10)
|
|
sensor["recent_readings"] = recent_readings.get("readings", [])
|
|
|
|
return sensor
|
|
|
|
return None
|
|
|
|
except Exception as e:
|
|
logger.error(f"Error getting sensor details: {e}")
|
|
raise
|
|
|
|
async def get_sensor_data(self, sensor_id: str, start_time: Optional[int] = None,
|
|
end_time: Optional[int] = None, limit: int = 100, offset: int = 0) -> Dict[str, Any]:
|
|
"""Get historical sensor data"""
|
|
try:
|
|
query = {"sensor_id": sensor_id}
|
|
|
|
if start_time or end_time:
|
|
query["timestamp"] = {}
|
|
if start_time:
|
|
query["timestamp"]["$gte"] = start_time
|
|
if end_time:
|
|
query["timestamp"]["$lte"] = end_time
|
|
|
|
# Get total count
|
|
total_count = await self.db.sensor_readings.count_documents(query)
|
|
|
|
# Get readings
|
|
cursor = self.db.sensor_readings.find(query).sort("timestamp", -1).skip(offset).limit(limit)
|
|
readings = []
|
|
|
|
async for reading in cursor:
|
|
reading["_id"] = str(reading["_id"])
|
|
readings.append(reading)
|
|
|
|
return {
|
|
"readings": readings,
|
|
"total_count": total_count,
|
|
"execution_time_ms": 0 # Placeholder
|
|
}
|
|
|
|
except Exception as e:
|
|
logger.error(f"Error getting sensor data: {e}")
|
|
raise
|
|
|
|
async def create_sensor(self, sensor_data) -> Dict[str, Any]:
|
|
"""Create a new sensor"""
|
|
try:
|
|
# Check if sensor already exists
|
|
existing = await self.db.sensors.find_one({"sensor_id": sensor_data.sensor_id})
|
|
if existing:
|
|
raise ValueError(f"Sensor {sensor_data.sensor_id} already exists")
|
|
|
|
# Create sensor document
|
|
sensor_doc = {
|
|
"sensor_id": sensor_data.sensor_id,
|
|
"name": sensor_data.name,
|
|
"sensor_type": sensor_data.sensor_type.value if hasattr(sensor_data.sensor_type, 'value') else str(sensor_data.sensor_type),
|
|
"room": sensor_data.room,
|
|
"location": sensor_data.location if hasattr(sensor_data, 'location') else None,
|
|
"status": "active",
|
|
"created_at": datetime.utcnow(),
|
|
"updated_at": datetime.utcnow()
|
|
}
|
|
|
|
result = await self.db.sensors.insert_one(sensor_doc)
|
|
|
|
return {"created_at": datetime.utcnow()}
|
|
|
|
except Exception as e:
|
|
logger.error(f"Error creating sensor: {e}")
|
|
raise
|
|
|
|
async def update_sensor(self, sensor_id: str, update_data: Dict[str, Any]) -> bool:
|
|
"""Update sensor metadata"""
|
|
try:
|
|
update_data["updated_at"] = datetime.utcnow()
|
|
|
|
result = await self.db.sensors.update_one(
|
|
{"sensor_id": sensor_id},
|
|
{"$set": update_data}
|
|
)
|
|
|
|
return result.modified_count > 0
|
|
|
|
except Exception as e:
|
|
logger.error(f"Error updating sensor: {e}")
|
|
raise
|
|
|
|
async def delete_sensor(self, sensor_id: str) -> Dict[str, Any]:
|
|
"""Delete a sensor and its data"""
|
|
try:
|
|
# Delete readings
|
|
readings_result = await self.db.sensor_readings.delete_many({"sensor_id": sensor_id})
|
|
|
|
# Delete sensor
|
|
await self.db.sensors.delete_one({"sensor_id": sensor_id})
|
|
|
|
return {"readings_deleted": readings_result.deleted_count}
|
|
|
|
except Exception as e:
|
|
logger.error(f"Error deleting sensor: {e}")
|
|
raise
|
|
|
|
async def ingest_sensor_data(self, sensor_data) -> Dict[str, Any]:
|
|
"""Ingest real-time sensor data"""
|
|
try:
|
|
# Create reading document
|
|
reading_doc = {
|
|
"sensor_id": sensor_data.sensor_id,
|
|
"timestamp": sensor_data.timestamp,
|
|
"value": sensor_data.value,
|
|
"unit": sensor_data.unit if hasattr(sensor_data, 'unit') else None,
|
|
"room": sensor_data.room if hasattr(sensor_data, 'room') else None,
|
|
"created_at": datetime.utcnow()
|
|
}
|
|
|
|
# Store in database
|
|
await self.db.sensor_readings.insert_one(reading_doc)
|
|
|
|
# Cache recent value in Redis
|
|
if self.redis:
|
|
cache_key = f"sensor:{sensor_data.sensor_id}:latest"
|
|
await self.redis.setex(cache_key, 3600, json.dumps(reading_doc, default=str))
|
|
|
|
return {"success": True}
|
|
|
|
except Exception as e:
|
|
logger.error(f"Error ingesting sensor data: {e}")
|
|
raise
|
|
|
|
async def export_data(self, start_time: int, end_time: int, sensor_ids: Optional[str] = None,
|
|
format: str = "json") -> Dict[str, Any]:
|
|
"""Export sensor data"""
|
|
try:
|
|
query = {
|
|
"timestamp": {"$gte": start_time, "$lte": end_time}
|
|
}
|
|
|
|
if sensor_ids:
|
|
sensor_list = [s.strip() for s in sensor_ids.split(",")]
|
|
query["sensor_id"] = {"$in": sensor_list}
|
|
|
|
cursor = self.db.sensor_readings.find(query).sort("timestamp", 1)
|
|
readings = []
|
|
|
|
async for reading in cursor:
|
|
reading["_id"] = str(reading["_id"])
|
|
readings.append(reading)
|
|
|
|
return {
|
|
"format": format,
|
|
"data": readings,
|
|
"total_records": len(readings),
|
|
"period": {"start": start_time, "end": end_time}
|
|
}
|
|
|
|
except Exception as e:
|
|
logger.error(f"Error exporting data: {e}")
|
|
raise
|
|
|
|
async def get_events(self, severity: Optional[str] = None, event_type: Optional[str] = None,
|
|
hours: int = 24, limit: int = 50) -> List[Dict[str, Any]]:
|
|
"""Get system events"""
|
|
try:
|
|
start_time = datetime.utcnow() - timedelta(hours=hours)
|
|
|
|
query = {"timestamp": {"$gte": start_time}}
|
|
|
|
if severity:
|
|
query["severity"] = severity
|
|
if event_type:
|
|
query["event_type"] = event_type
|
|
|
|
cursor = self.db.system_events.find(query).sort("timestamp", -1).limit(limit)
|
|
events = []
|
|
|
|
async for event in cursor:
|
|
event["_id"] = str(event["_id"])
|
|
events.append(event)
|
|
|
|
return events
|
|
|
|
except Exception as e:
|
|
logger.error(f"Error getting events: {e}")
|
|
return []
|
|
|
|
async def cleanup_old_data(self, cutoff_date: datetime):
|
|
"""Clean up old sensor data"""
|
|
try:
|
|
result = await self.db.sensor_readings.delete_many({
|
|
"created_at": {"$lt": cutoff_date}
|
|
})
|
|
|
|
logger.info(f"Cleaned up {result.deleted_count} old sensor readings")
|
|
|
|
except Exception as e:
|
|
logger.error(f"Error cleaning up old data: {e}")
|
|
raise |