feat: add RGB/color temp control and enhance error handling for groups
This commit is contained in:
4
.gitignore
vendored
Normal file
4
.gitignore
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
.venv/
|
||||
__pycache__/
|
||||
*.db
|
||||
.pytest_cache/
|
||||
@@ -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):
|
||||
|
||||
@@ -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
93
main.py
@@ -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__":
|
||||
|
||||
Reference in New Issue
Block a user