Switch control and schedules to JSON payloads

This commit is contained in:
Artem Kokos
2026-05-16 10:29:54 +07:00
parent 13fba2fa44
commit 15529961d6
8 changed files with 1171 additions and 748 deletions

View File

@@ -1,11 +1,19 @@
import asyncio
import json
import logging
from typing import Any, Optional
from typing import Any
from fastapi import APIRouter, Depends, HTTPException
from app.api.deps import verify_token, AuthContext
from app.api.schemas import (
BlinkResponse,
CommandRequest,
DeviceControlResponse,
DeviceStatusResponse,
GroupControlResponse,
GroupStatusResponse,
)
from app.core.database import async_session
from app.core.state import state_manager
from app.drivers.wiz import WizDriver, WizResponse
@@ -36,33 +44,6 @@ async def _log_event(
logger.error(f"Ошибка записи в лог: {e}")
def _build_command_params(
state: Optional[bool],
brightness: Optional[int],
scene: Optional[str],
temp: Optional[int],
r: Optional[int],
g: Optional[int],
b: Optional[int],
) -> dict[str, Any]:
params: dict[str, Any] = {}
if state is not None:
params["state"] = state
if brightness is not None:
params["dimming"] = brightness
if scene is not None:
if scene not in wiz.SCENES:
raise HTTPException(status_code=400, detail="Неизвестная сцена")
params["sceneId"] = wiz.SCENES[scene]
elif temp is not None:
params["temp"] = temp
elif any(v is not None for v in [r, g, b]):
params["r"], params["g"], params["b"] = r or 0, g or 0, b or 0
return params
def _resolve_action_name(params: dict[str, Any]) -> str:
if "state" in params:
return "toggle_on" if params["state"] else "toggle_off"
@@ -203,26 +184,21 @@ def _summarize_group_results(results: list[WizResponse]) -> tuple[int, int]:
return success_count, failure_count
@router.post("/device/{device_id}")
@router.post(
"/device/{device_id}",
response_model=DeviceControlResponse,
response_model_exclude_none=True,
)
async def control_device(
device_id: str,
payload: CommandRequest,
auth: AuthContext = Depends(verify_token),
state: Optional[bool] = None,
brightness: Optional[int] = None,
scene: Optional[str] = None,
temp: Optional[int] = None,
r: Optional[int] = None,
g: Optional[int] = None,
b: Optional[int] = None,
):
device = state_manager.devices.get(device_id)
if not device:
raise HTTPException(status_code=404, detail="Лампа не в сети")
params = _build_command_params(state, brightness, scene, temp, r, g, b)
if not params:
raise HTTPException(status_code=400, detail="Никаких команд не передано")
params = payload.to_wiz_params()
result = await wiz.set_pilot(device.ip, params)
if not result.ok:
await log_command_result(
@@ -257,26 +233,21 @@ async def control_device(
}
@router.post("/group/{group_id}")
@router.post(
"/group/{group_id}",
response_model=GroupControlResponse,
response_model_exclude_none=True,
)
async def control_group(
group_id: str,
payload: CommandRequest,
auth: AuthContext = Depends(verify_token),
state: Optional[bool] = None,
brightness: Optional[int] = None,
scene: Optional[str] = None,
temp: Optional[int] = None,
r: Optional[int] = None,
g: Optional[int] = None,
b: Optional[int] = None,
):
ips = state_manager.get_group_ips(group_id)
if not ips:
raise HTTPException(status_code=404, detail="Группа не найдена или оффлайн")
params = _build_command_params(state, brightness, scene, temp, r, g, b)
if not params:
raise HTTPException(status_code=400, detail="Никаких команд не передано")
params = payload.to_wiz_params()
tasks = [wiz.set_pilot(ip, params) for ip in ips]
results = await asyncio.gather(*tasks)
success_count, failure_count = _summarize_group_results(results)
@@ -313,7 +284,7 @@ async def control_group(
}
@router.post("/device/{device_id}/blink")
@router.post("/device/{device_id}/blink", response_model=BlinkResponse)
async def blink_device(device_id: str, _auth: AuthContext = Depends(verify_token)):
device = state_manager.devices.get(device_id)
if not device:
@@ -351,7 +322,11 @@ async def blink_device(device_id: str, _auth: AuthContext = Depends(verify_token
return {"status": "blink_done", "original": original_state}
@router.get("/device/{device_id}/status")
@router.get(
"/device/{device_id}/status",
response_model=DeviceStatusResponse,
response_model_exclude_none=True,
)
async def get_device_status(device_id: str, _auth: AuthContext = Depends(verify_token)):
"""Опрос реального состояния конкретной лампы."""
device = state_manager.devices.get(device_id)
@@ -368,7 +343,11 @@ async def get_device_status(device_id: str, _auth: AuthContext = Depends(verify_
return {"device_id": device_id, "status": status.result}
@router.get("/group/{group_id}/status")
@router.get(
"/group/{group_id}/status",
response_model=GroupStatusResponse,
response_model_exclude_none=True,
)
async def get_group_status(group_id: str, _auth: AuthContext = Depends(verify_token)):
"""Опрос состояния всей группы (возвращает список статусов)."""
ips = state_manager.get_group_ips(group_id)