"""Main FastAPI application for Dangerous Pi.""" import asyncio from contextlib import asynccontextmanager from fastapi import FastAPI from fastapi.middleware.cors import CORSMiddleware from fastapi.responses import JSONResponse from . import config from .models.database import init_db from .api import health, pm3, system, wifi, updates, plugins from .sse import events from .managers.update_manager import get_update_manager from .managers.ups_manager import get_ups_manager from .managers.ble_manager import get_ble_manager, NotificationType from .managers.plugin_manager import get_plugin_manager @asynccontextmanager async def lifespan(app: FastAPI): """Application lifespan manager.""" # Startup print(f"🚀 Starting Dangerous Pi backend...") await init_db() print(f"✅ Database initialized") # Start periodic update checks update_manager = get_update_manager() update_task = asyncio.create_task(update_manager.start_periodic_checks()) print(f"✅ Update manager started") # Initialize and start BLE manager ble_manager = get_ble_manager() await ble_manager.initialize() if ble_manager.is_available(): await ble_manager.start_advertising() print(f"✅ BLE manager started") else: print(f"⚠️ BLE not available") # Start UPS monitoring ups_manager = get_ups_manager() # Register UPS event callbacks for SSE notifications and BLE async def ups_event_handler(event): """Handle UPS events and broadcast via SSE and BLE.""" event_type = event.get("type") data = event.get("data", {}) if event_type == "battery_warning": await events.notify_ups_warning(data["percentage"], data["threshold"]) await ble_manager.send_notification( NotificationType.BATTERY_WARNING, f"Battery low: {data['percentage']:.1f}%", data ) elif event_type == "battery_low": await events.notify_ups_critical(data["percentage"]) await ble_manager.send_notification( NotificationType.BATTERY_CRITICAL, f"Battery critical: {data['percentage']:.1f}%", data ) elif event_type == "battery_critical": await events.notify_ups_shutdown(data.get("delay", 60), data["percentage"]) await ble_manager.send_notification( NotificationType.SHUTDOWN_INITIATED, f"Shutdown initiated: {data['percentage']:.1f}% battery", data ) ups_manager.register_event_callback(ups_event_handler) ups_task = asyncio.create_task(ups_manager.start_monitoring()) print(f"✅ UPS manager started") # Discover and load plugins plugin_manager = get_plugin_manager() discovered = await plugin_manager.discover_plugins() print(f"✅ Plugin manager started ({len(discovered)} plugins discovered)") yield # Shutdown print(f"🛑 Shutting down Dangerous Pi backend...") update_task.cancel() ups_task.cancel() try: await update_task except asyncio.CancelledError: pass try: await ups_task except asyncio.CancelledError: pass # Close UPS manager I2C connection ups_manager.close() app = FastAPI( title="Dangerous Pi API", description="Backend API for Proxmark3 management on Raspberry Pi Zero 2 W", version="0.1.0", lifespan=lifespan ) # CORS middleware for frontend app.add_middleware( CORSMiddleware, allow_origins=["*"], # TODO: Restrict in production allow_credentials=True, allow_methods=["*"], allow_headers=["*"], ) # Include routers app.include_router(health.router, prefix="/api", tags=["health"]) app.include_router(pm3.router, prefix="/api/pm3", tags=["proxmark3"]) app.include_router(system.router, prefix="/api/system", tags=["system"]) app.include_router(wifi.router, prefix="/api/wifi", tags=["wifi"]) app.include_router(updates.router, prefix="/api/updates", tags=["updates"]) app.include_router(plugins.router, prefix="/api/plugins", tags=["plugins"]) app.include_router(events.router, prefix="/sse", tags=["events"]) @app.exception_handler(Exception) async def global_exception_handler(request, exc): """Global exception handler.""" return JSONResponse( status_code=500, content={"error": str(exc)} ) if __name__ == "__main__": import uvicorn uvicorn.run( "app.backend.main:app", host=config.HOST, port=config.PORT, reload=True )