docs: add project description and server setup instructions to README

This commit is contained in:
2026-03-21 18:25:35 +00:00
parent c81815a83d
commit 6115a065c7
36 changed files with 4003 additions and 0 deletions

140
server/app/main.py Normal file
View 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()