Files
demeter/server/app/coap_bridge.py

99 lines
2.8 KiB
Python

"""
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))