Some checks failed
Lint & Test / test (push) Failing after 34s
New standalone WeatherSource entity with pluggable provider architecture (Open-Meteo v1, free, no API key). Full CRUD, test endpoint, browser geolocation, IconSelect provider picker, CardSection with test/clone/edit. WeatherColorStripStream maps WMO weather codes to ambient color palettes with temperature hue shifting and thunderstorm flash effects. Ref-counted WeatherManager polls API and caches data per source. CSS editor integration: weather type with EntitySelect source picker, speed and temperature influence sliders. Backup/restore support. i18n for en/ru/zh.
158 lines
5.5 KiB
Python
158 lines
5.5 KiB
Python
"""Weather source routes: CRUD + test endpoint."""
|
|
|
|
from fastapi import APIRouter, Depends, HTTPException
|
|
|
|
from wled_controller.api.auth import AuthRequired
|
|
from wled_controller.api.dependencies import (
|
|
fire_entity_event,
|
|
get_weather_manager,
|
|
get_weather_source_store,
|
|
)
|
|
from wled_controller.api.schemas.weather_sources import (
|
|
WeatherSourceCreate,
|
|
WeatherSourceListResponse,
|
|
WeatherSourceResponse,
|
|
WeatherSourceUpdate,
|
|
WeatherTestResponse,
|
|
)
|
|
from wled_controller.core.weather.weather_manager import WeatherManager
|
|
from wled_controller.core.weather.weather_provider import WMO_CONDITION_NAMES
|
|
from wled_controller.storage.base_store import EntityNotFoundError
|
|
from wled_controller.storage.weather_source import WeatherSource
|
|
from wled_controller.storage.weather_source_store import WeatherSourceStore
|
|
from wled_controller.utils import get_logger
|
|
|
|
logger = get_logger(__name__)
|
|
|
|
router = APIRouter()
|
|
|
|
|
|
def _to_response(source: WeatherSource) -> WeatherSourceResponse:
|
|
d = source.to_dict()
|
|
return WeatherSourceResponse(
|
|
id=d["id"],
|
|
name=d["name"],
|
|
provider=d["provider"],
|
|
provider_config=d.get("provider_config", {}),
|
|
latitude=d["latitude"],
|
|
longitude=d["longitude"],
|
|
update_interval=d["update_interval"],
|
|
description=d.get("description"),
|
|
tags=d.get("tags", []),
|
|
created_at=source.created_at,
|
|
updated_at=source.updated_at,
|
|
)
|
|
|
|
|
|
@router.get("/api/v1/weather-sources", response_model=WeatherSourceListResponse, tags=["Weather Sources"])
|
|
async def list_weather_sources(
|
|
_auth: AuthRequired,
|
|
store: WeatherSourceStore = Depends(get_weather_source_store),
|
|
):
|
|
sources = store.get_all_sources()
|
|
return WeatherSourceListResponse(
|
|
sources=[_to_response(s) for s in sources],
|
|
count=len(sources),
|
|
)
|
|
|
|
|
|
@router.post("/api/v1/weather-sources", response_model=WeatherSourceResponse, status_code=201, tags=["Weather Sources"])
|
|
async def create_weather_source(
|
|
data: WeatherSourceCreate,
|
|
_auth: AuthRequired,
|
|
store: WeatherSourceStore = Depends(get_weather_source_store),
|
|
):
|
|
try:
|
|
source = store.create_source(
|
|
name=data.name,
|
|
provider=data.provider,
|
|
provider_config=data.provider_config,
|
|
latitude=data.latitude,
|
|
longitude=data.longitude,
|
|
update_interval=data.update_interval,
|
|
description=data.description,
|
|
tags=data.tags,
|
|
)
|
|
except ValueError as e:
|
|
raise HTTPException(status_code=400, detail=str(e))
|
|
fire_entity_event("weather_source", "created", source.id)
|
|
return _to_response(source)
|
|
|
|
|
|
@router.get("/api/v1/weather-sources/{source_id}", response_model=WeatherSourceResponse, tags=["Weather Sources"])
|
|
async def get_weather_source(
|
|
source_id: str,
|
|
_auth: AuthRequired,
|
|
store: WeatherSourceStore = Depends(get_weather_source_store),
|
|
):
|
|
try:
|
|
return _to_response(store.get_source(source_id))
|
|
except EntityNotFoundError:
|
|
raise HTTPException(status_code=404, detail=f"Weather source {source_id} not found")
|
|
|
|
|
|
@router.put("/api/v1/weather-sources/{source_id}", response_model=WeatherSourceResponse, tags=["Weather Sources"])
|
|
async def update_weather_source(
|
|
source_id: str,
|
|
data: WeatherSourceUpdate,
|
|
_auth: AuthRequired,
|
|
store: WeatherSourceStore = Depends(get_weather_source_store),
|
|
manager: WeatherManager = Depends(get_weather_manager),
|
|
):
|
|
try:
|
|
source = store.update_source(
|
|
source_id,
|
|
name=data.name,
|
|
provider=data.provider,
|
|
provider_config=data.provider_config,
|
|
latitude=data.latitude,
|
|
longitude=data.longitude,
|
|
update_interval=data.update_interval,
|
|
description=data.description,
|
|
tags=data.tags,
|
|
)
|
|
except EntityNotFoundError:
|
|
raise HTTPException(status_code=404, detail=f"Weather source {source_id} not found")
|
|
except ValueError as e:
|
|
raise HTTPException(status_code=400, detail=str(e))
|
|
manager.update_source(source_id)
|
|
fire_entity_event("weather_source", "updated", source.id)
|
|
return _to_response(source)
|
|
|
|
|
|
@router.delete("/api/v1/weather-sources/{source_id}", status_code=204, tags=["Weather Sources"])
|
|
async def delete_weather_source(
|
|
source_id: str,
|
|
_auth: AuthRequired,
|
|
store: WeatherSourceStore = Depends(get_weather_source_store),
|
|
):
|
|
try:
|
|
store.delete_source(source_id)
|
|
except EntityNotFoundError:
|
|
raise HTTPException(status_code=404, detail=f"Weather source {source_id} not found")
|
|
fire_entity_event("weather_source", "deleted", source_id)
|
|
|
|
|
|
@router.post("/api/v1/weather-sources/{source_id}/test", response_model=WeatherTestResponse, tags=["Weather Sources"])
|
|
async def test_weather_source(
|
|
source_id: str,
|
|
_auth: AuthRequired,
|
|
store: WeatherSourceStore = Depends(get_weather_source_store),
|
|
manager: WeatherManager = Depends(get_weather_manager),
|
|
):
|
|
"""Force-fetch current weather and return the result."""
|
|
try:
|
|
store.get_source(source_id) # validate exists
|
|
except EntityNotFoundError:
|
|
raise HTTPException(status_code=404, detail=f"Weather source {source_id} not found")
|
|
|
|
data = manager.fetch_now(source_id)
|
|
condition = WMO_CONDITION_NAMES.get(data.code, f"Unknown ({data.code})")
|
|
return WeatherTestResponse(
|
|
code=data.code,
|
|
condition=condition,
|
|
temperature=data.temperature,
|
|
wind_speed=data.wind_speed,
|
|
cloud_cover=data.cloud_cover,
|
|
)
|