first commit

This commit is contained in:
rafaeldpsilva
2025-09-09 13:46:42 +01:00
commit a7a18e6295
77 changed files with 8678 additions and 0 deletions

View File

@@ -0,0 +1,262 @@
"""
Battery Management Microservice
Handles battery monitoring, charging, and energy storage optimization.
Port: 8002
"""
import asyncio
from datetime import datetime, timedelta
from fastapi import FastAPI, HTTPException, Depends, BackgroundTasks
from fastapi.middleware.cors import CORSMiddleware
from contextlib import asynccontextmanager
import logging
from typing import List, Optional
from models import (
BatteryStatus, BatteryCommand, BatteryResponse, BatteryListResponse,
ChargingRequest, HistoricalDataRequest, HealthResponse
)
from database import connect_to_mongo, close_mongo_connection, get_database, connect_to_redis, get_redis
from battery_service import BatteryService
# Configure logging
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
@asynccontextmanager
async def lifespan(app: FastAPI):
"""Application lifespan manager"""
logger.info("Battery Service starting up...")
await connect_to_mongo()
await connect_to_redis()
# Start background tasks
asyncio.create_task(battery_monitoring_task())
logger.info("Battery Service startup complete")
yield
logger.info("Battery Service shutting down...")
await close_mongo_connection()
logger.info("Battery Service shutdown complete")
app = FastAPI(
title="Battery Management Service",
description="Energy storage monitoring and control microservice",
version="1.0.0",
lifespan=lifespan
)
app.add_middleware(
CORSMiddleware,
allow_origins=["*"],
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
# Dependencies
async def get_db():
return await get_database()
async def get_battery_service(db=Depends(get_db)):
redis = await get_redis()
return BatteryService(db, redis)
@app.get("/health", response_model=HealthResponse)
async def health_check():
"""Health check endpoint"""
try:
db = await get_database()
await db.command("ping")
redis = await get_redis()
await redis.ping()
return HealthResponse(
service="battery-service",
status="healthy",
timestamp=datetime.utcnow(),
version="1.0.0"
)
except Exception as e:
logger.error(f"Health check failed: {e}")
raise HTTPException(status_code=503, detail="Service Unavailable")
@app.get("/batteries", response_model=BatteryListResponse)
async def get_batteries(service: BatteryService = Depends(get_battery_service)):
"""Get all registered batteries"""
try:
batteries = await service.get_batteries()
return BatteryListResponse(
batteries=batteries,
count=len(batteries),
total_capacity=sum(b.get("capacity", 0) for b in batteries),
total_stored_energy=sum(b.get("stored_energy", 0) for b in batteries)
)
except Exception as e:
logger.error(f"Error getting batteries: {e}")
raise HTTPException(status_code=500, detail="Internal server error")
@app.get("/batteries/{battery_id}", response_model=BatteryResponse)
async def get_battery(battery_id: str, service: BatteryService = Depends(get_battery_service)):
"""Get specific battery status"""
try:
battery = await service.get_battery_status(battery_id)
if not battery:
raise HTTPException(status_code=404, detail="Battery not found")
return BatteryResponse(
battery_id=battery_id,
status=battery
)
except HTTPException:
raise
except Exception as e:
logger.error(f"Error getting battery {battery_id}: {e}")
raise HTTPException(status_code=500, detail="Internal server error")
@app.post("/batteries/{battery_id}/charge")
async def charge_battery(
battery_id: str,
request: ChargingRequest,
service: BatteryService = Depends(get_battery_service)
):
"""Charge a battery with specified power"""
try:
result = await service.charge_battery(battery_id, request.power_kw, request.duration_minutes)
if not result.get("success"):
raise HTTPException(status_code=400, detail=result.get("error", "Charging failed"))
return {
"message": "Charging initiated successfully",
"battery_id": battery_id,
"power_kw": request.power_kw,
"duration_minutes": request.duration_minutes,
"estimated_completion": result.get("estimated_completion")
}
except HTTPException:
raise
except Exception as e:
logger.error(f"Error charging battery {battery_id}: {e}")
raise HTTPException(status_code=500, detail="Internal server error")
@app.post("/batteries/{battery_id}/discharge")
async def discharge_battery(
battery_id: str,
request: ChargingRequest,
service: BatteryService = Depends(get_battery_service)
):
"""Discharge a battery with specified power"""
try:
result = await service.discharge_battery(battery_id, request.power_kw, request.duration_minutes)
if not result.get("success"):
raise HTTPException(status_code=400, detail=result.get("error", "Discharging failed"))
return {
"message": "Discharging initiated successfully",
"battery_id": battery_id,
"power_kw": request.power_kw,
"duration_minutes": request.duration_minutes,
"estimated_completion": result.get("estimated_completion")
}
except HTTPException:
raise
except Exception as e:
logger.error(f"Error discharging battery {battery_id}: {e}")
raise HTTPException(status_code=500, detail="Internal server error")
@app.get("/batteries/{battery_id}/history")
async def get_battery_history(
battery_id: str,
hours: int = 24,
service: BatteryService = Depends(get_battery_service)
):
"""Get battery historical data"""
try:
history = await service.get_battery_history(battery_id, hours)
return {
"battery_id": battery_id,
"period_hours": hours,
"history": history,
"data_points": len(history)
}
except Exception as e:
logger.error(f"Error getting battery history for {battery_id}: {e}")
raise HTTPException(status_code=500, detail="Internal server error")
@app.get("/batteries/analytics/summary")
async def get_battery_analytics(
hours: int = 24,
service: BatteryService = Depends(get_battery_service)
):
"""Get battery system analytics"""
try:
analytics = await service.get_battery_analytics(hours)
return {
"period_hours": hours,
"timestamp": datetime.utcnow().isoformat(),
**analytics
}
except Exception as e:
logger.error(f"Error getting battery analytics: {e}")
raise HTTPException(status_code=500, detail="Internal server error")
@app.post("/batteries/{battery_id}/optimize")
async def optimize_battery(
battery_id: str,
target_soc: float, # State of Charge target (0-100%)
service: BatteryService = Depends(get_battery_service)
):
"""Optimize battery charging/discharging to reach target SOC"""
try:
if not (0 <= target_soc <= 100):
raise HTTPException(status_code=400, detail="Target SOC must be between 0 and 100")
result = await service.optimize_battery(battery_id, target_soc)
return {
"battery_id": battery_id,
"target_soc": target_soc,
"optimization_plan": result,
"message": "Battery optimization initiated"
}
except HTTPException:
raise
except Exception as e:
logger.error(f"Error optimizing battery {battery_id}: {e}")
raise HTTPException(status_code=500, detail="Internal server error")
async def battery_monitoring_task():
"""Background task for continuous battery monitoring"""
logger.info("Starting battery monitoring task")
while True:
try:
db = await get_database()
redis = await get_redis()
service = BatteryService(db, redis)
# Update all battery statuses
batteries = await service.get_batteries()
for battery in batteries:
await service.update_battery_status(battery["battery_id"])
# Check for maintenance alerts
await service.check_maintenance_alerts()
# Sleep for monitoring interval (30 seconds)
await asyncio.sleep(30)
except Exception as e:
logger.error(f"Error in battery monitoring task: {e}")
await asyncio.sleep(60) # Wait longer on error
if __name__ == "__main__":
import uvicorn
uvicorn.run(app, host="0.0.0.0", port=8002)