More hue implementation

This commit is contained in:
Maximilian Giller 2025-01-20 01:10:30 +01:00
parent db5e826aea
commit c78546ffcf
9 changed files with 1422 additions and 29 deletions

View file

@ -5,7 +5,7 @@ import os
from statistics import median from statistics import median
from typing import Optional from typing import Optional
import requests as r import requests as r
from ...endpoints.hue import hue from ...endpoints.hue import hue_bridge
import logging import logging
file_path: str = "bettwaage.csv" file_path: str = "bettwaage.csv"
@ -126,9 +126,9 @@ def check_for_change():
# Make room sexy # Make room sexy
if sexy_mode_detection: if sexy_mode_detection:
if number_of_people >= 2 and weight_increased: if number_of_people >= 2 and weight_increased:
hue.in_room_activate_scene("Max Zimmer", "Sexy") hue_bridge.in_room_activate_scene("Max Zimmer", "Sexy")
elif number_of_people == 1 and not weight_increased: elif number_of_people == 1 and not weight_increased:
hue.in_room_activate_scene("Max Zimmer", "Tageslicht") hue_bridge.in_room_activate_scene("Max Zimmer", "Tageslicht")
def add_line_to_bed_history(line: str) -> None: def add_line_to_bed_history(line: str) -> None:

View file

@ -1,5 +1,6 @@
import json
import logging import logging
from core import Bridge from core import Bridge, Group
from phue import Bridge as phueBridge from phue import Bridge as phueBridge
from time import sleep from time import sleep
@ -9,15 +10,15 @@ class HueBridge(Bridge):
def __init__( def __init__(
self, self,
*, *,
ip_address: str,
id: str, id: str,
ip: str,
retry_limit: int = 10, retry_limit: int = 10,
retry_timeout_seconds: int = 5, retry_timeout_seconds: int = 5,
) -> None: ) -> None:
super().__init__(id=id, type="hue") super().__init__(id=id, type="hue")
self._retry_limit = retry_limit self._retry_limit = retry_limit
self._retry_timeout_seconds = retry_timeout_seconds self._retry_timeout_seconds = retry_timeout_seconds
self._hue: phueBridge = phueBridge(ip) self._hue: phueBridge = phueBridge(ip_address)
def disconnect(self) -> None: def disconnect(self) -> None:
self._hue = None self._hue = None
@ -47,6 +48,10 @@ class HueBridge(Bridge):
def list_api(self) -> dict: def list_api(self) -> dict:
return self._hue.get_api() return self._hue.get_api()
def get_all_lights(self) -> Group:
light_states = await self.list_api()
# TODO
def list_scenes(self) -> dict: def list_scenes(self) -> dict:
return self._hue.get_scene() return self._hue.get_scene()

View file

@ -1,4 +1,4 @@
from core import Entity from core import Entity, Color
from bridges.hue import HueBridge from bridges.hue import HueBridge
@ -8,6 +8,7 @@ class HueLight(Entity):
self, self,
*, *,
bridge: HueBridge, bridge: HueBridge,
initial_state: dict,
id: str, id: str,
name: str, name: str,
room: str, room: str,
@ -15,8 +16,24 @@ class HueLight(Entity):
) -> None: ) -> None:
super().__init__(id=id, name=name, room=room, groups=groups) super().__init__(id=id, name=name, room=room, groups=groups)
self._bridge: HueBridge = bridge self._bridge: HueBridge = bridge
self._color: Color = Color()
self._on: bool = False
self._transition_duration_sec = 0
def poll_state(self): self.__parse_state__(initial_state)
def __parse_state__(self, state: dict):
max_int_value = 255
self._on = state["state"]["on"]
h = state["state"]["hue"] / max_int_value
s = state["state"]["sat"] / max_int_value
v = state["state"]["bri"] / max_int_value
# TODO: Update color instead of overwriting it, to better keep track of change?
self._color = Color(hue=h, saturation=s, brightness=v)
def update(self):
# TODO # TODO
pass pass

View file

@ -1,5 +1,5 @@
class Color: class Color:
def __init__(self, *, hue: float, saturation: float, brightness: float): def __init__(self, *, hue: float, saturation: float, brightness: float):
self.hue = hue self.hue: float = hue
self.saturation = saturation self.saturation: float = saturation
self.brightness = brightness self.brightness: float = brightness

View file

