From df955dd5a35576f52e337a5407f6df6e0cdae3a0 Mon Sep 17 00:00:00 2001 From: Pedro Ribeiro Date: Mon, 23 Sep 2024 21:58:26 +0000 Subject: [PATCH] Add support for Zigbee Lights Fixes #59 --- homepilot/api.py | 30 +++++++ homepilot/const.py | 4 + homepilot/light.py | 192 +++++++++++++++++++++++++++++++++++++++++++ homepilot/manager.py | 11 ++- 4 files changed, 236 insertions(+), 1 deletion(-) create mode 100644 homepilot/light.py diff --git a/homepilot/api.py b/homepilot/api.py index 79440ce..50081ee 100644 --- a/homepilot/api.py +++ b/homepilot/api.py @@ -18,6 +18,9 @@ APICAP_STOP_SLAT_CMD, APICAP_VENTIL_POS_CFG, APICAP_VENTIL_POS_MODE_CFG, + APICAP_RGB_CFG, + APICAP_COLOR_TEMP_CFG, + APICAP_COLOR_MODE_CFG, ) @@ -385,6 +388,33 @@ async def async_set_temperature_thresh_cfg(self, did, thresh_number, temperature ) as response: return await response.json() + async def async_set_rgb(self, did, rgb_value): + await self.authenticate() + async with aiohttp.ClientSession(cookie_jar=self.cookie_jar) as session: + async with session.put( + f"http://{self._host}{self._base_path}/devices/{did}", + json={"name": APICAP_RGB_CFG, "value": rgb_value}, + ) as response: + return await response.json() + + async def async_set_color_temp(self, did, color_temp_value): + await self.authenticate() + async with aiohttp.ClientSession(cookie_jar=self.cookie_jar) as session: + async with session.put( + f"http://{self._host}{self._base_path}/devices/{did}", + json={"name": APICAP_COLOR_TEMP_CFG, "value": color_temp_value}, + ) as response: + return await response.json() + + async def async_set_color_temp(self, did, color_mode_value): + await self.authenticate() + async with aiohttp.ClientSession(cookie_jar=self.cookie_jar) as session: + async with session.put( + f"http://{self._host}{self._base_path}/devices/{did}", + json={"name": APICAP_COLOR_MODE_CFG, "value": color_mode_value}, + ) as response: + return await response.json() + async def async_turn_led_on(self): await self.authenticate() async with aiohttp.ClientSession(cookie_jar=self.cookie_jar) as session: diff --git a/homepilot/const.py b/homepilot/const.py index dc1d1f4..f1f0047 100644 --- a/homepilot/const.py +++ b/homepilot/const.py @@ -59,6 +59,10 @@ APICAP_TEMPERATURE_THRESH_2_CFG = "TEMPERATURE_THRESH_2_CFG" APICAP_TEMPERATURE_THRESH_3_CFG = "TEMPERATURE_THRESH_3_CFG" APICAP_TEMPERATURE_THRESH_4_CFG = "TEMPERATURE_THRESH_4_CFG" +# Light +APICAP_RGB_CFG = "RGB_CFG" +APICAP_COLOR_TEMP_CFG = "COLOR_TEMP_CFG" +APICAP_COLOR_MODE_CFG = "COLOR_MODE_CFG" SUPPORTED_DEVICES = { "35001164": {"name": "DuoFern Switch actuator", diff --git a/homepilot/light.py b/homepilot/light.py new file mode 100644 index 0000000..251b44b --- /dev/null +++ b/homepilot/light.py @@ -0,0 +1,192 @@ +import asyncio +from .const import ( + APICAP_DEVICE_TYPE_LOC, + APICAP_ID_DEVICE_LOC, + APICAP_NAME_DEVICE_LOC, + APICAP_PING_CMD, + APICAP_PROD_CODE_DEVICE_LOC, + APICAP_PROT_ID_DEVICE_LOC, + APICAP_VERSION_CFG, + APICAP_RGB_CFG, + APICAP_COLOR_TEMP_CFG, + APICAP_COLOR_MODE_CFG, + SUPPORTED_DEVICES, +) +from .api import HomePilotApi +from .device import HomePilotDevice + +class HomePilotLight(HomePilotDevice): + _is_on: bool + _brightness: int + _has_rgb: bool + _r_value: int + _g_value: int + _b_value: int + _has_color_temp: bool + _color_temp_value: int + _has_color_mode: int + _color_mode: str + + def __init__( + self, + api: HomePilotApi, + did: int, + uid: str, + name: str, + device_number: str, + model: str, + fw_version: str, + device_group: int, + has_ping_cmd: bool = False, + has_rgb: bool = False, + has_color_temp: bool = False, + has_color_mode: bool = False, + ) -> None: + super().__init__( + api=api, + did=did, + uid=uid, + name=name, + device_number=device_number, + model=model, + fw_version=fw_version, + device_group=device_group, + has_ping_cmd=has_ping_cmd, + ) + self._has_rgb = has_rgb + self._has_color_temp = has_color_temp + self._has_color_mode = has_color_mode + + + @staticmethod + def build_from_api(api: HomePilotApi, did: str): + return asyncio.run(HomePilotLight.async_build_from_api(api, did)) + + @staticmethod + async def async_build_from_api(api: HomePilotApi, did): + """Build a new HomePilotDevice from the response of API""" + device = await api.get_device(did) + device_map = HomePilotDevice.get_capabilities_map(device) + return HomePilotLight( + api=api, + did=device_map[APICAP_ID_DEVICE_LOC]["value"], + uid=device_map[APICAP_PROT_ID_DEVICE_LOC]["value"], + name=device_map[APICAP_NAME_DEVICE_LOC]["value"], + device_number=device_map[APICAP_PROD_CODE_DEVICE_LOC]["value"], + model=SUPPORTED_DEVICES[device_map[APICAP_PROD_CODE_DEVICE_LOC]["value"]][ + "name" + ] + if device_map[APICAP_PROD_CODE_DEVICE_LOC]["value"] in SUPPORTED_DEVICES + else "Generic Device", + fw_version=device_map[APICAP_VERSION_CFG]["value"] if APICAP_VERSION_CFG in device_map else "" + if APICAP_VERSION_CFG in device_map else "", + device_group=device_map[APICAP_DEVICE_TYPE_LOC]["value"], + has_ping_cmd=APICAP_PING_CMD in device_map, + has_rgb=APICAP_RGB_CFG in device_map, + has_color_temp=APICAP_COLOR_TEMP_CFG in device_map, + has_color_mode=APICAP_COLOR_MODE_CFG in device_map, + ) + + async def update_state(self, state, api): + await super().update_state(state, api) + self.is_on = state["statusesMap"]["Position"] != 0 + self.brightness = state["statusesMap"]["Position"] + if self.has_rgb: + self.r_value: int = int(["statusesMap"]["rgb"][2:4], 16) + self.g_value: int = int(["statusesMap"]["rgb"][4:6], 16) + self.b_value: int = int(["statusesMap"]["rgb"][6:8], 16) + self.color_temp_value = state["statusesMap"]["colortemperature"] if self.has_color_temp else 0 + self.color_mode_value = state["statusesMap"]["colormode"] if self.has_color_mode else 0 + + @property + def is_on(self) -> bool: + return self._is_on + + @is_on.setter + def is_on(self, is_on): + self._is_on = is_on + + @property + def brightness(self) -> int: + return self._brightness + + @brightness.setter + def brightness(self, brightness): + self._brightness = brightness + + @property + def has_rgb(self) -> int: + return self._has_rgb + + @property + def has_color_temp(self) -> int: + return self._has_color_temp + + @property + def has_color_mode(self) -> int: + return self._has_color_mode + + @property + def r_value(self) -> int: + return self._r_value + + @r_value.setter + def r_value(self, r_value): + self._r_value = r_value + + @property + def g_value(self) -> int: + return self._g_value + + @g_value.setter + def g_value(self, g_value): + self._g_value = g_value + + @property + def b_value(self) -> int: + return self._b_value + + @b_value.setter + def b_value(self, b_value): + self._b_value = b_value + + @property + def color_temp_value(self) -> int: + return self._color_temp_value + + @color_temp_value.setter + def color_temp_value(self, color_temp_value): + self._color_temp_value = color_temp_value + + @property + def color_mode_value(self) -> int: + return self._color_mode_value + + @color_mode_value.setter + def color_mode_value(self, color_mode_value): + self.color_mode_value = color_mode_value + + async def async_turn_on(self) -> None: + await self.api.async_turn_on(self.did) + + async def async_turn_off(self) -> None: + await self.api.async_turn_off(self.did) + + async def async_set_brightness(self, new_brightness) -> None: + await self.api.async_set_cover_position(self.did, new_brightness) + + async def async_set_rgb(self, r, g, b) -> None: + new_rgb: str = "0x%0.6X" % (r * (2,16) + g * pow(2,8) + b) + await self.api.async_set_rgb(self.did, new_rgb) + + async def async_set_color_temp(self, new_color_temp) -> None: + await self.api.async_set_color_temp(self.did, new_color_temp) + + async def async_set_color_mode(self, new_color_mode) -> None: + await self.api.async_set_color_mode(self.did, new_color_mode) + + async def async_toggle(self) -> None: + if self.is_on: + await self.async_turn_off() + else: + await self.async_turn_on() diff --git a/homepilot/manager.py b/homepilot/manager.py index 85873b1..d284e3b 100644 --- a/homepilot/manager.py +++ b/homepilot/manager.py @@ -11,6 +11,7 @@ from .api import HomePilotApi, AuthError from .wallcontroller import HomePilotWallController from .scenes import HomePilotScene +from .light import HomePilotLight from .device import HomePilotDevice @@ -35,7 +36,7 @@ async def async_build_manager(api: HomePilotApi): manager.devices = { id_type["did"]: await HomePilotManager.async_build_device(manager.api, id_type) for id_type in await manager.get_device_ids_types() - if id_type["type"] in ["-1", "1", "2", "3", "4", "5", "8", "10"] + if id_type["type"] in ["-1", "1", "2", "3", "4", "5", "8", "10", "73", "74", "75", "76"] } try: manager.scenes = { @@ -65,6 +66,14 @@ async def async_build_device(api, id_type): return await HomePilotCover.async_build_from_api(api, id_type["did"]) if id_type["type"] == "10": return await HomePilotWallController.async_build_from_api(api, id_type["did"]) + if id_type["type"] == "73": + return await HomePilotLight.async_build_from_api(api, id_type["did"]) + if id_type["type"] == "74": + return await HomePilotLight.async_build_from_api(api, id_type["did"]) + if id_type["type"] == "75": + return await HomePilotLight.async_build_from_api(api, id_type["did"]) + if id_type["type"] == "76": + return await HomePilotLight.async_build_from_api(api, id_type["did"]) return None async def get_hub_macaddress(self):