""" Demeter Server — REST API Endpoints Provides JSON endpoints for querying device metadata and sensor readings from the in-memory store. Routes: GET /api/devices — list all devices + latest readings GET /api/devices/{device_id} — single device detail GET /api/devices/{device_id}/readings — sensor readings only GET /api/status — server status summary """ from __future__ import annotations import time from typing import Any from fastapi import APIRouter, HTTPException, Request router = APIRouter(prefix="/api", tags=["devices"]) def _get_store(request: Request): """Retrieve the DeviceStore from app state.""" return request.app.state.store def _get_observer(request: Request): """Retrieve the CoapObserverClient from app state.""" return request.app.state.observer @router.get("/devices") async def list_devices(request: Request) -> dict[str, Any]: """List all registered devices with their latest readings.""" store = _get_store(request) snapshot = await store.snapshot() return { "devices": list(snapshot.values()), "count": len(snapshot), "timestamp": time.time(), } @router.get("/devices/{device_id}") async def get_device(device_id: str, request: Request) -> dict[str, Any]: """Get a single device's full state.""" store = _get_store(request) device = await store.get_device(device_id) if device is None: raise HTTPException(status_code=404, detail=f"Device {device_id!r} not found") return device.to_dict() @router.get("/devices/{device_id}/readings") async def get_readings(device_id: str, request: Request) -> dict[str, Any]: """Get sensor readings for a specific device.""" store = _get_store(request) device = await store.get_device(device_id) if device is None: raise HTTPException(status_code=404, detail=f"Device {device_id!r} not found") return { "device_id": device_id, "online": device.online, "readings": { uri: reading.to_dict() for uri, reading in device.readings.items() }, } @router.get("/status") async def server_status(request: Request) -> dict[str, Any]: """Server status: subscription count, device overview.""" store = _get_store(request) observer = _get_observer(request) devices = await store.get_all_devices() online = sum(1 for d in devices if d.online) return { "server": "demeter", "version": request.app.state.settings.app_version, "devices_total": len(devices), "devices_online": online, "active_subscriptions": observer.active_subscriptions, "subscriptions": observer.subscription_status(), "timestamp": time.time(), }