From 57026ba8bf0981773a1b4cecb7427526b669b6da Mon Sep 17 00:00:00 2001 From: Diogo Gomes Date: Sun, 9 Oct 2022 21:29:26 +0100 Subject: [PATCH 01/58] don't make it an error --- custom_components/openhasp/config_flow.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/custom_components/openhasp/config_flow.py b/custom_components/openhasp/config_flow.py index 1b0d4e2..b5d2587 100644 --- a/custom_components/openhasp/config_flow.py +++ b/custom_components/openhasp/config_flow.py @@ -72,7 +72,7 @@ def __init__(self): async def async_step_user(self, user_input=None): """Handle a flow initialized by User.""" - _LOGGER.error("Discovery Only") + _LOGGER.info("Discovery Only") await self.hass.components.mqtt.async_publish( self.hass, From cae7a6b6a464113ce0e15ee8d1b02193fd80a7bf Mon Sep 17 00:00:00 2001 From: fvanroie <15969459+fvanroie@users.noreply.github.com> Date: Fri, 27 Jan 2023 20:17:59 +0100 Subject: [PATCH 02/58] Update const.py --- custom_components/openhasp/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/custom_components/openhasp/const.py b/custom_components/openhasp/const.py index 3d5b930..b433baa 100644 --- a/custom_components/openhasp/const.py +++ b/custom_components/openhasp/const.py @@ -2,7 +2,7 @@ # Version MAJOR = "0" -MINOR = "6" +MINOR = "7" DOMAIN = "openhasp" From dc2d98b55432ba0b92f8200ec03130397eb29822 Mon Sep 17 00:00:00 2001 From: Diogo Gomes Date: Sun, 29 Jan 2023 17:34:17 +0000 Subject: [PATCH 03/58] add optional image resize to fit dimensions --- custom_components/openhasp/__init__.py | 6 ++++-- custom_components/openhasp/const.py | 1 + custom_components/openhasp/image.py | 9 +++++---- 3 files changed, 10 insertions(+), 6 deletions(-) diff --git a/custom_components/openhasp/__init__.py b/custom_components/openhasp/__init__.py index ff2ec14..2f39aad 100644 --- a/custom_components/openhasp/__init__.py +++ b/custom_components/openhasp/__init__.py @@ -29,6 +29,7 @@ from .common import HASP_IDLE_SCHEMA from .const import ( ATTR_CONFIG_SUBMODULE, + ATTR_FORCE_FITSCREEN, ATTR_HEIGHT, ATTR_IDLE, ATTR_IMAGE, @@ -153,6 +154,7 @@ def hasp_object(value): vol.Required(ATTR_OBJECT): hasp_object, vol.Optional(ATTR_WIDTH): cv.positive_int, vol.Optional(ATTR_HEIGHT): cv.positive_int, + vol.Optional(ATTR_FORCE_FITSCREEN): cv.boolean, }, extra=vol.ALLOW_EXTRA, ) @@ -599,13 +601,13 @@ async def async_config_service(self, submodule, parameters): retain=False, ) - async def async_push_image(self, image, obj, width=None, height=None): + async def async_push_image(self, image, obj, width=None, height=None, fitscreen=False): """Update object image.""" image_id = hashlib.md5(image.encode("utf-8")).hexdigest() rgb_image = await self.hass.async_add_executor_job( - image_to_rgb565, image, (width, height) + image_to_rgb565, image, (width, height), fitscreen ) self.hass.data[DOMAIN][DATA_IMAGES][image_id] = rgb_image diff --git a/custom_components/openhasp/const.py b/custom_components/openhasp/const.py index b433baa..096e081 100644 --- a/custom_components/openhasp/const.py +++ b/custom_components/openhasp/const.py @@ -80,6 +80,7 @@ HASP_OFFLINE = "offline" HASP_LWT = (HASP_ONLINE, HASP_OFFLINE) +ATTR_FORCE_FITSCREEN = "fit screen" ATTR_PAGE = "page" ATTR_CURRENT_DIM = "dim" ATTR_IDLE = "idle" diff --git a/custom_components/openhasp/image.py b/custom_components/openhasp/image.py index 96ba7fe..367d550 100644 --- a/custom_components/openhasp/image.py +++ b/custom_components/openhasp/image.py @@ -15,7 +15,7 @@ _LOGGER = logging.getLogger(__name__) -def image_to_rgb565(in_image, size): +def image_to_rgb565(in_image, size, fitscreen): """Transform image to rgb565 format according to LVGL requirements.""" try: if in_image.startswith("http"): @@ -29,8 +29,9 @@ def image_to_rgb565(in_image, size): original_width, original_height = im.size width, height = size - width = min(w for w in [width, original_width] if w is not None and w > 0) - height = min(h for h in [height, original_height] if h is not None and h > 0) + if not fitscreen: + width = min(w for w in [width, original_width] if w is not None and w > 0) + height = min(h for h in [height, original_height] if h is not None and h > 0) im.thumbnail((height, width), Image.ANTIALIAS) width, height = im.size # actual size after resize @@ -47,7 +48,7 @@ def image_to_rgb565(in_image, size): b = (pix[2] >> 3) & 0x1F out_image.write(struct.pack("H", (r << 11) | (g << 5) | b)) - _LOGGER.debug("image_to_rgb565 out_image: %s", out_image.name) + _LOGGER.debug("image_to_rgb565 out_image: %s - %s > %s", out_image.name, (original_width, original_height), im.size) out_image.flush() From 8d7acda25aa5f9079f7eea1a74cc28bdc512c273 Mon Sep 17 00:00:00 2001 From: Diogo Gomes Date: Sun, 29 Jan 2023 17:35:19 +0000 Subject: [PATCH 04/58] add optional image resize to fit dimensions --- custom_components/openhasp/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/custom_components/openhasp/const.py b/custom_components/openhasp/const.py index 096e081..79cebe6 100644 --- a/custom_components/openhasp/const.py +++ b/custom_components/openhasp/const.py @@ -80,7 +80,7 @@ HASP_OFFLINE = "offline" HASP_LWT = (HASP_ONLINE, HASP_OFFLINE) -ATTR_FORCE_FITSCREEN = "fit screen" +ATTR_FORCE_FITSCREEN = "fit_screen" ATTR_PAGE = "page" ATTR_CURRENT_DIM = "dim" ATTR_IDLE = "idle" From 71e4f6af0103ca3af65a9a7cc0d0604300aaecb8 Mon Sep 17 00:00:00 2001 From: Diogo Gomes Date: Sun, 29 Jan 2023 20:41:31 +0000 Subject: [PATCH 05/58] resize don't thumbnail if fit screen --- custom_components/openhasp/image.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/custom_components/openhasp/image.py b/custom_components/openhasp/image.py index 367d550..0365ce7 100644 --- a/custom_components/openhasp/image.py +++ b/custom_components/openhasp/image.py @@ -32,8 +32,9 @@ def image_to_rgb565(in_image, size, fitscreen): if not fitscreen: width = min(w for w in [width, original_width] if w is not None and w > 0) height = min(h for h in [height, original_height] if h is not None and h > 0) - - im.thumbnail((height, width), Image.ANTIALIAS) + im.thumbnail((height, width), Image.ANTIALIAS) + else: + im.resize((height, width), Image.ANTIALIAS) width, height = im.size # actual size after resize out_image = tempfile.NamedTemporaryFile(mode="w+b") From b2ed1868549b682588c5fd633c3e8c9b020bcf2e Mon Sep 17 00:00:00 2001 From: Diogo Gomes Date: Mon, 30 Jan 2023 00:32:13 +0000 Subject: [PATCH 06/58] resize is not inplace --- custom_components/openhasp/image.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/custom_components/openhasp/image.py b/custom_components/openhasp/image.py index 0365ce7..1418119 100644 --- a/custom_components/openhasp/image.py +++ b/custom_components/openhasp/image.py @@ -34,7 +34,7 @@ def image_to_rgb565(in_image, size, fitscreen): height = min(h for h in [height, original_height] if h is not None and h > 0) im.thumbnail((height, width), Image.ANTIALIAS) else: - im.resize((height, width), Image.ANTIALIAS) + im = im.resize((height, width), Image.ANTIALIAS) width, height = im.size # actual size after resize out_image = tempfile.NamedTemporaryFile(mode="w+b") From 142724443523b5826fbf44907f341c116b70b285 Mon Sep 17 00:00:00 2001 From: Diogo Gomes Date: Mon, 30 Jan 2023 11:15:12 +0000 Subject: [PATCH 07/58] document fitscreen --- custom_components/openhasp/services.yaml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/custom_components/openhasp/services.yaml b/custom_components/openhasp/services.yaml index b1fb8e9..3a44388 100644 --- a/custom_components/openhasp/services.yaml +++ b/custom_components/openhasp/services.yaml @@ -133,3 +133,10 @@ push_image: min: 0 max: 1024 mode: box + fitscreen: + name: Fit Screen + description: If this is set to true, then image is resized to the previously defined width and height, regardless of screen dimensions and/or aspect ratio + required: false + example: false + selector: + boolean: From 932e5ddc125d2e759d2b494ab66e82f060391dae Mon Sep 17 00:00:00 2001 From: fvanroie <15969459+fvanroie@users.noreply.github.com> Date: Sat, 5 Aug 2023 20:03:55 +0200 Subject: [PATCH 08/58] Update image.py ANTIALIAS was removed in Pillow 10.0.0 (after being deprecated through many previous versions). Now you need to use PIL.Image.LANCZOS or PIL.Image.Resampling.LANCZOS. (This is the exact same algorithm that ANTIALIAS referred to, you just can no longer access it through the name ANTIALIAS.) --- custom_components/openhasp/image.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/custom_components/openhasp/image.py b/custom_components/openhasp/image.py index 1418119..c39095b 100644 --- a/custom_components/openhasp/image.py +++ b/custom_components/openhasp/image.py @@ -32,9 +32,9 @@ def image_to_rgb565(in_image, size, fitscreen): if not fitscreen: width = min(w for w in [width, original_width] if w is not None and w > 0) height = min(h for h in [height, original_height] if h is not None and h > 0) - im.thumbnail((height, width), Image.ANTIALIAS) + im.thumbnail((height, width), Image.LANCZOS) else: - im = im.resize((height, width), Image.ANTIALIAS) + im = im.resize((height, width), Image.LANCZOS) width, height = im.size # actual size after resize out_image = tempfile.NamedTemporaryFile(mode="w+b") From 4ccb06fe475cb2a26591f43b02651c78f5844681 Mon Sep 17 00:00:00 2001 From: fvanroie <15969459+fvanroie@users.noreply.github.com> Date: Sat, 5 Aug 2023 22:38:08 +0200 Subject: [PATCH 09/58] Update manifest.json Manifest keys should be sorted: domain, name, then alphabetical order --- custom_components/openhasp/manifest.json | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/custom_components/openhasp/manifest.json b/custom_components/openhasp/manifest.json index b85e9b4..6c8c8d3 100644 --- a/custom_components/openhasp/manifest.json +++ b/custom_components/openhasp/manifest.json @@ -1,13 +1,13 @@ { "domain": "openhasp", "name": "openHASP", + "codeowners": ["@dgomes"], + "config_flow": true, + "dependencies": ["mqtt", "http"], "documentation": "https://www.openhasp.com/", + "iot_class": "local_push", "issue_tracker": "https://github.com/HASwitchPlate/openHASP-custom-component/issues", - "dependencies": ["mqtt", "http"], + "mqtt": ["hasp/discovery/#"], "requirements": ["jsonschema>=3.2.0"], - "version": "0.7.0", - "config_flow": true, - "iot_class": "local_push", - "codeowners": ["@dgomes"], - "mqtt": ["hasp/discovery/#"] + "version": "0.7.0" } From 6ed4ea410fdf712a129fccdbe0596e1c28d801c0 Mon Sep 17 00:00:00 2001 From: fvanroie <15969459+fvanroie@users.noreply.github.com> Date: Sat, 5 Aug 2023 22:38:28 +0200 Subject: [PATCH 10/58] Update manifest.json --- custom_components/openhasp/manifest.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/custom_components/openhasp/manifest.json b/custom_components/openhasp/manifest.json index 6c8c8d3..bb1f9f0 100644 --- a/custom_components/openhasp/manifest.json +++ b/custom_components/openhasp/manifest.json @@ -9,5 +9,5 @@ "issue_tracker": "https://github.com/HASwitchPlate/openHASP-custom-component/issues", "mqtt": ["hasp/discovery/#"], "requirements": ["jsonschema>=3.2.0"], - "version": "0.7.0" + "version": "0.7.1" } From 3b8be7fc722f7447482059363eb77e1054f1e8d9 Mon Sep 17 00:00:00 2001 From: Diogo Gomes Date: Mon, 19 Feb 2024 01:01:35 +0000 Subject: [PATCH 11/58] fix mqtt discovery --- custom_components/openhasp/config_flow.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/custom_components/openhasp/config_flow.py b/custom_components/openhasp/config_flow.py index b5d2587..25d2ca2 100644 --- a/custom_components/openhasp/config_flow.py +++ b/custom_components/openhasp/config_flow.py @@ -54,8 +54,7 @@ def validate_jsonl(path): return file_in -@config_entries.HANDLERS.register(DOMAIN) -class OpenHASPFlowHandler(config_entries.ConfigFlow): +class OpenHASPFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): """Config flow for OpenHASP component.""" VERSION = 1 From 2a4bfce45ac14f70277ca8fc5178d9ed657a7862 Mon Sep 17 00:00:00 2001 From: Diogo Gomes Date: Sun, 25 Feb 2024 15:42:20 +0000 Subject: [PATCH 12/58] fix for magic numbers since 2024.1 --- custom_components/openhasp/light.py | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/custom_components/openhasp/light.py b/custom_components/openhasp/light.py index 596f825..22149ed 100644 --- a/custom_components/openhasp/light.py +++ b/custom_components/openhasp/light.py @@ -6,8 +6,7 @@ from homeassistant.components.light import ( ATTR_BRIGHTNESS, ATTR_HS_COLOR, - SUPPORT_BRIGHTNESS, - SUPPORT_COLOR, + ColorMode, LightEntity, ) from homeassistant.config_entries import ConfigEntry @@ -105,6 +104,7 @@ async def async_setup_entry( class HASPLight(HASPToggleEntity, LightEntity): """Representation of openHASP Light.""" + _attr_supported_color_modes = {ColorMode.BRIGHTNESS} def __init__(self, name, hwid, topic, gpio): """Initialize the light.""" @@ -159,12 +159,13 @@ async def light_state_message_received(msg): class HASPDimmableLight(HASPToggleEntity, LightEntity): """Representation of openHASP Light.""" + _attr_color_mode = ColorMode.BRIGHTNESS + _attr_supported_color_modes = {ColorMode.BRIGHTNESS} def __init__(self, name, hwid, topic, gpio): """Initialize the dimmable light.""" super().__init__(name, hwid, topic, gpio) self._brightness = None - self._attr_supported_features = SUPPORT_BRIGHTNESS self._gpio = gpio self._attr_name = f"{name} dimmable light {self._gpio}" @@ -241,6 +242,8 @@ async def async_turn_on(self, **kwargs): class HASPBackLight(HASPToggleEntity, LightEntity, RestoreEntity): """Representation of HASP LVGL Backlight.""" + _attr_color_mode = ColorMode.BRIGHTNESS + _attr_supported_color_modes = {ColorMode.BRIGHTNESS} def __init__(self, name, hwid, topic, brightness): """Initialize the light.""" @@ -248,7 +251,6 @@ def __init__(self, name, hwid, topic, brightness): self._awake_brightness = 255 self._brightness = None self._idle_brightness = brightness - self._attr_supported_features = SUPPORT_BRIGHTNESS self._attr_name = f"{name} backlight" @property @@ -396,12 +398,14 @@ async def async_turn_on(self, **kwargs): class HASPMoodLight(HASPToggleEntity, LightEntity, RestoreEntity): """Representation of HASP LVGL Moodlight.""" + _attr_color_mode = ColorMode.HS + _attr_supported_color_modes = {ColorMode.HS} + def __init__(self, name, hwid, topic): """Initialize the light.""" super().__init__(name, hwid, topic, "moodlight") self._hs = None self._brightness = None - self._attr_supported_features = SUPPORT_COLOR | SUPPORT_BRIGHTNESS self._attr_name = f"{name} moodlight" @property From dcc487584531e0fa100ffe78de7e2e1f6bc737bf Mon Sep 17 00:00:00 2001 From: Diogo Gomes Date: Sun, 25 Feb 2024 18:02:29 +0000 Subject: [PATCH 13/58] fix: method renamed --- custom_components/openhasp/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/custom_components/openhasp/__init__.py b/custom_components/openhasp/__init__.py index 2f39aad..0ba0c4b 100644 --- a/custom_components/openhasp/__init__.py +++ b/custom_components/openhasp/__init__.py @@ -314,7 +314,7 @@ async def async_remove_entry(hass, entry): device_registry.async_remove_device(dev.id) # Component does not remove entity from entity_registry, so we must do it - registry = await entity_registry.async_get_registry(hass) + registry = await entity_registry.async_get(hass) registry.async_remove(hass.data[DOMAIN][CONF_PLATE][plate].entity_id) # pylint: disable=R0902 From 78fec08f7d4e49618ad0e60ad218284bd49dad4d Mon Sep 17 00:00:00 2001 From: Diogo Gomes Date: Sun, 25 Feb 2024 18:20:53 +0000 Subject: [PATCH 14/58] don't create persistent notification and just issue warning on version mismatch --- custom_components/openhasp/__init__.py | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/custom_components/openhasp/__init__.py b/custom_components/openhasp/__init__.py index 0ba0c4b..2c1e066 100644 --- a/custom_components/openhasp/__init__.py +++ b/custom_components/openhasp/__init__.py @@ -403,15 +403,7 @@ async def statusupdate_message_received(msg): major, minor, _ = message["version"].split(".") if (major, minor) != (MAJOR, MINOR): - self.hass.components.persistent_notification.create( - f"You require firmware version {MAJOR}.{MINOR}.x \ - in plate {self._entry.data[CONF_NAME]} \ - for this component to work properly.\ -
Some features will simply not work!", - title="openHASP Firmware mismatch", - notification_id="openhasp_firmware_notification", - ) - _LOGGER.error( + _LOGGER.warning( "%s firmware mismatch %s <> %s", self._entry.data[CONF_NAME], (major, minor), From e9ac4d3a917a21032b0655f2b631910f5424669a Mon Sep 17 00:00:00 2001 From: Diogo Gomes Date: Sun, 25 Feb 2024 21:41:13 +0000 Subject: [PATCH 15/58] support discovery through mDNS --- custom_components/openhasp/__init__.py | 2 +- custom_components/openhasp/config_flow.py | 24 ++++++++++++++++++----- custom_components/openhasp/manifest.json | 1 + 3 files changed, 21 insertions(+), 6 deletions(-) diff --git a/custom_components/openhasp/__init__.py b/custom_components/openhasp/__init__.py index 2c1e066..5516e90 100644 --- a/custom_components/openhasp/__init__.py +++ b/custom_components/openhasp/__init__.py @@ -314,7 +314,7 @@ async def async_remove_entry(hass, entry): device_registry.async_remove_device(dev.id) # Component does not remove entity from entity_registry, so we must do it - registry = await entity_registry.async_get(hass) + registry = entity_registry.async_get(hass) registry.async_remove(hass.data[DOMAIN][CONF_PLATE][plate].entity_id) # pylint: disable=R0902 diff --git a/custom_components/openhasp/config_flow.py b/custom_components/openhasp/config_flow.py index 25d2ca2..87f9782 100644 --- a/custom_components/openhasp/config_flow.py +++ b/custom_components/openhasp/config_flow.py @@ -11,6 +11,7 @@ import voluptuous as vol from .const import ( + DEFAULT_TOPIC, CONF_DIMLIGHTS, CONF_HWID, CONF_IDLE_BRIGHTNESS, @@ -83,11 +84,24 @@ async def async_step_user(self, user_input=None): return self.async_abort(reason="discovery_only") + async def async_step_zeroconf(self, discovery_info=None): + _discovered = json.loads(discovery_info.properties.get('discovery')) + _LOGGER.error("Discovered ZeroConf: %s", _discovered) + + _discovered[CONF_TOPIC] = f"{DEFAULT_TOPIC}/{_discovered[DISCOVERED_NODE]}" + + return await self._process_discovery(_discovered) + async def async_step_mqtt(self, discovery_info=None): """Handle a flow initialized by MQTT discovery.""" _discovered = json.loads(discovery_info.payload) - _LOGGER.debug("Discovered: %s", _discovered) + _LOGGER.debug("Discovered MQTT: %s", _discovered) + _discovered[CONF_TOPIC] = f"{discovery_info.topic.split('/')[0]}/{_discovered[DISCOVERED_NODE]}" + + return await self._process_discovery(_discovered) + + async def _process_discovery(self, _discovered): await self.async_set_unique_id(_discovered[DISCOVERED_HWID], raise_on_progress=False) self._abort_if_unique_id_configured() @@ -106,9 +120,7 @@ async def async_step_mqtt(self, discovery_info=None): self.config_data[CONF_NODE] = self.config_data[CONF_NAME] = _discovered[ DISCOVERED_NODE ] - self.config_data[ - CONF_TOPIC - ] = f"{discovery_info.topic.split('/')[0]}/{self.config_data[CONF_NODE]}" + self.config_data[CONF_TOPIC] = _discovered[CONF_TOPIC] self.config_data[DISCOVERED_URL] = _discovered.get(DISCOVERED_URL) self.config_data[DISCOVERED_MANUFACTURER] = _discovered.get( @@ -121,6 +133,8 @@ async def async_step_mqtt(self, discovery_info=None): self.config_data[CONF_DIMLIGHTS] = _discovered.get(DISCOVERED_DIM) self.config_data[CONF_INPUT] = _discovered.get(DISCOVERED_INPUT) + self.context.update({"title_placeholders": {"name": self.config_data[CONF_NODE]}}) + return await self.async_step_personalize() async def async_step_personalize(self, user_input=None): @@ -164,7 +178,7 @@ async def async_step_personalize(self, user_input=None): data_schema=vol.Schema( { vol.Required( - CONF_TOPIC, default=self.config_data.get(CONF_TOPIC, "hasp") + CONF_TOPIC, default=self.config_data.get(CONF_TOPIC, DEFAULT_TOPIC) ): str, vol.Required( CONF_NAME, default=self.config_data.get(CONF_NAME) diff --git a/custom_components/openhasp/manifest.json b/custom_components/openhasp/manifest.json index bb1f9f0..30b5d8f 100644 --- a/custom_components/openhasp/manifest.json +++ b/custom_components/openhasp/manifest.json @@ -8,6 +8,7 @@ "iot_class": "local_push", "issue_tracker": "https://github.com/HASwitchPlate/openHASP-custom-component/issues", "mqtt": ["hasp/discovery/#"], + "zeroconf": ["_openhasp._tcp.local."], "requirements": ["jsonschema>=3.2.0"], "version": "0.7.1" } From 72f262be1bdc6a9231fb33f470aacbd6593576f1 Mon Sep 17 00:00:00 2001 From: Diogo Gomes Date: Sun, 25 Feb 2024 21:45:02 +0000 Subject: [PATCH 16/58] black + isort --- custom_components/openhasp/__init__.py | 22 ++++++++++++--------- custom_components/openhasp/binary_sensor.py | 1 - custom_components/openhasp/button.py | 8 ++------ custom_components/openhasp/common.py | 10 +++++++--- custom_components/openhasp/config_flow.py | 21 +++++++++++++------- custom_components/openhasp/image.py | 7 ++++++- custom_components/openhasp/light.py | 5 ++++- custom_components/openhasp/number.py | 5 +++-- custom_components/openhasp/switch.py | 4 ++-- 9 files changed, 51 insertions(+), 32 deletions(-) diff --git a/custom_components/openhasp/__init__.py b/custom_components/openhasp/__init__.py index 5516e90..caea74d 100644 --- a/custom_components/openhasp/__init__.py +++ b/custom_components/openhasp/__init__.py @@ -5,12 +5,11 @@ import os import pathlib import re -import jsonschema -from homeassistant.components.button import DOMAIN as BUTTON_DOMAIN -from homeassistant.components.number import DOMAIN as NUMBER_DOMAIN from homeassistant.components.binary_sensor import DOMAIN as BINARY_SENSOR_DOMAIN +from homeassistant.components.button import DOMAIN as BUTTON_DOMAIN from homeassistant.components.light import DOMAIN as LIGHT_DOMAIN +from homeassistant.components.number import DOMAIN as NUMBER_DOMAIN from homeassistant.components.switch import DOMAIN as SWITCH_DOMAIN from homeassistant.const import CONF_NAME, STATE_UNAVAILABLE, STATE_UNKNOWN from homeassistant.core import callback @@ -24,10 +23,14 @@ from homeassistant.helpers.restore_state import RestoreEntity from homeassistant.helpers.service import async_call_from_config from homeassistant.util import slugify +import jsonschema import voluptuous as vol from .common import HASP_IDLE_SCHEMA from .const import ( + ATTR_COMMAND_KEYWORD, + ATTR_COMMAND_PARAMETERS, + ATTR_CONFIG_PARAMETERS, ATTR_CONFIG_SUBMODULE, ATTR_FORCE_FITSCREEN, ATTR_HEIGHT, @@ -36,9 +39,6 @@ ATTR_OBJECT, ATTR_PAGE, ATTR_PATH, - ATTR_COMMAND_KEYWORD, - ATTR_COMMAND_PARAMETERS, - ATTR_CONFIG_PARAMETERS, ATTR_WIDTH, CONF_COMPONENT, CONF_EVENT, @@ -72,14 +72,14 @@ MAJOR, MINOR, SERVICE_CLEAR_PAGE, + SERVICE_COMMAND, + SERVICE_CONFIG, SERVICE_LOAD_PAGE, SERVICE_PAGE_CHANGE, SERVICE_PAGE_NEXT, SERVICE_PAGE_PREV, SERVICE_PUSH_IMAGE, SERVICE_WAKEUP, - SERVICE_COMMAND, - SERVICE_CONFIG, ) from .image import ImageServeView, image_to_rgb565 @@ -291,6 +291,7 @@ async def async_unload_entry(hass, entry): return True + async def async_remove_entry(hass, entry): plate = entry.data[CONF_NAME] @@ -317,6 +318,7 @@ async def async_remove_entry(hass, entry): registry = entity_registry.async_get(hass) registry.async_remove(hass.data[DOMAIN][CONF_PLATE][plate].entity_id) + # pylint: disable=R0902 class SwitchPlate(RestoreEntity): """Representation of an openHASP Plate.""" @@ -593,7 +595,9 @@ async def async_config_service(self, submodule, parameters): retain=False, ) - async def async_push_image(self, image, obj, width=None, height=None, fitscreen=False): + async def async_push_image( + self, image, obj, width=None, height=None, fitscreen=False + ): """Update object image.""" image_id = hashlib.md5(image.encode("utf-8")).hexdigest() diff --git a/custom_components/openhasp/binary_sensor.py b/custom_components/openhasp/binary_sensor.py index 828c188..18f2716 100644 --- a/custom_components/openhasp/binary_sensor.py +++ b/custom_components/openhasp/binary_sensor.py @@ -4,7 +4,6 @@ from typing import Callable from homeassistant.components.binary_sensor import BinarySensorEntity - # pylint: disable=R0801 from homeassistant.config_entries import ConfigEntry from homeassistant.const import CONF_NAME diff --git a/custom_components/openhasp/button.py b/custom_components/openhasp/button.py index 3397a54..6387e79 100644 --- a/custom_components/openhasp/button.py +++ b/custom_components/openhasp/button.py @@ -1,16 +1,12 @@ """Support for current page numbers.""" import logging -from homeassistant.components.button import ( - ButtonDeviceClass, - ButtonEntity, -) +from homeassistant.components.button import ButtonDeviceClass, ButtonEntity from homeassistant.config_entries import ConfigEntry -from homeassistant.core import HomeAssistant from homeassistant.const import CONF_NAME +from homeassistant.core import HomeAssistant from homeassistant.helpers.entity import EntityCategory - from .common import HASPEntity from .const import CONF_HWID, CONF_TOPIC diff --git a/custom_components/openhasp/common.py b/custom_components/openhasp/common.py index 50f8962..0630314 100644 --- a/custom_components/openhasp/common.py +++ b/custom_components/openhasp/common.py @@ -57,8 +57,12 @@ async def online(event): if self._state: await self.refresh() else: - self.async_write_ha_state() # Just to update availability - _LOGGER.debug("%s is available, %s", self.entity_id, "refresh" if self._state else "stale") + self.async_write_ha_state() # Just to update availability + _LOGGER.debug( + "%s is available, %s", + self.entity_id, + "refresh" if self._state else "stale", + ) self._subscriptions.append( self.hass.bus.async_listen(EVENT_HASP_PLATE_ONLINE, online) @@ -84,7 +88,7 @@ async def async_will_remove_from_hass(self): class HASPToggleEntity(HASPEntity, ToggleEntity): """Representation of HASP ToggleEntity.""" - + def __init__(self, name, hwid, topic, gpio): """Initialize the relay.""" super().__init__(name, hwid, topic, gpio) diff --git a/custom_components/openhasp/config_flow.py b/custom_components/openhasp/config_flow.py index 87f9782..648b421 100644 --- a/custom_components/openhasp/config_flow.py +++ b/custom_components/openhasp/config_flow.py @@ -11,7 +11,6 @@ import voluptuous as vol from .const import ( - DEFAULT_TOPIC, CONF_DIMLIGHTS, CONF_HWID, CONF_IDLE_BRIGHTNESS, @@ -23,6 +22,7 @@ CONF_RELAYS, CONF_TOPIC, DEFAULT_IDLE_BRIGHNESS, + DEFAULT_TOPIC, DISCOVERED_DIM, DISCOVERED_HWID, DISCOVERED_INPUT, @@ -85,7 +85,7 @@ async def async_step_user(self, user_input=None): return self.async_abort(reason="discovery_only") async def async_step_zeroconf(self, discovery_info=None): - _discovered = json.loads(discovery_info.properties.get('discovery')) + _discovered = json.loads(discovery_info.properties.get("discovery")) _LOGGER.error("Discovered ZeroConf: %s", _discovered) _discovered[CONF_TOPIC] = f"{DEFAULT_TOPIC}/{_discovered[DISCOVERED_NODE]}" @@ -97,12 +97,16 @@ async def async_step_mqtt(self, discovery_info=None): _discovered = json.loads(discovery_info.payload) _LOGGER.debug("Discovered MQTT: %s", _discovered) - _discovered[CONF_TOPIC] = f"{discovery_info.topic.split('/')[0]}/{_discovered[DISCOVERED_NODE]}" + _discovered[ + CONF_TOPIC + ] = f"{discovery_info.topic.split('/')[0]}/{_discovered[DISCOVERED_NODE]}" return await self._process_discovery(_discovered) async def _process_discovery(self, _discovered): - await self.async_set_unique_id(_discovered[DISCOVERED_HWID], raise_on_progress=False) + await self.async_set_unique_id( + _discovered[DISCOVERED_HWID], raise_on_progress=False + ) self._abort_if_unique_id_configured() version = _discovered.get(DISCOVERED_VERSION) @@ -116,7 +120,7 @@ async def _process_discovery(self, _discovered): self.config_data[DISCOVERED_VERSION] = version - self.config_data[CONF_HWID] = _discovered[DISCOVERED_HWID] + self.config_data[CONF_HWID] = _discovered[DISCOVERED_HWID] self.config_data[CONF_NODE] = self.config_data[CONF_NAME] = _discovered[ DISCOVERED_NODE ] @@ -133,7 +137,9 @@ async def _process_discovery(self, _discovered): self.config_data[CONF_DIMLIGHTS] = _discovered.get(DISCOVERED_DIM) self.config_data[CONF_INPUT] = _discovered.get(DISCOVERED_INPUT) - self.context.update({"title_placeholders": {"name": self.config_data[CONF_NODE]}}) + self.context.update( + {"title_placeholders": {"name": self.config_data[CONF_NODE]}} + ) return await self.async_step_personalize() @@ -178,7 +184,8 @@ async def async_step_personalize(self, user_input=None): data_schema=vol.Schema( { vol.Required( - CONF_TOPIC, default=self.config_data.get(CONF_TOPIC, DEFAULT_TOPIC) + CONF_TOPIC, + default=self.config_data.get(CONF_TOPIC, DEFAULT_TOPIC), ): str, vol.Required( CONF_NAME, default=self.config_data.get(CONF_NAME) diff --git a/custom_components/openhasp/image.py b/custom_components/openhasp/image.py index c39095b..6b2998a 100644 --- a/custom_components/openhasp/image.py +++ b/custom_components/openhasp/image.py @@ -49,7 +49,12 @@ def image_to_rgb565(in_image, size, fitscreen): b = (pix[2] >> 3) & 0x1F out_image.write(struct.pack("H", (r << 11) | (g << 5) | b)) - _LOGGER.debug("image_to_rgb565 out_image: %s - %s > %s", out_image.name, (original_width, original_height), im.size) + _LOGGER.debug( + "image_to_rgb565 out_image: %s - %s > %s", + out_image.name, + (original_width, original_height), + im.size, + ) out_image.flush() diff --git a/custom_components/openhasp/light.py b/custom_components/openhasp/light.py index 22149ed..476a299 100644 --- a/custom_components/openhasp/light.py +++ b/custom_components/openhasp/light.py @@ -104,7 +104,8 @@ async def async_setup_entry( class HASPLight(HASPToggleEntity, LightEntity): """Representation of openHASP Light.""" - _attr_supported_color_modes = {ColorMode.BRIGHTNESS} + + _attr_supported_color_modes = {ColorMode.BRIGHTNESS} def __init__(self, name, hwid, topic, gpio): """Initialize the light.""" @@ -159,6 +160,7 @@ async def light_state_message_received(msg): class HASPDimmableLight(HASPToggleEntity, LightEntity): """Representation of openHASP Light.""" + _attr_color_mode = ColorMode.BRIGHTNESS _attr_supported_color_modes = {ColorMode.BRIGHTNESS} @@ -242,6 +244,7 @@ async def async_turn_on(self, **kwargs): class HASPBackLight(HASPToggleEntity, LightEntity, RestoreEntity): """Representation of HASP LVGL Backlight.""" + _attr_color_mode = ColorMode.BRIGHTNESS _attr_supported_color_modes = {ColorMode.BRIGHTNESS} diff --git a/custom_components/openhasp/number.py b/custom_components/openhasp/number.py index d17f20b..e3632cd 100644 --- a/custom_components/openhasp/number.py +++ b/custom_components/openhasp/number.py @@ -1,11 +1,11 @@ """Support for current page numbers.""" -import logging from dataclasses import dataclass +import logging from homeassistant.components.number import NumberEntity, NumberEntityDescription from homeassistant.config_entries import ConfigEntry -from homeassistant.core import HomeAssistant, callback from homeassistant.const import CONF_NAME +from homeassistant.core import HomeAssistant, callback from homeassistant.helpers.entity import EntityCategory from homeassistant.helpers.restore_state import RestoreEntity @@ -22,6 +22,7 @@ class HASPNumberDescriptionMixin: command_topic: str state_topic: str + @dataclass class HASPNumberDescription(NumberEntityDescription, HASPNumberDescriptionMixin): """Class to describe an HASP Number Entity.""" diff --git a/custom_components/openhasp/switch.py b/custom_components/openhasp/switch.py index 05a4918..7e64905 100644 --- a/custom_components/openhasp/switch.py +++ b/custom_components/openhasp/switch.py @@ -5,10 +5,10 @@ # pylint: disable=R0801 from homeassistant.config_entries import ConfigEntry -from homeassistant.const import CONF_NAME, STATE_ON, STATE_OFF -from homeassistant.helpers.entity import EntityCategory +from homeassistant.const import CONF_NAME, STATE_OFF, STATE_ON from homeassistant.core import HomeAssistant, callback import homeassistant.helpers.config_validation as cv +from homeassistant.helpers.entity import EntityCategory import voluptuous as vol from .common import HASPToggleEntity From 7af0c7ab2c7e0b8fd01aee893b09eb2a806bbc0e Mon Sep 17 00:00:00 2001 From: Diogo Gomes Date: Sun, 25 Feb 2024 21:48:04 +0000 Subject: [PATCH 17/58] fix key sort --- custom_components/openhasp/manifest.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/custom_components/openhasp/manifest.json b/custom_components/openhasp/manifest.json index 30b5d8f..83c5f7d 100644 --- a/custom_components/openhasp/manifest.json +++ b/custom_components/openhasp/manifest.json @@ -8,7 +8,7 @@ "iot_class": "local_push", "issue_tracker": "https://github.com/HASwitchPlate/openHASP-custom-component/issues", "mqtt": ["hasp/discovery/#"], - "zeroconf": ["_openhasp._tcp.local."], "requirements": ["jsonschema>=3.2.0"], - "version": "0.7.1" + "version": "0.7.1", + "zeroconf": ["_openhasp._tcp.local."] } From fea4ebf74fd5e99052e9c6a0d8c7df695c17d188 Mon Sep 17 00:00:00 2001 From: Diogo Gomes Date: Sun, 25 Feb 2024 22:32:44 +0000 Subject: [PATCH 18/58] fix: adjust to firmware updates --- custom_components/openhasp/config_flow.py | 7 ++++--- custom_components/openhasp/const.py | 1 + 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/custom_components/openhasp/config_flow.py b/custom_components/openhasp/config_flow.py index 648b421..533f391 100644 --- a/custom_components/openhasp/config_flow.py +++ b/custom_components/openhasp/config_flow.py @@ -30,6 +30,7 @@ DISCOVERED_MANUFACTURER, DISCOVERED_MODEL, DISCOVERED_NODE, + DISCOVERED_NODE_T, DISCOVERED_PAGES, DISCOVERED_POWER, DISCOVERED_URL, @@ -85,10 +86,10 @@ async def async_step_user(self, user_input=None): return self.async_abort(reason="discovery_only") async def async_step_zeroconf(self, discovery_info=None): - _discovered = json.loads(discovery_info.properties.get("discovery")) - _LOGGER.error("Discovered ZeroConf: %s", _discovered) + _discovered = discovery_info.properties + _LOGGER.debug("Discovered ZeroConf: %s", _discovered) - _discovered[CONF_TOPIC] = f"{DEFAULT_TOPIC}/{_discovered[DISCOVERED_NODE]}" + _discovered[CONF_TOPIC] = _discovered[DISCOVERED_NODE_T][:-1] return await self._process_discovery(_discovered) diff --git a/custom_components/openhasp/const.py b/custom_components/openhasp/const.py index 79cebe6..493be7e 100644 --- a/custom_components/openhasp/const.py +++ b/custom_components/openhasp/const.py @@ -37,6 +37,7 @@ DEFAULT_IDLE_BRIGHNESS = 25 DISCOVERED_NODE = "node" +DISCOVERED_NODE_T = "node_t" DISCOVERED_MODEL = "mdl" DISCOVERED_MANUFACTURER = "mf" DISCOVERED_HWID = "hwid" From d257639000b9bd8435744f26c76ed7ab634bd689 Mon Sep 17 00:00:00 2001 From: Diogo Gomes Date: Tue, 27 Feb 2024 13:03:59 +0000 Subject: [PATCH 19/58] standardize state value with firmware state reply --- custom_components/openhasp/light.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/custom_components/openhasp/light.py b/custom_components/openhasp/light.py index 476a299..d96cdc3 100644 --- a/custom_components/openhasp/light.py +++ b/custom_components/openhasp/light.py @@ -335,13 +335,13 @@ async def idle_message_received(msg): if message == HASP_IDLE_OFF: brightness = self._awake_brightness - backlight = 1 + backlight = "on" elif message == HASP_IDLE_SHORT: brightness = self._idle_brightness - backlight = 1 + backlight = "on" elif message == HASP_IDLE_LONG: brightness = self._awake_brightness - backlight = 0 + backlight = "off" else: return @@ -374,7 +374,7 @@ async def refresh(self): """Sync local state back to plate.""" cmd_topic = f"{self._topic}/command" - new_state = {"state": self._state, "brightness": self._brightness} + new_state = {"state": "on" if self._state else "off", "brightness": self._brightness} _LOGGER.debug("refresh(%s) backlight - %s", self.name, new_state) From 1205da077a69d2bb808de48b75c316b4ae2d13dc Mon Sep 17 00:00:00 2001 From: fvanroie <15969459+fvanroie@users.noreply.github.com> Date: Wed, 28 Feb 2024 14:12:00 +0100 Subject: [PATCH 20/58] Update manifest.json --- custom_components/openhasp/manifest.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/custom_components/openhasp/manifest.json b/custom_components/openhasp/manifest.json index 83c5f7d..42f8ae4 100644 --- a/custom_components/openhasp/manifest.json +++ b/custom_components/openhasp/manifest.json @@ -9,6 +9,6 @@ "issue_tracker": "https://github.com/HASwitchPlate/openHASP-custom-component/issues", "mqtt": ["hasp/discovery/#"], "requirements": ["jsonschema>=3.2.0"], - "version": "0.7.1", + "version": "0.7.2", "zeroconf": ["_openhasp._tcp.local."] } From 520acdb39a71440528485c04f2ee92e96f6d6590 Mon Sep 17 00:00:00 2001 From: Barry Schut Date: Fri, 1 Mar 2024 12:24:44 +0100 Subject: [PATCH 21/58] Added optional setting http_proxy to push_image This will allow the user to set up an external reverse proxy to have the HA instance operate on HTTPS and push_image use that reverse proxy to avoid using HTTPS traffic processing on the plate. --- custom_components/openhasp/__init__.py | 17 ++++++++++++----- custom_components/openhasp/const.py | 1 + custom_components/openhasp/services.yaml | 7 +++++++ 3 files changed, 20 insertions(+), 5 deletions(-) diff --git a/custom_components/openhasp/__init__.py b/custom_components/openhasp/__init__.py index caea74d..7f982c5 100644 --- a/custom_components/openhasp/__init__.py +++ b/custom_components/openhasp/__init__.py @@ -35,6 +35,7 @@ ATTR_FORCE_FITSCREEN, ATTR_HEIGHT, ATTR_IDLE, + ATTR_PROXY, ATTR_IMAGE, ATTR_OBJECT, ATTR_PAGE, @@ -152,6 +153,7 @@ def hasp_object(value): { vol.Required(ATTR_IMAGE): vol.Any(cv.url, cv.isfile), vol.Required(ATTR_OBJECT): hasp_object, + vol.Optional(ATTR_PROXY): vol.Any(cv.url, cv.isfile), vol.Optional(ATTR_WIDTH): cv.positive_int, vol.Optional(ATTR_HEIGHT): cv.positive_int, vol.Optional(ATTR_FORCE_FITSCREEN): cv.boolean, @@ -596,7 +598,7 @@ async def async_config_service(self, submodule, parameters): ) async def async_push_image( - self, image, obj, width=None, height=None, fitscreen=False + self, image, obj, http_proxy=None, width=None, height=None, fitscreen=False ): """Update object image.""" @@ -610,10 +612,15 @@ async def async_push_image( cmd_topic = f"{self._topic}/command/{obj}.src" - rgb_image_url = ( - f"{get_url(self.hass, allow_external=False)}/api/openhasp/serve/{image_id}" - ) - + if http_proxy == "None": + rgb_image_url = ( + f"{get_url(self.hass, allow_external=False)}/api/openhasp/serve/{image_id}" + ) + else: + rgb_image_url = ( + f"{http_proxy}/api/openhasp/serve/{image_id}" + ) +#self._entry.data _LOGGER.debug("Push %s with %s", cmd_topic, rgb_image_url) await self.hass.components.mqtt.async_publish( diff --git a/custom_components/openhasp/const.py b/custom_components/openhasp/const.py index 493be7e..58907af 100644 --- a/custom_components/openhasp/const.py +++ b/custom_components/openhasp/const.py @@ -92,6 +92,7 @@ ATTR_COMMAND_PARAMETERS = "parameters" ATTR_CONFIG_SUBMODULE = "submodule" ATTR_CONFIG_PARAMETERS = "parameters" +ATTR_PROXY = "http_proxy" ATTR_IMAGE = "image" ATTR_OBJECT = "obj" ATTR_WIDTH = "width" diff --git a/custom_components/openhasp/services.yaml b/custom_components/openhasp/services.yaml index 3a44388..9643b4c 100644 --- a/custom_components/openhasp/services.yaml +++ b/custom_components/openhasp/services.yaml @@ -106,6 +106,13 @@ push_image: example: "https://people.sc.fsu.edu/~jburkardt/data/jpg/lena.jpg" selector: text: + http_proxy: + name: HTTP (Reverse) Proxy + description: Proxy address to use. This can be used to allow HTTP access to an otherwise SSL secured HA instance. Offering the proxy functionality is out of the scope of this integraiton. + required: false + example: "http://people.sc.fsu.edu:port" + selector: + text: obj: name: Object description: Object ID in the format p#b## From 780374f29c741a1887373022314af6e483cb8c34 Mon Sep 17 00:00:00 2001 From: Diogo Gomes Date: Sat, 9 Mar 2024 00:31:26 +0000 Subject: [PATCH 22/58] fix mDNS configurations --- custom_components/openhasp/config_flow.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/custom_components/openhasp/config_flow.py b/custom_components/openhasp/config_flow.py index 533f391..1a13fb1 100644 --- a/custom_components/openhasp/config_flow.py +++ b/custom_components/openhasp/config_flow.py @@ -91,6 +91,17 @@ async def async_step_zeroconf(self, discovery_info=None): _discovered[CONF_TOPIC] = _discovered[DISCOVERED_NODE_T][:-1] + for key in [ + DISCOVERED_PAGES, + DISCOVERED_POWER, + DISCOVERED_LIGHT, + DISCOVERED_DIM, + DISCOVERED_INPUT, + ]: + _discovered[key] = eval(_discovered.get(key)) + _discovered[key] = eval(_discovered.get(key)) + + return await self._process_discovery(_discovered) async def async_step_mqtt(self, discovery_info=None): From ee7172a8a47f48003c1202e723bc2478440144cf Mon Sep 17 00:00:00 2001 From: Diogo Gomes Date: Sat, 9 Mar 2024 00:57:15 +0000 Subject: [PATCH 23/58] eval -> json.loads --- custom_components/openhasp/config_flow.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/custom_components/openhasp/config_flow.py b/custom_components/openhasp/config_flow.py index 1a13fb1..88c58c1 100644 --- a/custom_components/openhasp/config_flow.py +++ b/custom_components/openhasp/config_flow.py @@ -98,9 +98,8 @@ async def async_step_zeroconf(self, discovery_info=None): DISCOVERED_DIM, DISCOVERED_INPUT, ]: - _discovered[key] = eval(_discovered.get(key)) - _discovered[key] = eval(_discovered.get(key)) - + _LOGGER.debug("[%s] Discovered %s = %s", _discovered[CONF_TOPIC], key, _discovered.get(key)) + _discovered[key] = json.loads(_discovered.get(key)) return await self._process_discovery(_discovered) From f7b555cc569ac36556bed9ef4282cee4ea9c5850 Mon Sep 17 00:00:00 2001 From: FreeBear Date: Thu, 2 May 2024 14:10:14 +0100 Subject: [PATCH 24/58] PIL uses a width,height tuple for resize() and thumbnail(). --- custom_components/openhasp/image.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/custom_components/openhasp/image.py b/custom_components/openhasp/image.py index 6b2998a..7a1b904 100644 --- a/custom_components/openhasp/image.py +++ b/custom_components/openhasp/image.py @@ -32,9 +32,9 @@ def image_to_rgb565(in_image, size, fitscreen): if not fitscreen: width = min(w for w in [width, original_width] if w is not None and w > 0) height = min(h for h in [height, original_height] if h is not None and h > 0) - im.thumbnail((height, width), Image.LANCZOS) + im.thumbnail((width, height), Image.LANCZOS) else: - im = im.resize((height, width), Image.LANCZOS) + im = im.resize((width, height), Image.LANCZOS) width, height = im.size # actual size after resize out_image = tempfile.NamedTemporaryFile(mode="w+b") From d36d97dd0898d746915474915572abba6c29cf08 Mon Sep 17 00:00:00 2001 From: Diogo Gomes Date: Mon, 13 May 2024 23:04:18 +0100 Subject: [PATCH 25/58] address https://developers.home-assistant.io/blog/2024/02/27/deprecate-bind-hass-and-hass-components --- custom_components/openhasp/__init__.py | 67 ++++++++------------- custom_components/openhasp/binary_sensor.py | 12 ++-- custom_components/openhasp/button.py | 3 +- custom_components/openhasp/config_flow.py | 22 ++++--- custom_components/openhasp/light.py | 47 ++++++++------- custom_components/openhasp/number.py | 6 +- custom_components/openhasp/switch.py | 21 ++++--- 7 files changed, 91 insertions(+), 87 deletions(-) diff --git a/custom_components/openhasp/__init__.py b/custom_components/openhasp/__init__.py index caea74d..55aa375 100644 --- a/custom_components/openhasp/__init__.py +++ b/custom_components/openhasp/__init__.py @@ -6,6 +6,7 @@ import pathlib import re +from homeassistant.components.mqtt import async_subscribe, async_publish from homeassistant.components.binary_sensor import DOMAIN as BINARY_SENSOR_DOMAIN from homeassistant.components.button import DOMAIN as BUTTON_DOMAIN from homeassistant.components.light import DOMAIN as LIGHT_DOMAIN @@ -391,8 +392,8 @@ async def page_update_received(msg): _LOGGER.error("%s in %s", err, msg.payload) self._subscriptions.append( - await self.hass.components.mqtt.async_subscribe( - f"{self._topic}/state/page", page_update_received + await async_subscribe( + self.hass, f"{self._topic}/state/page", page_update_received ) ) @@ -433,11 +434,13 @@ async def statusupdate_message_received(msg): _LOGGER.error("While processing status update: %s", err) self._subscriptions.append( - await self.hass.components.mqtt.async_subscribe( - f"{self._topic}/state/statusupdate", statusupdate_message_received + await async_subscribe( + self.hass, + f"{self._topic}/state/statusupdate", + statusupdate_message_received, ) ) - await self.hass.components.mqtt.async_publish( + await async_publish( self.hass, f"{self._topic}/command", "statusupdate", qos=0, retain=False ) @@ -451,8 +454,8 @@ async def idle_message_received(msg): _LOGGER.error("While processing idle message: %s", err) self._subscriptions.append( - await self.hass.components.mqtt.async_subscribe( - f"{self._topic}/state/idle", idle_message_received + await async_subscribe( + self.hass, f"{self._topic}/state/idle", idle_message_received ) ) @@ -491,9 +494,7 @@ async def lwt_message_received(msg): _LOGGER.error("While processing LWT: %s", err) self._subscriptions.append( - await self.hass.components.mqtt.async_subscribe( - f"{self._topic}/LWT", lwt_message_received - ) + await async_subscribe(self.hass, f"{self._topic}/LWT", lwt_message_received) ) @property @@ -515,9 +516,7 @@ async def async_wakeup(self): """Wake up the display.""" cmd_topic = f"{self._topic}/command" _LOGGER.warning("Wakeup will be deprecated in 0.8.0") # remove in version 0.8.0 - await self.hass.components.mqtt.async_publish( - self.hass, cmd_topic, "wakeup", qos=0, retain=False - ) + await async_publish(self.hass, cmd_topic, "wakeup", qos=0, retain=False) async def async_change_page_next(self): """Change page to next one.""" @@ -526,9 +525,7 @@ async def async_change_page_next(self): "page next service will be deprecated in 0.8.0" ) # remove in version 0.8.0 - await self.hass.components.mqtt.async_publish( - self.hass, cmd_topic, "page next", qos=0, retain=False - ) + await async_publish(self.hass, cmd_topic, "page next", qos=0, retain=False) async def async_change_page_prev(self): """Change page to previous one.""" @@ -537,22 +534,18 @@ async def async_change_page_prev(self): "page prev service will be deprecated in 0.8.0" ) # remove in version 0.8.0 - await self.hass.components.mqtt.async_publish( - self.hass, cmd_topic, "page prev", qos=0, retain=False - ) + await async_publish(self.hass, cmd_topic, "page prev", qos=0, retain=False) async def async_clearpage(self, page="all"): """Clear page.""" cmd_topic = f"{self._topic}/command" - await self.hass.components.mqtt.async_publish( + await async_publish( self.hass, cmd_topic, f"clearpage {page}", qos=0, retain=False ) if page == "all": - await self.hass.components.mqtt.async_publish( - self.hass, cmd_topic, "page 1", qos=0, retain=False - ) + await async_publish(self.hass, cmd_topic, "page 1", qos=0, retain=False) async def async_change_page(self, page): """Change page to number.""" @@ -570,14 +563,12 @@ async def async_change_page(self, page): self._page = page _LOGGER.debug("Change page %s", self._page) - await self.hass.components.mqtt.async_publish( - self.hass, cmd_topic, self._page, qos=0, retain=False - ) + await async_publish(self.hass, cmd_topic, self._page, qos=0, retain=False) self.async_write_ha_state() async def async_command_service(self, keyword, parameters): """Send commands directly to the plate entity.""" - await self.hass.components.mqtt.async_publish( + await async_publish( self.hass, f"{self._topic}/command", f"{keyword} {parameters}".strip(), @@ -587,7 +578,7 @@ async def async_command_service(self, keyword, parameters): async def async_config_service(self, submodule, parameters): """Send configuration commands to plate entity.""" - await self.hass.components.mqtt.async_publish( + await async_publish( self.hass, f"{self._topic}/config/{submodule}", f"{parameters}".strip(), @@ -616,9 +607,7 @@ async def async_push_image( _LOGGER.debug("Push %s with %s", cmd_topic, rgb_image_url) - await self.hass.components.mqtt.async_publish( - self.hass, cmd_topic, rgb_image_url, qos=0, retain=False - ) + await async_publish(self.hass, cmd_topic, rgb_image_url, qos=0, retain=False) async def refresh(self): """Refresh objects in the SwitchPlate.""" @@ -642,7 +631,7 @@ async def send_lines(lines): mqtt_payload_buffer = "" for line in lines: if len(mqtt_payload_buffer) + len(line) > 1000: - await self.hass.components.mqtt.async_publish( + await async_publish( self.hass, f"{cmd_topic}/jsonl", mqtt_payload_buffer, @@ -652,7 +641,7 @@ async def send_lines(lines): mqtt_payload_buffer = line else: mqtt_payload_buffer = mqtt_payload_buffer + line - await self.hass.components.mqtt.async_publish( + await async_publish( self.hass, f"{cmd_topic}/jsonl", mqtt_payload_buffer, @@ -770,9 +759,7 @@ async def _async_template_result_changed(event, updates): result, ) - await self.hass.components.mqtt.async_publish( - self.hass, self.command_topic + _property, result - ) + await async_publish(self.hass, self.command_topic + _property, result) property_template = async_track_template_result( self.hass, @@ -787,9 +774,7 @@ async def refresh(self): """Refresh based on cached values.""" for _property, result in self.cached_properties.items(): _LOGGER.debug("Refresh object %s.%s = %s", self.obj_id, _property, result) - await self.hass.components.mqtt.async_publish( - self.hass, self.command_topic + _property, result - ) + await async_publish(self.hass, self.command_topic + _property, result) async def async_listen_hasp_events(self): """Listen to messages on MQTT for HASP events.""" @@ -834,6 +819,4 @@ async def message_received(msg): ) _LOGGER.debug("Subscribe to '%s' events on '%s'", self.obj_id, self.state_topic) - return await self.hass.components.mqtt.async_subscribe( - self.state_topic, message_received - ) + return await async_subscribe(self.hass, self.state_topic, message_received) diff --git a/custom_components/openhasp/binary_sensor.py b/custom_components/openhasp/binary_sensor.py index 18f2716..951b221 100644 --- a/custom_components/openhasp/binary_sensor.py +++ b/custom_components/openhasp/binary_sensor.py @@ -3,7 +3,9 @@ import logging from typing import Callable +from homeassistant.components.mqtt import async_publish, async_subscribe from homeassistant.components.binary_sensor import BinarySensorEntity + # pylint: disable=R0801 from homeassistant.config_entries import ConfigEntry from homeassistant.const import CONF_NAME @@ -68,7 +70,7 @@ def device_class(self): async def refresh(self): """Force sync of plate state back to binary sensor.""" - await self.hass.components.mqtt.async_publish( + await async_publish( self.hass, f"{self._topic}/command/input{self._gpio}", "", @@ -96,12 +98,14 @@ async def state_message_received(msg): _LOGGER.error(err) self._subscriptions.append( - await self.hass.components.mqtt.async_subscribe( - f"{self._topic}/state/input{self._gpio}", state_message_received + await async_subscribe( + self.hass, + f"{self._topic}/state/input{self._gpio}", + state_message_received, ) ) - await self.hass.components.mqtt.async_publish( + await async_publish( self.hass, f"{self._topic}/command/input{self._gpio}", "", diff --git a/custom_components/openhasp/button.py b/custom_components/openhasp/button.py index 6387e79..5203215 100644 --- a/custom_components/openhasp/button.py +++ b/custom_components/openhasp/button.py @@ -1,6 +1,7 @@ """Support for current page numbers.""" import logging +from homeassistant.components.mqtt import async_publish from homeassistant.components.button import ButtonDeviceClass, ButtonEntity from homeassistant.config_entries import ConfigEntry from homeassistant.const import CONF_NAME @@ -45,7 +46,7 @@ def __init__(self, name, hwid, topic) -> None: async def async_press(self) -> None: """Handle the button press.""" - await self.hass.components.mqtt.async_publish( + await async_publish( self.hass, f"{self._topic}/command/restart", "", diff --git a/custom_components/openhasp/config_flow.py b/custom_components/openhasp/config_flow.py index 88c58c1..666639d 100644 --- a/custom_components/openhasp/config_flow.py +++ b/custom_components/openhasp/config_flow.py @@ -3,6 +3,7 @@ import logging import os +from homeassistant.components.mqtt import async_publish from homeassistant import config_entries, data_entry_flow, exceptions from homeassistant.components.mqtt import valid_subscribe_topic from homeassistant.const import CONF_NAME @@ -75,7 +76,7 @@ async def async_step_user(self, user_input=None): """Handle a flow initialized by User.""" _LOGGER.info("Discovery Only") - await self.hass.components.mqtt.async_publish( + await async_publish( self.hass, "hasp/broadcast/command/discovery", "discovery", @@ -92,13 +93,18 @@ async def async_step_zeroconf(self, discovery_info=None): _discovered[CONF_TOPIC] = _discovered[DISCOVERED_NODE_T][:-1] for key in [ - DISCOVERED_PAGES, - DISCOVERED_POWER, - DISCOVERED_LIGHT, - DISCOVERED_DIM, - DISCOVERED_INPUT, - ]: - _LOGGER.debug("[%s] Discovered %s = %s", _discovered[CONF_TOPIC], key, _discovered.get(key)) + DISCOVERED_PAGES, + DISCOVERED_POWER, + DISCOVERED_LIGHT, + DISCOVERED_DIM, + DISCOVERED_INPUT, + ]: + _LOGGER.debug( + "[%s] Discovered %s = %s", + _discovered[CONF_TOPIC], + key, + _discovered.get(key), + ) _discovered[key] = json.loads(_discovered.get(key)) return await self._process_discovery(_discovered) diff --git a/custom_components/openhasp/light.py b/custom_components/openhasp/light.py index d96cdc3..10afa20 100644 --- a/custom_components/openhasp/light.py +++ b/custom_components/openhasp/light.py @@ -9,6 +9,7 @@ ColorMode, LightEntity, ) +from homeassistant.components.mqtt import async_publish, async_subscribe from homeassistant.config_entries import ConfigEntry from homeassistant.const import CONF_NAME from homeassistant.core import HomeAssistant, callback @@ -114,7 +115,7 @@ def __init__(self, name, hwid, topic, gpio): async def refresh(self): """Sync local state back to plate.""" - await self.hass.components.mqtt.async_publish( + await async_publish( self.hass, f"{self._topic}/command/output{self._gpio}", json.dumps(HASP_LIGHT_SCHEMA({"state": int(self._state)})), @@ -143,13 +144,15 @@ async def light_state_message_received(msg): _LOGGER.error(err) self._subscriptions.append( - await self.hass.components.mqtt.async_subscribe( - f"{self._topic}/state/output{self._gpio}", light_state_message_received + await async_subscribe( + self.hass, + f"{self._topic}/state/output{self._gpio}", + light_state_message_received, ) ) # Force immediatable state update from plate - await self.hass.components.mqtt.async_publish( + await async_publish( self.hass, f"{self._topic}/command/output{self._gpio}", "", @@ -185,7 +188,7 @@ async def refresh(self): self._brightness, ) - await self.hass.components.mqtt.async_publish( + await async_publish( self.hass, f"{self._topic}/command/output{self._gpio}", json.dumps( @@ -219,14 +222,15 @@ async def dimmable_light_message_received(msg): _LOGGER.error(err) self._subscriptions.append( - await self.hass.components.mqtt.async_subscribe( + await async_subscribe( + self.hass, f"{self._topic}/state/output{self._gpio}", dimmable_light_message_received, ) ) # Force immediatable state update from plate - await self.hass.components.mqtt.async_publish( + await async_publish( self.hass, f"{self._topic}/command/output{self._gpio}", "", @@ -316,14 +320,10 @@ async def backlight_message_received(msg): ) self._subscriptions.append( - await self.hass.components.mqtt.async_subscribe( - state_topic, backlight_message_received - ) + await async_subscribe(self.hass, state_topic, backlight_message_received) ) - await self.hass.components.mqtt.async_publish( - self.hass, cmd_topic, "backlight", qos=0, retain=False - ) + await async_publish(self.hass, cmd_topic, "backlight", qos=0, retain=False) async def async_listen_idleness(self): """Listen to messages on MQTT for HASP idleness.""" @@ -355,7 +355,7 @@ async def idle_message_received(msg): new_state = {"state": backlight, "brightness": brightness} - await self.hass.components.mqtt.async_publish( + await async_publish( self.hass, f"{self._topic}/command", f"backlight {json.dumps(new_state)}", @@ -365,8 +365,8 @@ async def idle_message_received(msg): self.async_write_ha_state() self._subscriptions.append( - await self.hass.components.mqtt.async_subscribe( - f"{self._topic}/state/idle", idle_message_received + await async_subscribe( + self.hass, f"{self._topic}/state/idle", idle_message_received ) ) @@ -374,11 +374,14 @@ async def refresh(self): """Sync local state back to plate.""" cmd_topic = f"{self._topic}/command" - new_state = {"state": "on" if self._state else "off", "brightness": self._brightness} + new_state = { + "state": "on" if self._state else "off", + "brightness": self._brightness, + } _LOGGER.debug("refresh(%s) backlight - %s", self.name, new_state) - await self.hass.components.mqtt.async_publish( + await async_publish( self.hass, cmd_topic, f"backlight {json.dumps(new_state)}", @@ -457,12 +460,12 @@ async def moodlight_message_received(msg): _LOGGER.error("While proccessing moodlight: %s", err) self._subscriptions.append( - await self.hass.components.mqtt.async_subscribe( - f"{self._topic}/state/moodlight", moodlight_message_received + await async_subscribe( + self.hass, f"{self._topic}/state/moodlight", moodlight_message_received ) ) - await self.hass.components.mqtt.async_publish( + await async_publish( self.hass, f"{self._topic}/command", "moodlight", qos=0, retain=False ) @@ -478,7 +481,7 @@ async def refresh(self): new_state["brightness"] = self._brightness _LOGGER.debug("refresh(%s) moodlight - %s", self.name, new_state) - await self.hass.components.mqtt.async_publish( + await async_publish( self.hass, cmd_topic, f"moodlight {json.dumps(new_state)}", diff --git a/custom_components/openhasp/number.py b/custom_components/openhasp/number.py index e3632cd..8f5a5f1 100644 --- a/custom_components/openhasp/number.py +++ b/custom_components/openhasp/number.py @@ -2,6 +2,7 @@ from dataclasses import dataclass import logging +from homeassistant.components.mqtt import async_publish, async_subscribe from homeassistant.components.number import NumberEntity, NumberEntityDescription from homeassistant.config_entries import ConfigEntry from homeassistant.const import CONF_NAME @@ -74,7 +75,7 @@ def __init__(self, name, hwid, topic, description) -> None: async def refresh(self): """Sync local state back to plate.""" - await self.hass.components.mqtt.async_publish( + await async_publish( self.hass, f"{self._topic}{self.entity_description.command_topic}", "" if self._number is None else self._number, @@ -99,7 +100,8 @@ async def page_state_message_received(msg): self.async_write_ha_state() self._subscriptions.append( - await self.hass.components.mqtt.async_subscribe( + await async_subscribe( + self.hass, f"{self._topic}{self.entity_description.state_topic}", page_state_message_received, ) diff --git a/custom_components/openhasp/switch.py b/custom_components/openhasp/switch.py index 7e64905..49d93fc 100644 --- a/custom_components/openhasp/switch.py +++ b/custom_components/openhasp/switch.py @@ -4,6 +4,7 @@ from typing import Callable # pylint: disable=R0801 +from homeassistant.components.mqtt import async_publish, async_subscribe from homeassistant.config_entries import ConfigEntry from homeassistant.const import CONF_NAME, STATE_OFF, STATE_ON from homeassistant.core import HomeAssistant, callback @@ -66,7 +67,7 @@ async def refresh(self): # Don't do anything before we know the state return - await self.hass.components.mqtt.async_publish( + await async_publish( self.hass, f"{self._topic}/command/output{self._gpio}", json.dumps(HASP_RELAY_SCHEMA({"state": int(self._state)})), @@ -95,12 +96,14 @@ async def relay_state_message_received(msg): _LOGGER.error(err) self._subscriptions.append( - await self.hass.components.mqtt.async_subscribe( - f"{self._topic}/state/output{self._gpio}", relay_state_message_received + await async_subscribe( + self.hass, + f"{self._topic}/state/output{self._gpio}", + relay_state_message_received, ) ) - await self.hass.components.mqtt.async_publish( + await async_publish( self.hass, f"{self._topic}/command/output{self._gpio}", "", @@ -122,7 +125,7 @@ def __init__(self, name, hwid, topic): async def refresh(self): """Sync local state back to plate.""" - await self.hass.components.mqtt.async_publish( + await async_publish( self.hass, f"{self._topic}/command/antiburn", int(self._state), @@ -152,15 +155,17 @@ async def antiburn_state_message_received(msg): _LOGGER.error(err) self._subscriptions.append( - await self.hass.components.mqtt.async_subscribe( - f"{self._topic}/state/antiburn", antiburn_state_message_received + await async_subscribe( + self.hass, + f"{self._topic}/state/antiburn", + antiburn_state_message_received, ) ) self._state = False self._available = True - await self.hass.components.mqtt.async_publish( + await async_publish( self.hass, f"{self._topic}/command/antiburn", int(self._state), From 24f071cb510f3da0e5c801ccd52eb5abad9c4d6b Mon Sep 17 00:00:00 2001 From: Diogo Gomes Date: Mon, 13 May 2024 23:41:21 +0100 Subject: [PATCH 26/58] move file open to executor job --- custom_components/openhasp/__init__.py | 35 ++++++++++++++------------ 1 file changed, 19 insertions(+), 16 deletions(-) diff --git a/custom_components/openhasp/__init__.py b/custom_components/openhasp/__init__.py index 55aa375..d28b135 100644 --- a/custom_components/openhasp/__init__.py +++ b/custom_components/openhasp/__init__.py @@ -344,15 +344,15 @@ def __init__(self, hass, config, entry): self._subscriptions = [] - with open( - pathlib.Path(__file__).parent.joinpath("pages_schema.json"), "r" - ) as schema_file: - self.json_schema = json.load(schema_file) - self._attr_unique_id = entry.data[CONF_HWID] self._attr_name = entry.data[CONF_NAME] self._attr_icon = "mdi:gesture-tap-box" + def _read_file(self, path): + """Executor helper to read file.""" + with open(path, "r") as src_file: + return src_file.read() + @property def state(self): """Return the state of the component.""" @@ -377,6 +377,9 @@ async def async_added_to_hass(self): """Run when entity about to be added.""" await super().async_added_to_hass() + schema_file_contents = await self.hass.async_add_executor_job(self._read_file, pathlib.Path(__file__).parent.joinpath("pages_schema.json")) + self.json_schema = json.loads(schema_file_contents) + state = await self.async_get_last_state() if state and state.state not in [STATE_UNAVAILABLE, STATE_UNKNOWN, None]: self._page = int(state.state) @@ -650,17 +653,17 @@ async def send_lines(lines): ) try: - with open(path, "r") as pages_file: - if path.endswith(".json"): - json_data = json.load(pages_file) - jsonschema.validate(instance=json_data, schema=self.json_schema) - lines = [] - for item in json_data: - if isinstance(item, dict): - lines.append(json.dumps(item) + "\n") - await send_lines(lines) - else: - await send_lines(pages_file) + pages_file = await self.hass.async_add_executor_job(self._read_file, path) + if path.endswith(".json"): + json_data = json.load(pages_file) + jsonschema.validate(instance=json_data, schema=self.json_schema) + lines = [] + for item in json_data: + if isinstance(item, dict): + lines.append(json.dumps(item) + "\n") + await send_lines(lines) + else: + await send_lines(pages_file) await self.refresh() except (IndexError, FileNotFoundError, IsADirectoryError, UnboundLocalError): From d7ec526c051411ac69932cdf208d5cd97c64d607 Mon Sep 17 00:00:00 2001 From: dev-docker Date: Tue, 8 Aug 2023 22:07:02 +0200 Subject: [PATCH 27/58] feat: allow full script syntax in event section This upgrades the current "list of services" restriction to allow the full syntax of HA scripts. fixes #65 As a side-effect this also requires to generate a context for the script to run. see #96 This can also be seen as a preparatory step to allow defining HASPObject-wide variables. see #63 --- custom_components/openhasp/__init__.py | 23 +++++++++++------------ 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/custom_components/openhasp/__init__.py b/custom_components/openhasp/__init__.py index 55aa375..ecededb 100644 --- a/custom_components/openhasp/__init__.py +++ b/custom_components/openhasp/__init__.py @@ -13,7 +13,7 @@ from homeassistant.components.number import DOMAIN as NUMBER_DOMAIN from homeassistant.components.switch import DOMAIN as SWITCH_DOMAIN from homeassistant.const import CONF_NAME, STATE_UNAVAILABLE, STATE_UNKNOWN -from homeassistant.core import callback +from homeassistant.core import callback, Context from homeassistant.exceptions import TemplateError from homeassistant.helpers import device_registry as dr, entity_registry import homeassistant.helpers.config_validation as cv @@ -22,7 +22,7 @@ from homeassistant.helpers.network import get_url from homeassistant.helpers.reload import async_integration_yaml_config from homeassistant.helpers.restore_state import RestoreEntity -from homeassistant.helpers.service import async_call_from_config +from homeassistant.helpers.script import Script from homeassistant.util import slugify import jsonschema import voluptuous as vol @@ -103,7 +103,7 @@ def hasp_object(value): # Configuration YAML schemas -EVENT_SCHEMA = cv.schema_with_slug_keys([cv.SERVICE_SCHEMA]) +EVENT_SCHEMA = cv.schema_with_slug_keys(cv.SCRIPT_SCHEMA) PROPERTY_SCHEMA = cv.schema_with_slug_keys(cv.template) @@ -697,7 +697,9 @@ def __init__(self, hass, plate_topic, config): self.cached_properties = {} self.properties = config.get(CONF_PROPERTIES) - self.event_services = config.get(CONF_EVENT) + self.event_services = { + event:Script(hass, script, plate_topic, DOMAIN) + for (event,script) in config[CONF_EVENT].items() } self._tracked_property_templates = [] self._freeze_properties = [] self._subscriptions = [] @@ -791,7 +793,7 @@ async def message_received(msg): elif message[HASP_EVENT] in [HASP_EVENT_UP, HASP_EVENT_RELEASE]: self._freeze_properties = [] - for event in self.event_services: + for (event,script) in self.event_services.items(): if event in message[HASP_EVENT]: _LOGGER.debug( "Service call for '%s' triggered by '%s' on '%s' with variables %s", @@ -800,13 +802,10 @@ async def message_received(msg): msg.topic, message, ) - for service in self.event_services[event]: - await async_call_from_config( - self.hass, - service, - validate_config=False, - variables=message, - ) + await script.async_run( + run_variables=message, + context=Context(), + ) except vol.error.Invalid: _LOGGER.debug( "Could not handle openHASP event: '%s' on '%s'", From dc719919facd91e04f527d5bde027177e06e40a3 Mon Sep 17 00:00:00 2001 From: dev-docker Date: Tue, 8 Aug 2023 22:38:35 +0200 Subject: [PATCH 28/58] chore: apply black style I just read the contributing guidelines... --- custom_components/openhasp/__init__.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/custom_components/openhasp/__init__.py b/custom_components/openhasp/__init__.py index ecededb..a74cc65 100644 --- a/custom_components/openhasp/__init__.py +++ b/custom_components/openhasp/__init__.py @@ -698,8 +698,9 @@ def __init__(self, hass, plate_topic, config): self.properties = config.get(CONF_PROPERTIES) self.event_services = { - event:Script(hass, script, plate_topic, DOMAIN) - for (event,script) in config[CONF_EVENT].items() } + event: Script(hass, script, plate_topic, DOMAIN) + for (event, script) in config[CONF_EVENT].items() + } self._tracked_property_templates = [] self._freeze_properties = [] self._subscriptions = [] @@ -793,7 +794,7 @@ async def message_received(msg): elif message[HASP_EVENT] in [HASP_EVENT_UP, HASP_EVENT_RELEASE]: self._freeze_properties = [] - for (event,script) in self.event_services.items(): + for event, script in self.event_services.items(): if event in message[HASP_EVENT]: _LOGGER.debug( "Service call for '%s' triggered by '%s' on '%s' with variables %s", From b7bd8254dac9316b13a3a445b63561e037a6963e Mon Sep 17 00:00:00 2001 From: illuzn <57167030+illuzn@users.noreply.github.com> Date: Fri, 12 Jul 2024 13:35:38 +0930 Subject: [PATCH 29/58] Fixes #131 Fixes #131 --- custom_components/openhasp/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/custom_components/openhasp/__init__.py b/custom_components/openhasp/__init__.py index 94e4541..9a62d80 100644 --- a/custom_components/openhasp/__init__.py +++ b/custom_components/openhasp/__init__.py @@ -655,7 +655,7 @@ async def send_lines(lines): try: pages_file = await self.hass.async_add_executor_job(self._read_file, path) if path.endswith(".json"): - json_data = json.load(pages_file) + json_data = json.loads(pages_file) jsonschema.validate(instance=json_data, schema=self.json_schema) lines = [] for item in json_data: From 276c4cdd4d7d9205af2a747f9c803b705ec1376e Mon Sep 17 00:00:00 2001 From: "Lars R." <5354495+TNTLarsn@users.noreply.github.com> Date: Tue, 6 Aug 2024 08:25:39 +0200 Subject: [PATCH 30/58] Replace deprecated async_forward_entry_setup call Calling hass.config_entries.async_forward_entry_setup is deprecated and will be removed in Home Assistant 2025.6. Instead, await hass.config_entries.async_forward_entry_setups as it can load multiple platforms at once and is more efficient since it does not require a separate import executor job for each platform. https://developers.home-assistant.io/blog/2024/06/12/async_forward_entry_setups/ --- custom_components/openhasp/__init__.py | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/custom_components/openhasp/__init__.py b/custom_components/openhasp/__init__.py index 9a62d80..da017c0 100644 --- a/custom_components/openhasp/__init__.py +++ b/custom_components/openhasp/__init__.py @@ -264,11 +264,8 @@ async def async_setup_entry(hass, entry) -> bool: await component.async_add_entities([plate_entity]) hass.data[DOMAIN][CONF_PLATE][plate] = plate_entity - for domain in PLATFORMS: - hass.async_create_task( - hass.config_entries.async_forward_entry_setup(entry, domain) - ) - + await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS) + listener = entry.add_update_listener(async_update_options) entry.async_on_unload(listener) From f8002375b7fc1f2864c9ac541f63b125f9f78887 Mon Sep 17 00:00:00 2001 From: dev Date: Tue, 6 Aug 2024 21:53:49 +0200 Subject: [PATCH 31/58] fix: properly split jsonl upload at lineends --- custom_components/openhasp/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/custom_components/openhasp/__init__.py b/custom_components/openhasp/__init__.py index da017c0..9d6afeb 100644 --- a/custom_components/openhasp/__init__.py +++ b/custom_components/openhasp/__init__.py @@ -660,7 +660,7 @@ async def send_lines(lines): lines.append(json.dumps(item) + "\n") await send_lines(lines) else: - await send_lines(pages_file) + await send_lines(pages_file.splitlines(keepends=True)) await self.refresh() except (IndexError, FileNotFoundError, IsADirectoryError, UnboundLocalError): From 4fdc6a07012e35873d56ff4d37731af040b8bdce Mon Sep 17 00:00:00 2001 From: "[NUT]" Date: Thu, 8 Aug 2024 13:38:14 +0200 Subject: [PATCH 32/58] page or num_pages can contain a string as a result, automatic updating by the component throws an error and does not work. --- custom_components/openhasp/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/custom_components/openhasp/__init__.py b/custom_components/openhasp/__init__.py index 4bc2a2d..3ca7bdc 100644 --- a/custom_components/openhasp/__init__.py +++ b/custom_components/openhasp/__init__.py @@ -556,7 +556,7 @@ async def async_change_page(self, page): if self._statusupdate: num_pages = self._statusupdate[HASP_NUM_PAGES] - if page <= 0 or page > num_pages: + if page <= 0 or int(page) > int(num_pages): _LOGGER.error( "Can't change to %s, available pages are 1 to %s", page, num_pages ) From 1f00a56b591150c27a1b74c60f64e91ee4630ef6 Mon Sep 17 00:00:00 2001 From: xNUTx Date: Thu, 8 Aug 2024 16:05:34 +0200 Subject: [PATCH 33/58] not a file, so check for it removed and comparison syntax fixed --- custom_components/openhasp/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/custom_components/openhasp/__init__.py b/custom_components/openhasp/__init__.py index 3ca7bdc..727413a 100644 --- a/custom_components/openhasp/__init__.py +++ b/custom_components/openhasp/__init__.py @@ -154,7 +154,7 @@ def hasp_object(value): { vol.Required(ATTR_IMAGE): vol.Any(cv.url, cv.isfile), vol.Required(ATTR_OBJECT): hasp_object, - vol.Optional(ATTR_PROXY): vol.Any(cv.url, cv.isfile), + vol.Optional(ATTR_PROXY): vol.Any(cv.url), vol.Optional(ATTR_WIDTH): cv.positive_int, vol.Optional(ATTR_HEIGHT): cv.positive_int, vol.Optional(ATTR_FORCE_FITSCREEN): cv.boolean, @@ -603,7 +603,7 @@ async def async_push_image( cmd_topic = f"{self._topic}/command/{obj}.src" - if http_proxy == "None": + if http_proxy == None: rgb_image_url = ( f"{get_url(self.hass, allow_external=False)}/api/openhasp/serve/{image_id}" ) From 6fa12553a09e2a1e1728b179e7cd747ac93ef23c Mon Sep 17 00:00:00 2001 From: "[NUT]" Date: Thu, 8 Aug 2024 16:25:02 +0200 Subject: [PATCH 34/58] Update custom_components/openhasp/services.yaml Co-authored-by: Diogo Gomes --- custom_components/openhasp/services.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/custom_components/openhasp/services.yaml b/custom_components/openhasp/services.yaml index 9643b4c..518056e 100644 --- a/custom_components/openhasp/services.yaml +++ b/custom_components/openhasp/services.yaml @@ -108,7 +108,7 @@ push_image: text: http_proxy: name: HTTP (Reverse) Proxy - description: Proxy address to use. This can be used to allow HTTP access to an otherwise SSL secured HA instance. Offering the proxy functionality is out of the scope of this integraiton. + description: Proxy address to use. This can be used to allow HTTP access to an otherwise SSL secured HA instance. Offering the proxy functionality is out of the scope of this integration. required: false example: "http://people.sc.fsu.edu:port" selector: From ad6d7539f83512abc3945a70d35feb3e366bb98c Mon Sep 17 00:00:00 2001 From: xNUTx Date: Thu, 8 Aug 2024 16:42:45 +0200 Subject: [PATCH 35/58] if statement improved as requested --- custom_components/openhasp/__init__.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/custom_components/openhasp/__init__.py b/custom_components/openhasp/__init__.py index 727413a..e9411da 100644 --- a/custom_components/openhasp/__init__.py +++ b/custom_components/openhasp/__init__.py @@ -603,13 +603,13 @@ async def async_push_image( cmd_topic = f"{self._topic}/command/{obj}.src" - if http_proxy == None: + if http_proxy: rgb_image_url = ( - f"{get_url(self.hass, allow_external=False)}/api/openhasp/serve/{image_id}" + f"{http_proxy}/api/openhasp/serve/{image_id}" ) else: rgb_image_url = ( - f"{http_proxy}/api/openhasp/serve/{image_id}" + f"{get_url(self.hass, allow_external=False)}/api/openhasp/serve/{image_id}" ) #self._entry.data _LOGGER.debug("Push %s with %s", cmd_topic, rgb_image_url) From f25c3d235e0430e8f1b728270bfc2c5d1b878eec Mon Sep 17 00:00:00 2001 From: xNUTx Date: Thu, 8 Aug 2024 16:47:37 +0200 Subject: [PATCH 36/58] if statement reverted to include in a different PR --- custom_components/openhasp/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/custom_components/openhasp/__init__.py b/custom_components/openhasp/__init__.py index e9411da..33e6580 100644 --- a/custom_components/openhasp/__init__.py +++ b/custom_components/openhasp/__init__.py @@ -556,7 +556,7 @@ async def async_change_page(self, page): if self._statusupdate: num_pages = self._statusupdate[HASP_NUM_PAGES] - if page <= 0 or int(page) > int(num_pages): + if page <= 0 or page > num_pages: _LOGGER.error( "Can't change to %s, available pages are 1 to %s", page, num_pages ) From 86bca7ecd77ccb163084d66dc2724a34b23bdb40 Mon Sep 17 00:00:00 2001 From: xNUTx Date: Thu, 8 Aug 2024 17:04:44 +0200 Subject: [PATCH 37/58] the display stops or refuses to dynamically update and throws an error in the logs because (most likely) num_pages contains a string, while page contains an int, resulting in a python type error --- custom_components/openhasp/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/custom_components/openhasp/__init__.py b/custom_components/openhasp/__init__.py index 9d6afeb..5a1d313 100644 --- a/custom_components/openhasp/__init__.py +++ b/custom_components/openhasp/__init__.py @@ -554,7 +554,7 @@ async def async_change_page(self, page): if self._statusupdate: num_pages = self._statusupdate[HASP_NUM_PAGES] - if page <= 0 or page > num_pages: + if page <= 0 or int(page) > int(num_pages): _LOGGER.error( "Can't change to %s, available pages are 1 to %s", page, num_pages ) From 97b88e524aa206f58c3df8528346262cca3a8ba9 Mon Sep 17 00:00:00 2001 From: xNUTx Date: Wed, 14 Aug 2024 16:40:18 +0200 Subject: [PATCH 38/58] When using the same image source more then once, using a md5 hash of the image itself will cause the same image to be overwritten time and time again if you redo the resize for another screen... resulting in an ever changing image size. This change undoes that and the new ImageOps feature will speed up the image processing compared to the orignal without sacrificing the quality too much. --- custom_components/openhasp/__init__.py | 2 +- custom_components/openhasp/image.py | 6 ++++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/custom_components/openhasp/__init__.py b/custom_components/openhasp/__init__.py index 9d6afeb..56ac55c 100644 --- a/custom_components/openhasp/__init__.py +++ b/custom_components/openhasp/__init__.py @@ -591,7 +591,7 @@ async def async_push_image( ): """Update object image.""" - image_id = hashlib.md5(image.encode("utf-8")).hexdigest() + image_id = hashlib.md5(image.encode("utf-8") + self._entry.data[CONF_NAME].encode('utf-8')).hexdigest() rgb_image = await self.hass.async_add_executor_job( image_to_rgb565, image, (width, height), fitscreen diff --git a/custom_components/openhasp/image.py b/custom_components/openhasp/image.py index 7a1b904..49399d0 100644 --- a/custom_components/openhasp/image.py +++ b/custom_components/openhasp/image.py @@ -4,7 +4,7 @@ import struct import tempfile -from PIL import Image +from PIL import Image, ImageOps from aiohttp import hdrs, web from homeassistant.components.http.static import CACHE_HEADERS from homeassistant.components.http.view import HomeAssistantView @@ -34,7 +34,9 @@ def image_to_rgb565(in_image, size, fitscreen): height = min(h for h in [height, original_height] if h is not None and h > 0) im.thumbnail((width, height), Image.LANCZOS) else: - im = im.resize((width, height), Image.LANCZOS) + im = ImageOps.fit(im, (width, height), method = 3, + bleed = 0.0, centering =(0.5, 0.5)) + width, height = im.size # actual size after resize out_image = tempfile.NamedTemporaryFile(mode="w+b") From 95eb55d6c6c18046a29600d7ad524c844677acb5 Mon Sep 17 00:00:00 2001 From: xNUTx Date: Thu, 15 Aug 2024 11:17:00 +0200 Subject: [PATCH 39/58] Checking for int type to correctly validate the page variable content --- custom_components/openhasp/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/custom_components/openhasp/__init__.py b/custom_components/openhasp/__init__.py index 5a1d313..033125f 100644 --- a/custom_components/openhasp/__init__.py +++ b/custom_components/openhasp/__init__.py @@ -554,7 +554,7 @@ async def async_change_page(self, page): if self._statusupdate: num_pages = self._statusupdate[HASP_NUM_PAGES] - if page <= 0 or int(page) > int(num_pages): + if isinstance(page, int) and (page <= 0 or page > num_pages): _LOGGER.error( "Can't change to %s, available pages are 1 to %s", page, num_pages ) From 9f3cd3082f2f573b8cb158a3c983149ae1826b5f Mon Sep 17 00:00:00 2001 From: xNUTx Date: Mon, 19 Aug 2024 09:38:08 +0200 Subject: [PATCH 40/58] As requested, just the var instead. --- custom_components/openhasp/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/custom_components/openhasp/__init__.py b/custom_components/openhasp/__init__.py index ea0ea5d..4df2881 100644 --- a/custom_components/openhasp/__init__.py +++ b/custom_components/openhasp/__init__.py @@ -154,7 +154,7 @@ def hasp_object(value): { vol.Required(ATTR_IMAGE): vol.Any(cv.url, cv.isfile), vol.Required(ATTR_OBJECT): hasp_object, - vol.Optional(ATTR_PROXY): vol.Any(cv.url), + vol.Optional(ATTR_PROXY): cv.url, vol.Optional(ATTR_WIDTH): cv.positive_int, vol.Optional(ATTR_HEIGHT): cv.positive_int, vol.Optional(ATTR_FORCE_FITSCREEN): cv.boolean, From 2960137721cc04d5a6d4ff8c1201827f4a718494 Mon Sep 17 00:00:00 2001 From: fvanroie <15969459+fvanroie@users.noreply.github.com> Date: Mon, 19 Aug 2024 18:55:55 +0200 Subject: [PATCH 41/58] Update manifest.json #145 Update manifest version --- custom_components/openhasp/manifest.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/custom_components/openhasp/manifest.json b/custom_components/openhasp/manifest.json index 42f8ae4..aa437ad 100644 --- a/custom_components/openhasp/manifest.json +++ b/custom_components/openhasp/manifest.json @@ -9,6 +9,6 @@ "issue_tracker": "https://github.com/HASwitchPlate/openHASP-custom-component/issues", "mqtt": ["hasp/discovery/#"], "requirements": ["jsonschema>=3.2.0"], - "version": "0.7.2", + "version": "0.7.5", "zeroconf": ["_openhasp._tcp.local."] } From 512f05a438836cbfe800f54205e33dd7b03c2053 Mon Sep 17 00:00:00 2001 From: Diogo Gomes Date: Thu, 29 Aug 2024 21:39:56 +0100 Subject: [PATCH 42/58] fix for 2024.9 --- custom_components/openhasp/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/custom_components/openhasp/__init__.py b/custom_components/openhasp/__init__.py index 088ca60..86d2b11 100644 --- a/custom_components/openhasp/__init__.py +++ b/custom_components/openhasp/__init__.py @@ -149,7 +149,7 @@ def hasp_object(value): HASP_PAGE_SCHEMA = vol.Schema(vol.All(vol.Coerce(int), vol.Range(min=0, max=12))) -PUSH_IMAGE_SCHEMA = vol.Schema( +PUSH_IMAGE_SCHEMA = cv.make_entity_service_schema( { vol.Required(ATTR_IMAGE): vol.Any(cv.url, cv.isfile), vol.Required(ATTR_OBJECT): hasp_object, From 906749d728b31fdaed21505801b6df87923a623b Mon Sep 17 00:00:00 2001 From: fvanroie <15969459+fvanroie@users.noreply.github.com> Date: Wed, 4 Sep 2024 22:36:33 +0200 Subject: [PATCH 43/58] Update manifest.json --- custom_components/openhasp/manifest.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/custom_components/openhasp/manifest.json b/custom_components/openhasp/manifest.json index aa437ad..77a71e7 100644 --- a/custom_components/openhasp/manifest.json +++ b/custom_components/openhasp/manifest.json @@ -9,6 +9,6 @@ "issue_tracker": "https://github.com/HASwitchPlate/openHASP-custom-component/issues", "mqtt": ["hasp/discovery/#"], "requirements": ["jsonschema>=3.2.0"], - "version": "0.7.5", + "version": "0.7.6", "zeroconf": ["_openhasp._tcp.local."] } From 5de3dac172a0d1abb65e152e5c3bb3bd4b13549c Mon Sep 17 00:00:00 2001 From: xNUTx Date: Thu, 5 Sep 2024 20:47:41 +0200 Subject: [PATCH 44/58] PR #140 seems to be a partial fix, so revisit to fix it again. --- custom_components/openhasp/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/custom_components/openhasp/__init__.py b/custom_components/openhasp/__init__.py index 91e7f78..75213eb 100644 --- a/custom_components/openhasp/__init__.py +++ b/custom_components/openhasp/__init__.py @@ -556,7 +556,7 @@ async def async_change_page(self, page): if self._statusupdate: num_pages = self._statusupdate[HASP_NUM_PAGES] - if isinstance(page, int) and (page <= 0 or page > num_pages): + if isinstance(page, int) and isinstance(num_pages, int) and (page <= 0 or page > num_pages): _LOGGER.error( "Can't change to %s, available pages are 1 to %s", page, num_pages ) From d2c9afd3275ddc3b57b5cb6e4852f2f92d824afa Mon Sep 17 00:00:00 2001 From: xNUTx Date: Fri, 6 Sep 2024 10:40:36 +0200 Subject: [PATCH 45/58] Made sure the component setup is not done until MQTT is fully configured and started. This should also fix https://github.com/HASwitchPlate/openHASP-custom-component/issues/133 but needs testing by someone who had the MQTT error (I did not, I actually never have). --- custom_components/openhasp/__init__.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/custom_components/openhasp/__init__.py b/custom_components/openhasp/__init__.py index 75213eb..07af04b 100644 --- a/custom_components/openhasp/__init__.py +++ b/custom_components/openhasp/__init__.py @@ -7,6 +7,7 @@ import re from homeassistant.components.mqtt import async_subscribe, async_publish +import homeassistant.components.mqtt as mqtt from homeassistant.components.binary_sensor import DOMAIN as BINARY_SENSOR_DOMAIN from homeassistant.components.button import DOMAIN as BUTTON_DOMAIN from homeassistant.components.light import DOMAIN as LIGHT_DOMAIN @@ -164,6 +165,9 @@ def hasp_object(value): async def async_setup(hass, config): + """Wait for MQTT to become available before starting.""" + await mqtt.async_wait_for_mqtt_client(hass) + """Set up the MQTT async example component.""" conf = config.get(DOMAIN) From f84cd3d849b123a9e46b91d4e0d4efa342c49187 Mon Sep 17 00:00:00 2001 From: fvanroie <15969459+fvanroie@users.noreply.github.com> Date: Fri, 6 Sep 2024 19:39:01 +0200 Subject: [PATCH 46/58] Update manifest.json --- custom_components/openhasp/manifest.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/custom_components/openhasp/manifest.json b/custom_components/openhasp/manifest.json index 77a71e7..a41e8c0 100644 --- a/custom_components/openhasp/manifest.json +++ b/custom_components/openhasp/manifest.json @@ -9,6 +9,6 @@ "issue_tracker": "https://github.com/HASwitchPlate/openHASP-custom-component/issues", "mqtt": ["hasp/discovery/#"], "requirements": ["jsonschema>=3.2.0"], - "version": "0.7.6", + "version": "0.7.7", "zeroconf": ["_openhasp._tcp.local."] } From b2df3dc05c2ace57551141a8277137c63dee6397 Mon Sep 17 00:00:00 2001 From: fvanroie <15969459+fvanroie@users.noreply.github.com> Date: Fri, 6 Sep 2024 19:44:25 +0200 Subject: [PATCH 47/58] Update RELEASE.md --- RELEASE.md | 34 ++++++++++++++++++++++++++++++++-- 1 file changed, 32 insertions(+), 2 deletions(-) diff --git a/RELEASE.md b/RELEASE.md index f777d03..acb93a6 100644 --- a/RELEASE.md +++ b/RELEASE.md @@ -1,10 +1,40 @@ -# 0.6.6 +# 0.7.6 +* Fix: MQTTt not being ready at startup. by @xNUTx in https://github.com/HASwitchPlate/openHASP-custom-component/pull/149 + +# 0.7.5 +* Support for HA 2024.9 by @dgomes +* fix: Reuse of the same image with different sized displays causes a random resize. by @xNUTx in https://github.com/HASwitchPlate/openHASP-custom-component/pull/143 +* fix: Dynamic reloading broken on recently added displays. by @xNUTx in https://github.com/HASwitchPlate/openHASP-custom-component/pull/140 +* feat: Added optional setting http_proxy to push_image by @xNUTx in https://github.com/HASwitchPlate/openHASP-custom-component/pull/144 + +# 0.7.4 +* Fixes 'str' object has no attribute 'read' by @illuzn in https://github.com/HASwitchPlate/openHASP-custom-component/pull/132 +* Replace deprecated async_forward_entry_setup call by @TNTLarsn in https://github.com/HASwitchPlate/openHASP-custom-component/pull/137 +* fix: properly split jsonl upload at lineends by @akloeckner in https://github.com/HASwitchPlate/openHASP-custom-component/pull/138 + +# 0.7.3 +- Support for 2024.6.0 +- Fixed height & width were being transposed when fitscreen=true by @FreeBear-nc in https://github.com/HASwitchPlate/openHASP-custom-component/pull/121 +- Move file open() to executor job by @dgomes in https://github.com/HASwitchPlate/openHASP-custom-component/pull/123 +- feat: allow full script syntax in event section by @akloeckner in https://github.com/HASwitchPlate/openHASP-custom-component/pull/112 +# 0.7.2 +- Support discovery through mDNS (0.7.0-rc11 or higher) +- Support for HA 2024.1 +- Replace version popup for legacy 0.6.x plates with log warning + +# 0.7.1 +- Fix error as `ANTIALIAS` was removed in Pillow 10.0.0. Now using `LANCZOS` instead. +- Updated Manifest.json + +# 0.7.0 +- Better handling of discovery for 0.7.0-dev firmware +- +# 0.6.6 - Support for 2022.7.0 - Code improvements # 0.6.5 - - Support for 2022.4.0 - Adds page number entity - Adds restart button From 65639481c5aa3bf88a1100dee5dbf4d7f5ece4b5 Mon Sep 17 00:00:00 2001 From: fvanroie <15969459+fvanroie@users.noreply.github.com> Date: Fri, 6 Sep 2024 19:44:40 +0200 Subject: [PATCH 48/58] Update RELEASE.md --- RELEASE.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/RELEASE.md b/RELEASE.md index acb93a6..f9b8b3e 100644 --- a/RELEASE.md +++ b/RELEASE.md @@ -29,7 +29,7 @@ # 0.7.0 - Better handling of discovery for 0.7.0-dev firmware -- + # 0.6.6 - Support for 2022.7.0 - Code improvements From 4218153e6d2bdfa31e444a44412be38a17e956f1 Mon Sep 17 00:00:00 2001 From: Diogo Gomes Date: Fri, 20 Sep 2024 15:43:48 +0100 Subject: [PATCH 49/58] Add information on plate MAC address and reformats code --- custom_components/openhasp/__init__.py | 33 ++++++++++++++++---------- custom_components/openhasp/image.py | 5 ++-- 2 files changed, 24 insertions(+), 14 deletions(-) diff --git a/custom_components/openhasp/__init__.py b/custom_components/openhasp/__init__.py index 07af04b..b074078 100644 --- a/custom_components/openhasp/__init__.py +++ b/custom_components/openhasp/__init__.py @@ -6,6 +6,10 @@ import pathlib import re +from homeassistant.helpers.device_registry import ( + CONNECTION_NETWORK_MAC, + format_mac, +) from homeassistant.components.mqtt import async_subscribe, async_publish import homeassistant.components.mqtt as mqtt from homeassistant.components.binary_sensor import DOMAIN as BINARY_SENSOR_DOMAIN @@ -151,7 +155,7 @@ def hasp_object(value): HASP_PAGE_SCHEMA = vol.Schema(vol.All(vol.Coerce(int), vol.Range(min=0, max=12))) -PUSH_IMAGE_SCHEMA = cv.make_entity_service_schema( +PUSH_IMAGE_SCHEMA = cv.make_entity_service_schema( { vol.Required(ATTR_IMAGE): vol.Any(cv.url, cv.isfile), vol.Required(ATTR_OBJECT): hasp_object, @@ -262,6 +266,7 @@ async def async_setup_entry(hass, entry) -> bool: sw_version=entry.data[DISCOVERED_VERSION], configuration_url=entry.data.get(DISCOVERED_URL), name=plate, + connections={(CONNECTION_NETWORK_MAC, format_mac(entry.data[CONF_HWID]))}, ) # Add entity to component @@ -271,7 +276,7 @@ async def async_setup_entry(hass, entry) -> bool: hass.data[DOMAIN][CONF_PLATE][plate] = plate_entity await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS) - + listener = entry.add_update_listener(async_update_options) entry.async_on_unload(listener) @@ -380,7 +385,9 @@ async def async_added_to_hass(self): """Run when entity about to be added.""" await super().async_added_to_hass() - schema_file_contents = await self.hass.async_add_executor_job(self._read_file, pathlib.Path(__file__).parent.joinpath("pages_schema.json")) + schema_file_contents = await self.hass.async_add_executor_job( + self._read_file, pathlib.Path(__file__).parent.joinpath("pages_schema.json") + ) self.json_schema = json.loads(schema_file_contents) state = await self.async_get_last_state() @@ -560,7 +567,11 @@ async def async_change_page(self, page): if self._statusupdate: num_pages = self._statusupdate[HASP_NUM_PAGES] - if isinstance(page, int) and isinstance(num_pages, int) and (page <= 0 or page > num_pages): + if ( + isinstance(page, int) + and isinstance(num_pages, int) + and (page <= 0 or page > num_pages) + ): _LOGGER.error( "Can't change to %s, available pages are 1 to %s", page, num_pages ) @@ -597,7 +608,9 @@ async def async_push_image( ): """Update object image.""" - image_id = hashlib.md5(image.encode("utf-8") + self._entry.data[CONF_NAME].encode('utf-8')).hexdigest() + image_id = hashlib.md5( + image.encode("utf-8") + self._entry.data[CONF_NAME].encode("utf-8") + ).hexdigest() rgb_image = await self.hass.async_add_executor_job( image_to_rgb565, image, (width, height), fitscreen @@ -608,14 +621,10 @@ async def async_push_image( cmd_topic = f"{self._topic}/command/{obj}.src" if http_proxy: - rgb_image_url = ( - f"{http_proxy}/api/openhasp/serve/{image_id}" - ) + rgb_image_url = f"{http_proxy}/api/openhasp/serve/{image_id}" else: - rgb_image_url = ( - f"{get_url(self.hass, allow_external=False)}/api/openhasp/serve/{image_id}" - ) -#self._entry.data + rgb_image_url = f"{get_url(self.hass, allow_external=False)}/api/openhasp/serve/{image_id}" + # self._entry.data _LOGGER.debug("Push %s with %s", cmd_topic, rgb_image_url) await async_publish(self.hass, cmd_topic, rgb_image_url, qos=0, retain=False) diff --git a/custom_components/openhasp/image.py b/custom_components/openhasp/image.py index 49399d0..18a2d84 100644 --- a/custom_components/openhasp/image.py +++ b/custom_components/openhasp/image.py @@ -34,8 +34,9 @@ def image_to_rgb565(in_image, size, fitscreen): height = min(h for h in [height, original_height] if h is not None and h > 0) im.thumbnail((width, height), Image.LANCZOS) else: - im = ImageOps.fit(im, (width, height), method = 3, - bleed = 0.0, centering =(0.5, 0.5)) + im = ImageOps.fit( + im, (width, height), method=3, bleed=0.0, centering=(0.5, 0.5) + ) width, height = im.size # actual size after resize From 1029cfcc61608a1af62ad4d5447f764fbe982f76 Mon Sep 17 00:00:00 2001 From: Will <65567450+wjnelson78@users.noreply.github.com> Date: Tue, 11 Mar 2025 07:43:36 -0700 Subject: [PATCH 50/58] fix(openhasp): Use proper entity service schemas and switch to `async_forward_entry_setups` This commit addresses two deprecation warnings in the openHASP integration: 1. **Entity Service Schema** Home Assistant 2025.9 and onward no longer allows registering entity services with an empty `{}` schema. This commit adds minimal entity service schemas for `SERVICE_WAKEUP`, `SERVICE_PAGE_NEXT`, and `SERVICE_PAGE_PREV` by using `cv.make_entity_service_schema({})`. Additionally, the existing `PUSH_IMAGE` service is updated to use `cv.make_entity_service_schema(...)` to ensure compliance with future HA releases. 2. **async_forward_entry_setup Deprecation** Replaces multiple `await hass.config_entries.async_forward_entry_setup(entry, domain)` calls in a loop with a single `await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)`. This ensures the setup lock is handled correctly and removes the related deprecation warning. These changes make the custom component compatible with the upcoming breaking changes in Home Assistant 2025.x and beyond, preventing warnings and ensuring a smooth user experience. --- custom_components/openhasp/__init__.py | 31 ++++++++++---------------- 1 file changed, 12 insertions(+), 19 deletions(-) diff --git a/custom_components/openhasp/__init__.py b/custom_components/openhasp/__init__.py index b074078..d6b2c66 100644 --- a/custom_components/openhasp/__init__.py +++ b/custom_components/openhasp/__init__.py @@ -184,26 +184,19 @@ async def async_setup(hass, config): hass.data[DOMAIN] = {CONF_PLATE: {}} - component = hass.data[DOMAIN][CONF_COMPONENT] = EntityComponent( - _LOGGER, DOMAIN, hass - ) + component = hass.data[DOMAIN][CONF_COMPONENT] = EntityComponent(_LOGGER, DOMAIN, hass) + + WAKEUP_SERVICE_SCHEMA = cv.make_entity_service_schema({}) + PAGE_NEXT_SERVICE_SCHEMA = cv.make_entity_service_schema({}) + PAGE_PREV_SERVICE_SCHEMA = cv.make_entity_service_schema({}) + + component.async_register_entity_service(SERVICE_WAKEUP, WAKEUP_SCHEMA, "async_wakeup") + component.async_register_entity_service(SERVICE_PAGE_NEXT, PAGE_NEXT_SCHEMA, "async_change_page_next") + component.async_register_entity_service(SERVICE_PAGE_PREV, PAGE_PREV_SCHEMA, "async_change_page_prev") + component.async_register_entity_service(SERVICE_PAGE_CHANGE, {vol.Required(ATTR_PAGE): int}, "async_change_page") + component.async_register_entity_service(SERVICE_LOAD_PAGE, {vol.Required(ATTR_PATH): cv.isfile}, "async_load_page") + component.async_register_entity_service(SERVICE_CLEAR_PAGE, {vol.Optional(ATTR_PAGE): int}, "async_clearpage") - component.async_register_entity_service(SERVICE_WAKEUP, {}, "async_wakeup") - component.async_register_entity_service( - SERVICE_PAGE_NEXT, {}, "async_change_page_next" - ) - component.async_register_entity_service( - SERVICE_PAGE_PREV, {}, "async_change_page_prev" - ) - component.async_register_entity_service( - SERVICE_PAGE_CHANGE, {vol.Required(ATTR_PAGE): int}, "async_change_page" - ) - component.async_register_entity_service( - SERVICE_LOAD_PAGE, {vol.Required(ATTR_PATH): cv.isfile}, "async_load_page" - ) - component.async_register_entity_service( - SERVICE_CLEAR_PAGE, {vol.Optional(ATTR_PAGE): int}, "async_clearpage" - ) component.async_register_entity_service( SERVICE_COMMAND, { From 7c293899558d52596c23f072614ba6a0af832d18 Mon Sep 17 00:00:00 2001 From: fvanroie <15969459+fvanroie@users.noreply.github.com> Date: Thu, 9 Oct 2025 13:03:12 +0200 Subject: [PATCH 51/58] Update __init__.py Limit version parsing to major, minor and patch digits, other items are discarded. --- custom_components/openhasp/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/custom_components/openhasp/__init__.py b/custom_components/openhasp/__init__.py index b074078..54dde11 100644 --- a/custom_components/openhasp/__init__.py +++ b/custom_components/openhasp/__init__.py @@ -417,7 +417,7 @@ async def statusupdate_message_received(msg): try: message = HASP_STATUSUPDATE_SCHEMA(json.loads(msg.payload)) - major, minor, _ = message["version"].split(".") + major, minor, patch = message["version"].split(".")[:3] if (major, minor) != (MAJOR, MINOR): _LOGGER.warning( "%s firmware mismatch %s <> %s", From 2295ad8b67bfa121192e292b8918c4dd3518cf54 Mon Sep 17 00:00:00 2001 From: fvanroie <15969459+fvanroie@users.noreply.github.com> Date: Thu, 9 Oct 2025 13:15:56 +0200 Subject: [PATCH 52/58] Update manifest.json Bump version number --- custom_components/openhasp/manifest.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/custom_components/openhasp/manifest.json b/custom_components/openhasp/manifest.json index a41e8c0..f99d98e 100644 --- a/custom_components/openhasp/manifest.json +++ b/custom_components/openhasp/manifest.json @@ -9,6 +9,6 @@ "issue_tracker": "https://github.com/HASwitchPlate/openHASP-custom-component/issues", "mqtt": ["hasp/discovery/#"], "requirements": ["jsonschema>=3.2.0"], - "version": "0.7.7", + "version": "0.7.8", "zeroconf": ["_openhasp._tcp.local."] } From d82fa8b6867b5f94c7afaab817fb5ea981a1ceb5 Mon Sep 17 00:00:00 2001 From: fvanroie <15969459+fvanroie@users.noreply.github.com> Date: Thu, 9 Oct 2025 13:20:57 +0200 Subject: [PATCH 53/58] Update RELEASE.md --- RELEASE.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/RELEASE.md b/RELEASE.md index f9b8b3e..cea11fb 100644 --- a/RELEASE.md +++ b/RELEASE.md @@ -1,3 +1,6 @@ +# 0.7.7 +* Fix for firmware version number has more than three digits. by @fvanroie + # 0.7.6 * Fix: MQTTt not being ready at startup. by @xNUTx in https://github.com/HASwitchPlate/openHASP-custom-component/pull/149 From dd0f83497185592cdb0a16fbb6dfbf563660a144 Mon Sep 17 00:00:00 2001 From: William Nelson Date: Mon, 15 Dec 2025 12:42:33 -0800 Subject: [PATCH 54/58] Fix OptionsFlow config_entry setter error in Home Assistant 2024.x+ In Home Assistant 2024.x and later, the OptionsFlow base class provides config_entry as a read-only property. The previous implementation tried to set self.config_entry in __init__, which fails with: AttributeError: property 'config_entry' of 'OpenHASPOptionsFlowHandler' object has no setter This caused a 500 Internal Server Error when clicking the gear icon to configure any openHASP device in the integration settings. Fix: Remove the custom __init__ method and don't pass config_entry to the constructor. The base class automatically provides self.config_entry. --- custom_components/openhasp/config_flow.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/custom_components/openhasp/config_flow.py b/custom_components/openhasp/config_flow.py index 666639d..5811bd6 100644 --- a/custom_components/openhasp/config_flow.py +++ b/custom_components/openhasp/config_flow.py @@ -220,16 +220,12 @@ async def async_step_personalize(self, user_input=None): @callback def async_get_options_flow(config_entry): """Set the OptionsFlowHandler.""" - return OpenHASPOptionsFlowHandler(config_entry) + return OpenHASPOptionsFlowHandler() class OpenHASPOptionsFlowHandler(config_entries.OptionsFlow): """ConfigOptions flow for openHASP.""" - def __init__(self, config_entry): - """Initialize openHASP options flow.""" - self.config_entry = config_entry - async def async_step_init(self, user_input=None): """Manage the options.""" if user_input is not None: From 674c56166b2d057fd65a0d82252f6afed431f55d Mon Sep 17 00:00:00 2001 From: William Nelson Date: Mon, 15 Dec 2025 13:08:20 -0800 Subject: [PATCH 55/58] Address review feedback: use cv.make_entity_service_schema for all services Per dgomes' feedback, updated ALL entity services to use cv.make_entity_service_schema() for consistency, not just the ones with empty schemas. This fixes: - Variable naming bug (WAKEUP_SERVICE_SCHEMA vs WAKEUP_SCHEMA) - Applies consistent schema wrapping to PAGE_CHANGE, LOAD_PAGE, CLEAR_PAGE, COMMAND, and CONFIG services --- custom_components/openhasp/__init__.py | 56 +++++++++++++++++--------- 1 file changed, 38 insertions(+), 18 deletions(-) diff --git a/custom_components/openhasp/__init__.py b/custom_components/openhasp/__init__.py index d6b2c66..da3537d 100644 --- a/custom_components/openhasp/__init__.py +++ b/custom_components/openhasp/__init__.py @@ -186,37 +186,57 @@ async def async_setup(hass, config): component = hass.data[DOMAIN][CONF_COMPONENT] = EntityComponent(_LOGGER, DOMAIN, hass) - WAKEUP_SERVICE_SCHEMA = cv.make_entity_service_schema({}) - PAGE_NEXT_SERVICE_SCHEMA = cv.make_entity_service_schema({}) - PAGE_PREV_SERVICE_SCHEMA = cv.make_entity_service_schema({}) - - component.async_register_entity_service(SERVICE_WAKEUP, WAKEUP_SCHEMA, "async_wakeup") - component.async_register_entity_service(SERVICE_PAGE_NEXT, PAGE_NEXT_SCHEMA, "async_change_page_next") - component.async_register_entity_service(SERVICE_PAGE_PREV, PAGE_PREV_SCHEMA, "async_change_page_prev") - component.async_register_entity_service(SERVICE_PAGE_CHANGE, {vol.Required(ATTR_PAGE): int}, "async_change_page") - component.async_register_entity_service(SERVICE_LOAD_PAGE, {vol.Required(ATTR_PATH): cv.isfile}, "async_load_page") - component.async_register_entity_service(SERVICE_CLEAR_PAGE, {vol.Optional(ATTR_PAGE): int}, "async_clearpage") - + # Use cv.make_entity_service_schema for all services for consistency + component.async_register_entity_service( + SERVICE_WAKEUP, + cv.make_entity_service_schema({}), + "async_wakeup", + ) + component.async_register_entity_service( + SERVICE_PAGE_NEXT, + cv.make_entity_service_schema({}), + "async_change_page_next", + ) + component.async_register_entity_service( + SERVICE_PAGE_PREV, + cv.make_entity_service_schema({}), + "async_change_page_prev", + ) + component.async_register_entity_service( + SERVICE_PAGE_CHANGE, + cv.make_entity_service_schema({vol.Required(ATTR_PAGE): int}), + "async_change_page", + ) + component.async_register_entity_service( + SERVICE_LOAD_PAGE, + cv.make_entity_service_schema({vol.Required(ATTR_PATH): cv.isfile}), + "async_load_page", + ) + component.async_register_entity_service( + SERVICE_CLEAR_PAGE, + cv.make_entity_service_schema({vol.Optional(ATTR_PAGE): int}), + "async_clearpage", + ) component.async_register_entity_service( SERVICE_COMMAND, - { + cv.make_entity_service_schema({ vol.Required(ATTR_COMMAND_KEYWORD): cv.string, vol.Optional(ATTR_COMMAND_PARAMETERS, default=""): cv.string, - }, + }), "async_command_service", ) - component.async_register_entity_service( SERVICE_CONFIG, - { + cv.make_entity_service_schema({ vol.Required(ATTR_CONFIG_SUBMODULE): cv.string, vol.Required(ATTR_CONFIG_PARAMETERS): cv.string, - }, + }), "async_config_service", ) - component.async_register_entity_service( - SERVICE_PUSH_IMAGE, PUSH_IMAGE_SCHEMA, "async_push_image" + SERVICE_PUSH_IMAGE, + PUSH_IMAGE_SCHEMA, + "async_push_image", ) hass.data[DOMAIN][DATA_IMAGES] = dict() From 8de2d3ac7b226980c643580b3ff54b7d9845e45c Mon Sep 17 00:00:00 2001 From: cumal <43264640+cumal@users.noreply.github.com> Date: Mon, 23 Mar 2026 01:42:40 +0100 Subject: [PATCH 56/58] Add support for custom/subtopic (#167) Support to handle custom commands. https://github.com/HASwitchPlate/openHASP/blob/26671370f527d70b5b1a737833a8a78085c543b2/src/hasp/hasp_dispatch.cpp#L455 --- custom_components/openhasp/__init__.py | 8 +++++++- custom_components/openhasp/const.py | 2 ++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/custom_components/openhasp/__init__.py b/custom_components/openhasp/__init__.py index 7783f1b..f79d816 100644 --- a/custom_components/openhasp/__init__.py +++ b/custom_components/openhasp/__init__.py @@ -58,6 +58,7 @@ CONF_PROPERTIES, CONF_TOPIC, CONF_TRACK, + CONF_SUBTOPIC, DATA_IMAGES, DATA_LISTENER, DISCOVERED_MANUFACTURER, @@ -119,6 +120,7 @@ def hasp_object(value): vol.Optional(CONF_TRACK, default=None): vol.Any(cv.entity_id, None), vol.Optional(CONF_PROPERTIES, default={}): PROPERTY_SCHEMA, vol.Optional(CONF_EVENT, default={}): EVENT_SCHEMA, + vol.Optional(CONF_SUBTOPIC): cv.string, } ) @@ -725,7 +727,11 @@ def __init__(self, hass, plate_topic, config): self.hass = hass self.obj_id = config[CONF_OBJID] - self.command_topic = f"{plate_topic}/command/{self.obj_id}." + subtopic = config.get("subtopic") + if subtopic: + self.command_topic = f"{plate_topic}/command/{subtopic}/{self.obj_id}." + else: + self.command_topic = f"{plate_topic}/command/{self.obj_id}." self.state_topic = f"{plate_topic}/state/{self.obj_id}" self.cached_properties = {} diff --git a/custom_components/openhasp/const.py b/custom_components/openhasp/const.py index 58907af..83d3e42 100644 --- a/custom_components/openhasp/const.py +++ b/custom_components/openhasp/const.py @@ -28,6 +28,8 @@ CONF_NODE = "node" CONF_HWID = "hwid" CONF_INPUT = "input" +CONF_SUBTOPIC = "subtopic" + DATA_LISTENER = "listener" DATA_IMAGES = "images" From bc6690c9e565258534e87bd4733fc49fb15a434c Mon Sep 17 00:00:00 2001 From: fvanroie <15969459+fvanroie@users.noreply.github.com> Date: Mon, 23 Mar 2026 02:05:41 +0100 Subject: [PATCH 57/58] Bump version from 0.7.8 to 0.7.9 --- custom_components/openhasp/manifest.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/custom_components/openhasp/manifest.json b/custom_components/openhasp/manifest.json index f99d98e..da5dabf 100644 --- a/custom_components/openhasp/manifest.json +++ b/custom_components/openhasp/manifest.json @@ -9,6 +9,6 @@ "issue_tracker": "https://github.com/HASwitchPlate/openHASP-custom-component/issues", "mqtt": ["hasp/discovery/#"], "requirements": ["jsonschema>=3.2.0"], - "version": "0.7.8", + "version": "0.7.9", "zeroconf": ["_openhasp._tcp.local."] } From 474f6eebf1fc59a2b40bb1354ce9c8ba995c923b Mon Sep 17 00:00:00 2001 From: fvanroie <15969459+fvanroie@users.noreply.github.com> Date: Mon, 23 Mar 2026 02:13:43 +0100 Subject: [PATCH 58/58] Change supported color modes to ONOFF for HASPLight Fix for [HA Core 2025.3 Warning: HASPLight does not report a color mode #161 --- custom_components/openhasp/light.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/custom_components/openhasp/light.py b/custom_components/openhasp/light.py index 10afa20..5f6db2c 100644 --- a/custom_components/openhasp/light.py +++ b/custom_components/openhasp/light.py @@ -106,7 +106,8 @@ async def async_setup_entry( class HASPLight(HASPToggleEntity, LightEntity): """Representation of openHASP Light.""" - _attr_supported_color_modes = {ColorMode.BRIGHTNESS} + _attr_color_mode = ColorMode.ONOFF + _attr_supported_color_modes = {ColorMode.ONOFF} def __init__(self, name, hwid, topic, gpio): """Initialize the light."""