- Store energy data in separate MongoDB collections for each SLGs/Community/Building directory - Update FTP monitor and database manager to track directory paths and select appropriate collections - Add collection stats to database statistics API - Update sensor and token services for improved API consistency - Add 'rb' (rebuild and restart) option to deploy.sh script
560 lines
18 KiB
Python
560 lines
18 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
Bootstrap script to populate the database with sensors and room configurations.
|
|
This script creates a realistic smart building sensor setup for testing and development.
|
|
"""
|
|
|
|
import asyncio
|
|
import json
|
|
import sys
|
|
import logging
|
|
from datetime import datetime
|
|
from typing import List, Dict, Any
|
|
import aiohttp
|
|
|
|
# Configure logging
|
|
logging.basicConfig(level=logging.INFO)
|
|
logger = logging.getLogger(__name__)
|
|
|
|
# Base URLs
|
|
API_BASE_URL = "http://localhost:8000"
|
|
SENSOR_SERVICE_URL = "http://localhost:8007"
|
|
|
|
s = {
|
|
'id': 'sensor_1',
|
|
'name': 'Energy Monitor 1',
|
|
'type': 'energy',
|
|
'room': 'Conference Room A',
|
|
'status': 'online',
|
|
'lastSeen': Date.now() / 1000,
|
|
'capabilities': {
|
|
'monitoring': ['energy'],
|
|
'actions': [],
|
|
},
|
|
'metadata': {
|
|
'location': 'Wall mounted',
|
|
'model': 'EM-100',
|
|
'firmware': '2.1.0',
|
|
},
|
|
},{
|
|
'id': 'sensor_2',
|
|
'name': 'HVAC Controller 1',
|
|
'type': 'hvac',
|
|
'room': 'Conference Room A',
|
|
'status': 'online',
|
|
'lastSeen': Date.now() / 1000,
|
|
'capabilities': {
|
|
'monitoring': ['temperature', 'co2'],
|
|
'actions': [
|
|
{
|
|
'id': 'temp_adjust',
|
|
'name': 'Adjust Temperature',
|
|
'type': 'adjust',
|
|
'icon': '🌡️',
|
|
'parameters': { min: 18, max: 28, step: 0.5 },
|
|
},
|
|
{
|
|
'id': 'fan_speed',
|
|
'name': 'Fan Speed',
|
|
'type': 'adjust',
|
|
'icon': '💨',
|
|
'parameters': { min: 0, max: 5, step: 1 },
|
|
},
|
|
{ 'id': 'power_toggle', 'name': 'Power', 'type': 'toggle', 'icon': '⚡' },
|
|
],
|
|
},
|
|
metadata: {
|
|
location: 'Ceiling mounted',
|
|
model: 'HVAC-200',
|
|
firmware: '3.2.1',
|
|
},
|
|
},
|
|
{
|
|
id: 'sensor_3',
|
|
name: 'Smart Light Controller',
|
|
type: 'lighting',
|
|
room: 'Office Floor 1',
|
|
status: 'online',
|
|
lastSeen: Date.now() / 1000,
|
|
capabilities: {
|
|
monitoring: ['energy'],
|
|
actions: [
|
|
{
|
|
id: 'brightness',
|
|
name: 'Brightness',
|
|
type: 'adjust',
|
|
icon: '💡',
|
|
parameters: { min: 0, max: 100, step: 5 },
|
|
},
|
|
{ id: 'power_toggle', name: 'Power', type: 'toggle', icon: '⚡' },
|
|
{
|
|
id: 'scene',
|
|
name: 'Scene',
|
|
type: 'adjust',
|
|
icon: '🎨',
|
|
parameters: { options: ['Work', 'Meeting', 'Presentation', 'Relax'] },
|
|
},
|
|
],
|
|
},
|
|
metadata: {
|
|
location: 'Ceiling grid',
|
|
model: 'SL-300',
|
|
firmware: '1.5.2',
|
|
},
|
|
},
|
|
{
|
|
id: 'sensor_4',
|
|
name: 'CO2 Sensor',
|
|
type: 'co2',
|
|
room: 'Meeting Room 1',
|
|
status: 'online',
|
|
lastSeen: Date.now() / 1000,
|
|
capabilities: {
|
|
monitoring: ['co2', 'temperature', 'humidity'],
|
|
actions: [{ id: 'calibrate', name: 'Calibrate', type: 'trigger', icon: '⚙️' }],
|
|
},
|
|
metadata: {
|
|
location: 'Wall mounted',
|
|
model: 'CO2-150',
|
|
firmware: '2.0.3',
|
|
battery: 85,
|
|
},
|
|
},
|
|
{
|
|
id: 'sensor_5',
|
|
name: 'Security Camera',
|
|
type: 'security',
|
|
room: 'Lobby',
|
|
status: 'online',
|
|
lastSeen: Date.now() / 1000,
|
|
capabilities: {
|
|
monitoring: ['motion'],
|
|
actions: [
|
|
{ id: 'record_toggle', name: 'Recording', type: 'toggle', icon: '📹' },
|
|
{ id: 'ptz_control', name: 'Pan/Tilt/Zoom', type: 'trigger', icon: '🎥' },
|
|
{ id: 'night_mode', name: 'Night Mode', type: 'toggle', icon: '🌙' },
|
|
],
|
|
},
|
|
metadata: {
|
|
location: 'Corner ceiling',
|
|
model: 'SEC-400',
|
|
firmware: '4.1.0',
|
|
},
|
|
},
|
|
# Bootstrap data configuration
|
|
BOOTSTRAP_SENSORS = [
|
|
# Living Room Sensors
|
|
{
|
|
"sensor_id": "lr_energy_001",
|
|
"name": "Living Room Main Energy Monitor",
|
|
"sensor_type": "energy",
|
|
"room": "living_room",
|
|
"location": "Main electrical panel - Living Room circuit",
|
|
"floor": "1",
|
|
"manufacturer": "SmartMeter Co",
|
|
"model": "SM-E300",
|
|
"status": "online"
|
|
},
|
|
{
|
|
"sensor_id": "lr_co2_001",
|
|
"name": "Living Room Air Quality Monitor",
|
|
"sensor_type": "co2",
|
|
"room": "living_room",
|
|
"location": "Wall mounted near seating area",
|
|
"floor": "1",
|
|
"manufacturer": "AirSense",
|
|
"model": "AS-CO2-Pro",
|
|
"status": "online"
|
|
},
|
|
{
|
|
"sensor_id": "lr_temp_001",
|
|
"name": "Living Room Temperature Sensor",
|
|
"sensor_type": "temperature",
|
|
"room": "living_room",
|
|
"location": "Central wall position",
|
|
"floor": "1",
|
|
"manufacturer": "TempTech",
|
|
"model": "TT-T200",
|
|
"status": "online"
|
|
},
|
|
|
|
# Kitchen Sensors
|
|
{
|
|
"sensor_id": "kt_energy_001",
|
|
"name": "Kitchen Appliances Energy Monitor",
|
|
"sensor_type": "energy",
|
|
"room": "kitchen",
|
|
"location": "Kitchen appliance circuit",
|
|
"floor": "1",
|
|
"manufacturer": "SmartMeter Co",
|
|
"model": "SM-E300",
|
|
"status": "online"
|
|
},
|
|
{
|
|
"sensor_id": "kt_humidity_001",
|
|
"name": "Kitchen Humidity Sensor",
|
|
"sensor_type": "humidity",
|
|
"room": "kitchen",
|
|
"location": "Above sink area",
|
|
"floor": "1",
|
|
"manufacturer": "HumidSense",
|
|
"model": "HS-H150",
|
|
"status": "online"
|
|
},
|
|
{
|
|
"sensor_id": "kt_temp_001",
|
|
"name": "Kitchen Temperature Monitor",
|
|
"sensor_type": "temperature",
|
|
"room": "kitchen",
|
|
"location": "Central kitchen position",
|
|
"floor": "1",
|
|
"manufacturer": "TempTech",
|
|
"model": "TT-T200",
|
|
"status": "online"
|
|
},
|
|
|
|
# Bedroom Sensors
|
|
{
|
|
"sensor_id": "br_energy_001",
|
|
"name": "Bedroom Energy Monitor",
|
|
"sensor_type": "energy",
|
|
"room": "bedroom",
|
|
"location": "Bedroom electrical circuit",
|
|
"floor": "1",
|
|
"manufacturer": "SmartMeter Co",
|
|
"model": "SM-E200",
|
|
"status": "online"
|
|
},
|
|
{
|
|
"sensor_id": "br_co2_001",
|
|
"name": "Bedroom Air Quality Monitor",
|
|
"sensor_type": "co2",
|
|
"room": "bedroom",
|
|
"location": "Bedside wall mount",
|
|
"floor": "1",
|
|
"manufacturer": "AirSense",
|
|
"model": "AS-CO2-Basic",
|
|
"status": "online"
|
|
},
|
|
{
|
|
"sensor_id": "br_temp_001",
|
|
"name": "Bedroom Temperature Sensor",
|
|
"sensor_type": "temperature",
|
|
"room": "bedroom",
|
|
"location": "Opposite wall from bed",
|
|
"floor": "1",
|
|
"manufacturer": "TempTech",
|
|
"model": "TT-T100",
|
|
"status": "online"
|
|
},
|
|
|
|
# Office Sensors
|
|
{
|
|
"sensor_id": "of_energy_001",
|
|
"name": "Office Equipment Energy Monitor",
|
|
"sensor_type": "energy",
|
|
"room": "office",
|
|
"location": "Office equipment circuit",
|
|
"floor": "1",
|
|
"manufacturer": "SmartMeter Co",
|
|
"model": "SM-E300",
|
|
"status": "online"
|
|
},
|
|
{
|
|
"sensor_id": "of_co2_001",
|
|
"name": "Office Air Quality Monitor",
|
|
"sensor_type": "co2",
|
|
"room": "office",
|
|
"location": "Desk area wall mount",
|
|
"floor": "1",
|
|
"manufacturer": "AirSense",
|
|
"model": "AS-CO2-Pro",
|
|
"status": "online"
|
|
},
|
|
{
|
|
"sensor_id": "of_motion_001",
|
|
"name": "Office Motion Detector",
|
|
"sensor_type": "motion",
|
|
"room": "office",
|
|
"location": "Ceiling mounted - center",
|
|
"floor": "1",
|
|
"manufacturer": "MotionTech",
|
|
"model": "MT-M100",
|
|
"status": "online"
|
|
},
|
|
|
|
# Bathroom Sensors
|
|
{
|
|
"sensor_id": "bt_humidity_001",
|
|
"name": "Bathroom Humidity Monitor",
|
|
"sensor_type": "humidity",
|
|
"room": "bathroom",
|
|
"location": "Ceiling mounted",
|
|
"floor": "1",
|
|
"manufacturer": "HumidSense",
|
|
"model": "HS-H200",
|
|
"status": "online"
|
|
},
|
|
{
|
|
"sensor_id": "bt_temp_001",
|
|
"name": "Bathroom Temperature Sensor",
|
|
"sensor_type": "temperature",
|
|
"room": "bathroom",
|
|
"location": "Wall mounted near mirror",
|
|
"floor": "1",
|
|
"manufacturer": "TempTech",
|
|
"model": "TT-T150",
|
|
"status": "online"
|
|
},
|
|
|
|
# Garage Sensors
|
|
{
|
|
"sensor_id": "gr_energy_001",
|
|
"name": "Garage Energy Monitor",
|
|
"sensor_type": "energy",
|
|
"room": "garage",
|
|
"location": "Garage main circuit",
|
|
"floor": "0",
|
|
"manufacturer": "SmartMeter Co",
|
|
"model": "SM-E100",
|
|
"status": "online"
|
|
},
|
|
{
|
|
"sensor_id": "gr_motion_001",
|
|
"name": "Garage Motion Detector",
|
|
"sensor_type": "motion",
|
|
"room": "garage",
|
|
"location": "Ceiling mounted - entrance",
|
|
"floor": "0",
|
|
"manufacturer": "MotionTech",
|
|
"model": "MT-M200",
|
|
"status": "online"
|
|
}
|
|
]
|
|
|
|
BOOTSTRAP_ROOMS = [
|
|
{
|
|
"name": "living_room",
|
|
"display_name": "Living Room",
|
|
"description": "Main living area with entertainment center",
|
|
"floor": "1",
|
|
"area_sqm": 35.5,
|
|
"room_type": "living"
|
|
},
|
|
{
|
|
"name": "kitchen",
|
|
"display_name": "Kitchen",
|
|
"description": "Main kitchen with appliances",
|
|
"floor": "1",
|
|
"area_sqm": 15.2,
|
|
"room_type": "kitchen"
|
|
},
|
|
{
|
|
"name": "bedroom",
|
|
"display_name": "Master Bedroom",
|
|
"description": "Primary bedroom",
|
|
"floor": "1",
|
|
"area_sqm": 20.1,
|
|
"room_type": "bedroom"
|
|
},
|
|
{
|
|
"name": "office",
|
|
"display_name": "Home Office",
|
|
"description": "Work from home office space",
|
|
"floor": "1",
|
|
"area_sqm": 12.8,
|
|
"room_type": "office"
|
|
},
|
|
{
|
|
"name": "bathroom",
|
|
"display_name": "Main Bathroom",
|
|
"description": "Primary bathroom",
|
|
"floor": "1",
|
|
"area_sqm": 8.5,
|
|
"room_type": "bathroom"
|
|
},
|
|
{
|
|
"name": "garage",
|
|
"display_name": "Garage",
|
|
"description": "Two-car garage with workshop area",
|
|
"floor": "0",
|
|
"area_sqm": 42.0,
|
|
"room_type": "garage"
|
|
}
|
|
]
|
|
|
|
async def generate_auth_token() -> str:
|
|
"""Generate authentication token for API calls"""
|
|
token_payload = {
|
|
"name": "bootstrap_user",
|
|
"list_of_resources": ["sensors", "rooms", "analytics", "health", "data", "export", "events"],
|
|
"data_aggregation": True,
|
|
"time_aggregation": True,
|
|
"embargo": 0,
|
|
"exp_hours": 24
|
|
}
|
|
|
|
async with aiohttp.ClientSession() as session:
|
|
async with session.post(
|
|
f"{API_BASE_URL}/api/v1/tokens/generate",
|
|
json=token_payload,
|
|
headers={"Content-Type": "application/json"}
|
|
) as response:
|
|
if response.status == 200:
|
|
data = await response.json()
|
|
logger.info("Successfully generated authentication token")
|
|
return data["token"]
|
|
else:
|
|
error_text = await response.text()
|
|
raise Exception(f"Failed to generate token: {response.status} - {error_text}")
|
|
|
|
async def create_rooms(auth_token: str) -> bool:
|
|
"""Create rooms in the database"""
|
|
logger.info("Creating bootstrap rooms...")
|
|
|
|
headers = {
|
|
"Authorization": f"Bearer {auth_token}",
|
|
"Content-Type": "application/json"
|
|
}
|
|
|
|
success_count = 0
|
|
async with aiohttp.ClientSession() as session:
|
|
for room in BOOTSTRAP_ROOMS:
|
|
try:
|
|
async with session.post(
|
|
f"{SENSOR_SERVICE_URL}/rooms",
|
|
json=room,
|
|
headers=headers
|
|
) as response:
|
|
if response.status in [200, 201]:
|
|
success_count += 1
|
|
logger.info(f"✓ Created room: {room['display_name']}")
|
|
elif response.status == 400:
|
|
# Room might already exist
|
|
error_data = await response.json()
|
|
if "already exists" in error_data.get("detail", ""):
|
|
logger.info(f"○ Room already exists: {room['display_name']}")
|
|
success_count += 1
|
|
else:
|
|
logger.error(f"✗ Failed to create room {room['name']}: {error_data}")
|
|
else:
|
|
error_text = await response.text()
|
|
logger.error(f"✗ Failed to create room {room['name']}: {response.status} - {error_text}")
|
|
except Exception as e:
|
|
logger.error(f"✗ Exception creating room {room['name']}: {e}")
|
|
|
|
logger.info(f"Rooms created: {success_count}/{len(BOOTSTRAP_ROOMS)}")
|
|
return success_count > 0
|
|
|
|
async def create_sensors(auth_token: str) -> bool:
|
|
"""Create sensors in the database"""
|
|
logger.info("Creating bootstrap sensors...")
|
|
|
|
headers = {
|
|
"Authorization": f"Bearer {auth_token}",
|
|
"Content-Type": "application/json"
|
|
}
|
|
|
|
success_count = 0
|
|
async with aiohttp.ClientSession() as session:
|
|
for sensor in BOOTSTRAP_SENSORS:
|
|
try:
|
|
async with session.post(
|
|
f"{SENSOR_SERVICE_URL}/sensors",
|
|
json=sensor,
|
|
headers=headers
|
|
) as response:
|
|
if response.status in [200, 201]:
|
|
success_count += 1
|
|
logger.info(f"✓ Created sensor: {sensor['name']} ({sensor['sensor_id']})")
|
|
elif response.status == 400:
|
|
# Sensor might already exist
|
|
error_data = await response.json()
|
|
if "already exists" in error_data.get("detail", ""):
|
|
logger.info(f"○ Sensor already exists: {sensor['sensor_id']}")
|
|
success_count += 1
|
|
else:
|
|
logger.error(f"✗ Failed to create sensor {sensor['sensor_id']}: {error_data}")
|
|
else:
|
|
error_text = await response.text()
|
|
logger.error(f"✗ Failed to create sensor {sensor['sensor_id']}: {response.status} - {error_text}")
|
|
except Exception as e:
|
|
logger.error(f"✗ Exception creating sensor {sensor['sensor_id']}: {e}")
|
|
|
|
logger.info(f"Sensors created: {success_count}/{len(BOOTSTRAP_SENSORS)}")
|
|
return success_count > 0
|
|
|
|
async def verify_bootstrap() -> None:
|
|
"""Verify that sensors were created successfully"""
|
|
logger.info("Verifying bootstrap results...")
|
|
|
|
try:
|
|
# Check sensors directly on sensor service (no auth required for health checks)
|
|
async with aiohttp.ClientSession() as session:
|
|
async with session.get(f"{SENSOR_SERVICE_URL}/sensors/get") as response:
|
|
if response.status == 200:
|
|
data = await response.json()
|
|
logger.info(f"✓ Total sensors in database: {data['count']}")
|
|
|
|
# Group by room
|
|
rooms = {}
|
|
for sensor in data.get('sensors', []):
|
|
room = sensor.get('room', 'unknown')
|
|
if room not in rooms:
|
|
rooms[room] = []
|
|
rooms[room].append(sensor['sensor_id'])
|
|
|
|
for room, sensors in rooms.items():
|
|
logger.info(f" - {room}: {len(sensors)} sensors")
|
|
else:
|
|
logger.error(f"Failed to verify sensors: {response.status}")
|
|
|
|
async with session.get(f"{SENSOR_SERVICE_URL}/rooms") as response:
|
|
if response.status == 200:
|
|
data = await response.json()
|
|
logger.info(f"✓ Total rooms in database: {data.get('count', 0)}")
|
|
else:
|
|
logger.error(f"Failed to verify rooms: {response.status}")
|
|
|
|
except Exception as e:
|
|
logger.error(f"✗ Exception during verification: {e}")
|
|
|
|
async def main():
|
|
"""Main bootstrap function"""
|
|
logger.info("=== Starting Sensor Bootstrap Process ===")
|
|
|
|
try:
|
|
# Step 1: Generate authentication token
|
|
logger.info("Step 1: Generating authentication token...")
|
|
auth_token = await generate_auth_token()
|
|
|
|
# Step 2: Create rooms
|
|
logger.info("Step 2: Creating rooms...")
|
|
rooms_success = await create_rooms(auth_token)
|
|
|
|
# Step 3: Create sensors
|
|
logger.info("Step 3: Creating sensors...")
|
|
sensors_success = await create_sensors(auth_token)
|
|
|
|
# Step 4: Verify results
|
|
logger.info("Step 4: Verifying bootstrap...")
|
|
await verify_bootstrap()
|
|
|
|
if rooms_success and sensors_success:
|
|
logger.info("=== Bootstrap Complete! ===")
|
|
logger.info("You can now run the data simulator to generate real-time sensor data.")
|
|
logger.info("Run: python data_simulator_enhanced.py")
|
|
return True
|
|
else:
|
|
logger.error("=== Bootstrap Failed ===")
|
|
return False
|
|
|
|
except Exception as e:
|
|
logger.error(f"Bootstrap failed with error: {e}")
|
|
return False
|
|
|
|
if __name__ == "__main__":
|
|
# Run the bootstrap
|
|
success = asyncio.run(main())
|
|
sys.exit(0 if success else 1)
|