From 071aa7a9ac56f24c220288cd4c87c19bb968bb18 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20Lov=C3=A9n?= Date: Thu, 20 Oct 2022 17:27:31 +0200 Subject: [PATCH] Add scene support --- README.md | 14 ------ custom_components/plejd/__init__.py | 8 +++- custom_components/plejd/button.py | 47 +++++++++++++++++++ custom_components/plejd/light.py | 1 - custom_components/plejd/pyplejd/__init__.py | 12 ++++- custom_components/plejd/pyplejd/api.py | 15 ++++++ custom_components/plejd/pyplejd/mesh.py | 7 +++ .../plejd/pyplejd/plejd_device.py | 22 ++++++++- 8 files changed, 107 insertions(+), 19 deletions(-) create mode 100644 custom_components/plejd/button.py diff --git a/README.md b/README.md index c28bed7..2ce2da5 100644 --- a/README.md +++ b/README.md @@ -13,20 +13,6 @@ I do not guarantee it will work or even that it will not harm your system. I don - Hopefully, your Plejd mesh will be auto discovered and you should see a message popping up in your integrations page. - Log in with the credentials you use in the Plejd app when prompted (email address and password) -## Current limitations - -- I only have a single DIM-01 device, so that's the only one I know for sure works. -- Only one channel per device is expected to work right now. E.g. only one lamp connected to a DIM-02 will show up in Home Assistant. - -## About connections - -Plejd devices doesn't seem to like to have multiple connections going. -Once a controller like the official Plejd app or Home Assistant connects, they will hide their precense from everyone else until a time after that connection is broken. - -That means that if you only have one Plejd device you may not be able to use Home Assistant and the app to controll the device at the same time, and either may have a hard time connecting while the other is running. - -Even after turning off the app or Home Assistant it may take 30 minutes to several hours before the othe rcan connect again. Turning off bluetooth on the last connected device or cutting power to the Plejd for a minute may help. - ## Getting more debug information diff --git a/custom_components/plejd/__init__.py b/custom_components/plejd/__init__.py index 54ba3f0..46da6bd 100644 --- a/custom_components/plejd/__init__.py +++ b/custom_components/plejd/__init__.py @@ -30,6 +30,7 @@ async def async_setup_entry(hass, config_entry): plejdManager = pyplejd.PlejdManager(config_entry.data) devices = await plejdManager.get_devices() + scenes = await plejdManager.get_scenes() # Add a service entry if there are no devices - just so the user can get diagnostics data if sum(d.type in ["light", "switch"] for d in devices.values()) == 0: @@ -52,6 +53,9 @@ async def async_setup_entry(hass, config_entry): hass.data[DOMAIN].setdefault("devices", {}).update({ config_entry.entry_id: devices }) + hass.data[DOMAIN].setdefault("scenes", {}).update({ + config_entry.entry_id: scenes + }) hass.data[DOMAIN].setdefault("manager", {}).update({ config_entry.entry_id: plejdManager, }) @@ -83,7 +87,9 @@ async def async_setup_entry(hass, config_entry): plejdManager.add_mesh_device(service_info.device) - await hass.config_entries.async_forward_entry_setups(config_entry, ["light", "switch"]) + await hass.config_entries.async_forward_entry_setups(config_entry, + ["light", "switch", "button"] + ) # Ping mesh intermittently to keep the connection alive async def _ping(now=None): diff --git a/custom_components/plejd/button.py b/custom_components/plejd/button.py new file mode 100644 index 0000000..44afbb3 --- /dev/null +++ b/custom_components/plejd/button.py @@ -0,0 +1,47 @@ +import logging + +from homeassistant.components.button import ButtonEntity + +_LOGGER = logging.getLogger(__name__) + +DOMAIN = "plejd" + +async def async_setup_entry(hass, config_entry, async_add_entities): + scenes = hass.data[DOMAIN]["scenes"].get(config_entry.entry_id, []) + + entities = [] + for s in scenes: + button = PlejdSceneButton(s, config_entry.entry_id) + entities.append(button) + async_add_entities(entities, False) + +class PlejdSceneButton(ButtonEntity): + + def __init__(self, device, entry_id): + super().__init__() + self.device = device + self.entry_id = entry_id + + @property + def device_info(self): + return { + "identifiers": {(DOMAIN, f"{self.entry_id}:{self.device.index}")}, + "name": self.device.name, + "manufacturer": "Plejd", + #"connections": ???, + } + + @property + def has_entity_name(self): + return True + + @property + def name(self): + return None + + @property + def unique_id(self): + return f"{self.entry_id}:{self.device.index}" + + async def async_press(self): + await self.device.activate() diff --git a/custom_components/plejd/light.py b/custom_components/plejd/light.py index 8ecf6f5..c234c65 100644 --- a/custom_components/plejd/light.py +++ b/custom_components/plejd/light.py @@ -1,4 +1,3 @@ -from builtins import property import logging from homeassistant.components.light import LightEntity, ColorMode from homeassistant.helpers.update_coordinator import CoordinatorEntity, DataUpdateCoordinator diff --git a/custom_components/plejd/pyplejd/__init__.py b/custom_components/plejd/pyplejd/__init__.py index c63c33e..1bf53a5 100644 --- a/custom_components/plejd/pyplejd/__init__.py +++ b/custom_components/plejd/pyplejd/__init__.py @@ -4,8 +4,8 @@ from datetime import timedelta from bleak_retry_connector import close_stale_connections from .mesh import PlejdMesh -from .api import get_cryptokey, get_devices, get_site_data -from .plejd_device import PlejdDevice +from .api import get_cryptokey, get_devices, get_site_data, get_scenes +from .plejd_device import PlejdDevice, PlejdScene from .const import PLEJD_SERVICE @@ -18,6 +18,7 @@ class PlejdManager: self.mesh = PlejdMesh() self.mesh.statecallback = self._update_device self.devices = { } + self.scenes = [] self.credentials = credentials def add_mesh_device(self, device): @@ -46,6 +47,13 @@ class PlejdManager: _LOGGER.info(self.devices) return self.devices + async def get_scenes(self): + scenes = await get_scenes(**self.credentials) + self.scenes = [PlejdScene(self, **s) for s in scenes] + _LOGGER.info("Scenes") + _LOGGER.info(self.scenes) + return self.scenes + async def _update_device(self, deviceState): address = deviceState["address"] if address in self.devices: diff --git a/custom_components/plejd/pyplejd/api.py b/custom_components/plejd/pyplejd/api.py index ef49689..f20c27c 100644 --- a/custom_components/plejd/pyplejd/api.py +++ b/custom_components/plejd/pyplejd/api.py @@ -103,3 +103,18 @@ async def get_devices(**credentials): } return retval + +async def get_scenes(**credentials): + site_data = await get_site_data(**credentials) + retval = [] + for scene in site_data["scenes"]: + if scene["hiddenFromSceneList"]: continue + sceneId = scene["sceneId"] + index = site_data["sceneIndex"].get(sceneId) + + retval.append({ + "index": index, + "title": scene["title"], + }) + + return retval diff --git a/custom_components/plejd/pyplejd/mesh.py b/custom_components/plejd/pyplejd/mesh.py index 7043a18..973df65 100644 --- a/custom_components/plejd/pyplejd/mesh.py +++ b/custom_components/plejd/pyplejd/mesh.py @@ -136,6 +136,13 @@ class PlejdMesh(): await self.poll() return retval + async def activate_scene(self, index): + payload = binascii.a2b_hex(f"0201100021{index:02x}") + retval = await self.write(payload) + if self.pollonWrite: + await self.poll() + return retval + async def ping(self): if self.client is None: return False diff --git a/custom_components/plejd/pyplejd/plejd_device.py b/custom_components/plejd/pyplejd/plejd_device.py index 55b54b8..5a17038 100644 --- a/custom_components/plejd/pyplejd/plejd_device.py +++ b/custom_components/plejd/pyplejd/plejd_device.py @@ -48,7 +48,6 @@ class PlejdDevice: def __repr__(self): return f", {self.address}, {self.BLE_address}, {self.data}>" - pass @property def available(self): @@ -111,3 +110,24 @@ class PlejdDevice: async def turn_off(self): await self.manager.mesh.set_state(self.address, False) + +class PlejdScene: + + def __init__(self, manager, index, title): + self._manager = manager + self._index = index + self._title = title + + def __repr__(self): + return f", {self._index}, '{self._title}'>" + + @property + def name(self): + return self._title + + @property + def index(self): + return self._index + + async def activate(self): + await self._manager.mesh.activate_scene(self._index)