docs: add project description and server setup instructions to README
This commit is contained in:
140
server/app/main.py
Normal file
140
server/app/main.py
Normal file
@@ -0,0 +1,140 @@
|
||||
"""
|
||||
Demeter Server — FastAPI Application
|
||||
|
||||
Entry point for the Demeter IoT management server. Manages the
|
||||
lifecycle of the aiocoap observer client and FastAPI HTTP server
|
||||
on a shared asyncio event loop.
|
||||
|
||||
Run with:
|
||||
cd server
|
||||
uvicorn app.main:app --reload --port 8000
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
import logging
|
||||
from contextlib import asynccontextmanager
|
||||
|
||||
from fastapi import FastAPI
|
||||
from fastapi.responses import Response
|
||||
|
||||
from .coap_bridge import router as coap_bridge_router
|
||||
from .coap_observer import CoapObserverClient
|
||||
from .config import Settings, load_devices_config
|
||||
from .dashboard import router as dashboard_router
|
||||
from .device_store import DeviceStore
|
||||
from .devices_api import router as devices_api_router
|
||||
from .logging_config import setup_logging
|
||||
from .metrics import MetricsCollector
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@asynccontextmanager
|
||||
async def lifespan(app: FastAPI):
|
||||
"""
|
||||
Manage startup and shutdown of the CoAP observer client.
|
||||
|
||||
Startup:
|
||||
1. Configure logging (console + optional Loki)
|
||||
2. Load device registry from YAML
|
||||
3. Initialize the in-memory store
|
||||
4. Create the aiocoap client context
|
||||
5. Subscribe to all enabled device resources
|
||||
|
||||
Shutdown:
|
||||
1. Cancel all CoAP subscriptions
|
||||
2. Close the aiocoap context
|
||||
"""
|
||||
settings: Settings = app.state.settings
|
||||
metrics: MetricsCollector = app.state.metrics
|
||||
|
||||
# ── Logging ──
|
||||
setup_logging(loki_url=settings.loki_url, debug=settings.debug)
|
||||
|
||||
logger.info("=" * 50)
|
||||
logger.info(" Demeter IoT Server v%s", settings.app_version)
|
||||
logger.info("=" * 50)
|
||||
|
||||
# ── Device Config ──
|
||||
devices_config = load_devices_config(settings.devices_config_path)
|
||||
enabled = [d for d in devices_config.devices if d.enabled]
|
||||
logger.info("Loaded %d devices (%d enabled)", len(devices_config.devices), len(enabled))
|
||||
|
||||
# ── Store ──
|
||||
store = DeviceStore(devices_config)
|
||||
app.state.store = store
|
||||
|
||||
# ── Metrics ──
|
||||
metrics.set_server_info(settings.app_version)
|
||||
|
||||
# ── CoAP Observer ──
|
||||
observer = CoapObserverClient(
|
||||
store=store,
|
||||
metrics=metrics,
|
||||
request_timeout=settings.coap_request_timeout,
|
||||
reconnect_base=settings.coap_reconnect_base,
|
||||
reconnect_max=settings.coap_reconnect_max,
|
||||
)
|
||||
app.state.observer = observer
|
||||
|
||||
await observer.startup()
|
||||
|
||||
# Subscribe to all enabled device resources
|
||||
for device in enabled:
|
||||
for resource in device.resources:
|
||||
await observer.subscribe(
|
||||
device_id=device.id,
|
||||
ip=device.ip,
|
||||
port=device.port,
|
||||
resource_uri=resource.uri,
|
||||
)
|
||||
|
||||
total_subs = sum(len(d.resources) for d in enabled)
|
||||
logger.info("Subscribed to %d resources across %d devices", total_subs, len(enabled))
|
||||
metrics.set_active_subscriptions(total_subs)
|
||||
|
||||
logger.info("Server ready — dashboard at http://%s:%d/dashboard", settings.host, settings.port)
|
||||
|
||||
yield
|
||||
|
||||
# ── Shutdown ──
|
||||
logger.info("Shutting down...")
|
||||
await observer.shutdown()
|
||||
logger.info("Shutdown complete")
|
||||
|
||||
|
||||
def create_app() -> FastAPI:
|
||||
"""Create and configure the FastAPI application."""
|
||||
settings = Settings()
|
||||
metrics = MetricsCollector()
|
||||
|
||||
app = FastAPI(
|
||||
title=settings.app_title,
|
||||
version=settings.app_version,
|
||||
lifespan=lifespan,
|
||||
)
|
||||
|
||||
# Store settings and metrics on app state (available before lifespan)
|
||||
app.state.settings = settings
|
||||
app.state.metrics = metrics
|
||||
|
||||
# ── Routers ──
|
||||
app.include_router(devices_api_router)
|
||||
app.include_router(coap_bridge_router)
|
||||
app.include_router(dashboard_router)
|
||||
|
||||
# ── Prometheus /metrics ──
|
||||
@app.get("/metrics", tags=["monitoring"], include_in_schema=False)
|
||||
async def prometheus_metrics():
|
||||
return Response(
|
||||
content=metrics.generate(),
|
||||
media_type="text/plain; version=0.0.4; charset=utf-8",
|
||||
)
|
||||
|
||||
return app
|
||||
|
||||
|
||||
# Module-level app instance for uvicorn
|
||||
app = create_app()
|
||||
Reference in New Issue
Block a user