feat: add RGB/color temp control and enhance error handling for groups

This commit is contained in:
Артём Кокос
2026-02-12 22:52:05 +07:00
parent 738edd4db9
commit 3c52fcf4ec
4 changed files with 91 additions and 38 deletions

4
.gitignore vendored Normal file
View File

@@ -0,0 +1,4 @@
.venv/
__pycache__/
*.db
.pytest_cache/

View File

@@ -6,17 +6,39 @@ import socket
class WizDriver:
PORT = 38899
# Стандартные ID сцен WiZ
SCENES = {
# Динамические (меняют цвет/яркость во времени)
"ocean": 1,
"romance": 2,
"party": 3,
"fireplace": 5,
"cozy": 6,
"forest": 10,
"pastel_colors": 11,
"wake_up": 12,
"bedtime": 13,
"warm_white": 33,
"daylight": 34,
"warm_white": 14,
"daylight": 15,
"cool_white": 16,
"night_light": 17,
"focus": 18,
"relax": 19,
"true_colors": 20,
"tv_time": 21,
"plant_growth": 22,
"spring": 23,
"summer": 24,
"fall": 25,
"deep_dive": 26,
"jungle": 27,
"mojito": 28,
"club": 29,
"christmas": 30,
"halloween": 31,
"candlelight": 32,
"golden_white": 33,
"pulse": 34,
"steampunk": 35,
}
async def send_udp(self, ip: str, payload: dict):

View File

@@ -22,7 +22,9 @@ class GroupModel(Base):
id: Mapped[str] = mapped_column(String, primary_key=True)
name: Mapped[str] = mapped_column(String)
device_ids: Mapped[list] = mapped_column(JSON) # Храним список MAC-адресов как JSON
device_ids: Mapped[List[str]] = mapped_column(
JSON
) # Храним список MAC-адресов как JSON
# --- Pydantic модели для API (оставляем для валидации) ---

93
main.py
View File

@@ -33,11 +33,9 @@ async def lifespan(app: FastAPI):
result = await session.execute(select(GroupModel))
groups = result.scalars().all()
for g in groups:
# Превращаем модель БД в формат стейта (если нужно)
state_manager.groups[g.id] = g
logger.info(f"📂 Загружена группа: {g.name} (MACs: {g.device_ids})")
# 3. Задача для периодического сканирования сети
async def periodic_discovery():
logger.info("🚀 Запущена фоновая служба Discovery")
@@ -74,30 +72,17 @@ async def get_all_devices():
return state_manager.devices
@app.post("/control/device/{device_id}")
async def control_device(device_id: str, state: bool, brightness: Optional[int] = None):
"""Прямое управление лампой по MAC."""
device = state_manager.devices.get(device_id)
if not device:
raise HTTPException(status_code=404, detail="Лампа не в сети")
params = {"state": state}
if brightness:
params["dimming"] = brightness
result = await wiz.set_pilot(device.ip, params)
return {"device_id": device_id, "result": result}
# --- Эндпоинты Групп ---
@app.get("/groups")
async def get_groups():
"""Список всех созданных люстр/групп."""
return state_manager.groups
@app.get("/scenes")
async def get_scenes():
return wiz.SCENES
@app.post("/groups")
async def create_group(data: GroupCreateSchema):
async with async_session() as session:
@@ -115,37 +100,77 @@ async def create_group(data: GroupCreateSchema):
return {"status": "created", "group": data.name}
@app.post("/control/group/{group_id}")
async def control_group(
group_id: str,
@app.post("/control/device/{device_id}")
async def control_device(
device_id: str,
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="Группа не найдена или лампы оффлайн"
)
device = state_manager.devices.get(device_id)
if not device:
raise HTTPException(status_code=404, detail="Лампа не в сети")
params = {}
if state is not None:
params["state"] = state
if brightness is not None:
params["dimming"] = brightness
if scene and scene in wiz.SCENES:
params["sceneId"] = wiz.SCENES[scene]
elif temp is not None:
params["temp"] = temp
elif r is not None or g is not None or b is not None:
params["r"], params["g"], params["b"] = r or 0, g or 0, b or 0
if not params:
raise HTTPException(status_code=400, detail="Никаких команд не передано")
# Параллельная отправка всем лампам группы
tasks = [wiz.set_pilot(ip, params) for ip in ips]
await asyncio.gather(*tasks)
result = await wiz.set_pilot(device.ip, params)
return {"device_id": device_id, "applied": params, "result": result}
return {"status": "ok", "group": group_id, "sent_to": ips}
@app.post("/control/group/{group_id}")
async def control_group(
group_id: str,
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 = {}
if state is not None:
params["state"] = state
if brightness is not None:
params["dimming"] = brightness
if scene and scene in wiz.SCENES:
params["sceneId"] = wiz.SCENES[scene]
elif temp is not None:
params["temp"] = temp
elif r is not None or g is not None or b is not None:
params["r"], params["g"], params["b"] = r or 0, g or 0, b or 0
if not params:
raise HTTPException(status_code=400, detail="Никаких команд не передано")
# Используем return_exceptions=True, чтобы ошибки одной лампы не ломали всё
tasks = [wiz.set_pilot(ip, params) for ip in ips]
await asyncio.gather(*tasks, return_exceptions=True)
return {"status": "ok", "applied": params, "sent_to": ips}
if __name__ == "__main__":