""" Demeter Server — CoAP-to-REST Bridge Proxies HTTP requests to CoAP endpoints on ESP devices, allowing the dashboard and external tools to query devices via standard HTTP. Routes: GET /api/devices/{device_id}/coap/{resource_path} — proxy CoAP GET PUT /api/devices/{device_id}/coap/{resource_path} — proxy CoAP PUT """ from __future__ import annotations from typing import Any from fastapi import APIRouter, HTTPException, Request router = APIRouter(prefix="/api", tags=["coap-bridge"]) def _get_store(request: Request): return request.app.state.store def _get_observer(request: Request): return request.app.state.observer @router.get("/devices/{device_id}/coap/{resource_path:path}") async def coap_proxy_get( device_id: str, resource_path: str, request: Request, ) -> dict[str, Any]: """ Proxy a CoAP GET request to an ESP device. Example: GET /api/devices/esp32-plant-01/coap/sensors/temperature → CoAP GET coap://192.168.1.100:5683/sensors/temperature """ store = _get_store(request) observer = _get_observer(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") try: result = await observer.coap_get( device.config.ip, device.config.port, resource_path ) return { "device_id": device_id, "resource": resource_path, "method": "GET", "response": result, } except ConnectionError as e: raise HTTPException(status_code=504, detail=str(e)) except ValueError as e: raise HTTPException(status_code=502, detail=str(e)) @router.put("/devices/{device_id}/coap/{resource_path:path}") async def coap_proxy_put( device_id: str, resource_path: str, body: dict[str, Any], request: Request, ) -> dict[str, Any]: """ Proxy a CoAP PUT request to an ESP device. Example: PUT /api/devices/esp32-plant-01/coap/config/interval Body: {"interval": 10} → CoAP PUT coap://192.168.1.100:5683/config/interval """ store = _get_store(request) observer = _get_observer(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") try: result = await observer.coap_put( device.config.ip, device.config.port, resource_path, body ) return { "device_id": device_id, "resource": resource_path, "method": "PUT", "response": result, } except ConnectionError as e: raise HTTPException(status_code=504, detail=str(e)) except ValueError as e: raise HTTPException(status_code=502, detail=str(e))