@ -1,4 +1,4 @@
from core import Color from .color import Color
class EntityOpNotSupportedError(Exception): class EntityOpNotSupportedError(Exception):
@ -48,9 +48,9 @@ class Entity:
def __str__(self) -> str: def __str__(self) -> str:
return f"{self.name} [{self.id}, type {self.device_type}, room {self.room}, in {len(self.groups)} groups]" return f"{self.name} [{self.id}, type {self.device_type}, room {self.room}, in {len(self.groups)} groups]"
async def poll_state(self): async def update(self):
"""Implements an entity specific poll operation to get the latest state.""" """Implements an entity specific update operation to get the latest state."""
raise EntityOpNotSupportedError("poll_state") raise EntityOpNotSupportedError("update")
async def toggle_state(self): async def toggle_state(self):
"""Turns entity on, if off, and vice versa, if supported.""" """Turns entity on, if off, and vice versa, if supported."""

View file

@ -1,6 +1,43 @@
from core import Entity from .entity import Entity, EntityOpNotSupportedError
class Group(Entity): class Group(Entity):
def __init__(self, *, id: str, name: str):
def __init__(
self,
*,
entities: list[Entity] = ...,
id: str = "group",
name: str = "Empty Group"
):
super().__init__(id=id, name=name, room=None, device_type="group") super().__init__(id=id, name=name, room=None, device_type="group")
self._entities: list[Entity] = entities
# List of method names to dynamically create
methods_to_create = [
"set_brightness",
"set_hue",
"set_saturation",
"set_color",
"set_transition_duration",
"turn_on",
"turn_off",
]
for method_name in methods_to_create:
setattr(self, method_name, self._create_group_method(method_name))
async def __call_method__(self, method_name: str, *args, **kwargs):
for entity in self._entities:
try:
func = getattr(entity, method_name)
await func(*args, **kwargs)
except EntityOpNotSupportedError:
pass
def _create_group_method(self, method_name: str):
# Create a method that calls __call_method__ for the given method name
async def group_method(*args, **kwargs):
await self.__call_method__(method_name, *args, **kwargs)
return group_method

View file

@ -1,4 +1,4 @@
from core import Group from .group import Group
class Room(Group): class Room(Group):

View file

@ -5,22 +5,23 @@ from bridges.hue import HueBridge, HueLight
from fastapi import APIRouter from fastapi import APIRouter
from fastapi.responses import HTMLResponse from fastapi.responses import HTMLResponse
from core import Group
router = APIRouter(tags=["hue"]) router = APIRouter(tags=["hue"])
hue = HueBridge("192.168.178.85") hue_bridge = HueBridge(ip_address="192.168.178.85", id="hue-bridge")
lights: dict[int, HueLight] = {} hue_lights: Group = Group()
poll_delay_sec = 5 poll_delay_sec = 5
async def hue_service(): async def hue_service():
global lights global hue_lights
hue_lights = await hue_bridge.get_all_lights()
while True: while True:
try: try:
for light in lights: await hue_lights.update()
light.poll_state()
# TODO: Get all new lights
await asyncio.sleep(poll_delay_sec) await asyncio.sleep(poll_delay_sec)
except: except:
@ -29,7 +30,7 @@ async def hue_service():
@router.get("/scenes", tags=["scene"]) @router.get("/scenes", tags=["scene"])
async def get_scenes(): async def get_scenes():
return hue.list_scenes() return hue_bridge.list_scenes()
@router.post( @router.post(
@ -38,7 +39,7 @@ async def get_scenes():
) )
async def activate_scene(room_name: str, scene_name: str): async def activate_scene(room_name: str, scene_name: str):
try: try:
hue.in_room_activate_scene(room_name, scene_name) hue_bridge.in_room_activate_scene(room_name, scene_name)
except Exception as e: except Exception as e:
return HTMLResponse(status_code=400, content=str(e)) return HTMLResponse(status_code=400, content=str(e))
@ -49,7 +50,7 @@ async def activate_scene(room_name: str, scene_name: str):
) )
async def deactivate_room(room_name: str): async def deactivate_room(room_name: str):
try: try:
hue.in_room_deactivate_lights(room_name) hue_bridge.in_room_deactivate_lights(room_name)
except Exception as e: except Exception as e:
return HTMLResponse(status_code=400, content=str(e)) return HTMLResponse(status_code=400, content=str(e))
@ -60,6 +61,6 @@ async def deactivate_room(room_name: str):
) )
async def activate_room(room_name: str): async def activate_room(room_name: str):
try: try:
hue.in_room_activate_lights(room_name) hue_bridge.in_room_activate_lights(room_name)
except Exception as e: except Exception as e:
return HTMLResponse(status_code=400, content=str(e)) return HTMLResponse(status_code=400, content=str(e))

1333
src/hue_api.json Normal file

File diff suppressed because it is too large Load diff