diff --git a/custom_components/button_plus/button.py b/custom_components/button_plus/button.py index c4b1fd3..dbdf3f7 100644 --- a/custom_components/button_plus/button.py +++ b/custom_components/button_plus/button.py @@ -3,12 +3,18 @@ from __future__ import annotations import logging +from datetime import timedelta +from functools import cached_property +from typing import Any from homeassistant.components.button import ButtonEntity, ButtonDeviceClass from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant -from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.device_registry import DeviceInfo +from homeassistant.helpers.entity_platform import ( + AddEntitiesCallback, + async_get_current_platform, +) from .button_plus_api.model import Connector, ConnectorEnum from .const import DOMAIN @@ -16,6 +22,10 @@ _LOGGER = logging.getLogger(__name__) +SCAN_INTERVAL = timedelta(seconds=30) +SERVICE_LONG_PRESS = "long_press" +SERVICE_RELEASE = "release" + async def async_setup_entry( hass: HomeAssistant, @@ -47,8 +57,23 @@ async def async_setup_entry( async_add_entities(button_entities) + platform = async_get_current_platform() + platform.async_register_entity_service( + SERVICE_LONG_PRESS, + {}, + "_async_long_press_action", + ) + + platform.async_register_entity_service( + SERVICE_RELEASE, + {}, + "_async_release_action", + ) + class ButtonPlusButton(ButtonEntity): + _attr_click_type: str | None = None + def __init__(self, btn_id: int, hub: ButtonPlusHub): self._is_on = False self._hub_id = hub.hub_id @@ -107,3 +132,24 @@ def device_info(self) -> DeviceInfo: async def async_press(self) -> None: """Handle the button press.""" _LOGGER.debug(f"async press from mqtt button: {self._btn_id}") + + async def _async_long_press_action(self) -> None: + self._attr_click_type = "long" + await super()._async_press_action() + + async def _async_press_action(self) -> None: + self._attr_click_type = "single" + await super()._async_press_action() + + async def _async_release_action(self) -> None: + pass + + @property + def state_attributes(self) -> dict[str, Any] | None: + return { + "click_type": self._attr_click_type, + } + + @cached_property + def click_type(self) -> str | None: + return self._attr_click_type diff --git a/custom_components/button_plus/config_flow.py b/custom_components/button_plus/config_flow.py index c94983e..ccdc464 100644 --- a/custom_components/button_plus/config_flow.py +++ b/custom_components/button_plus/config_flow.py @@ -342,6 +342,14 @@ def add_topics_to_buttons( } ) + # Create topics for button click + button.topics.append({ + "brokerid": "ha-button-plus", + "topic": f"buttonplus/{device_id}/button/{button.button_id}/long_press", + "payload": "press", + "eventtype": EventType.LONG_PRESS + }) + return device_config def get_mqtt_endpoint(self, endpoint: str) -> str: diff --git a/custom_components/button_plus/coordinator.py b/custom_components/button_plus/coordinator.py index e8bf58e..9b438a3 100644 --- a/custom_components/button_plus/coordinator.py +++ b/custom_components/button_plus/coordinator.py @@ -29,33 +29,25 @@ def __init__(self, hass: HomeAssistant, hub: ButtonPlusHub): self.hub = hub self._hass = hass self._mqtt_subscribed_buttons = False - self._mqtt_topic_buttons = f"buttonplus/{hub.hub_id}/button/+/click" - self._mqtt_topic_brightness = f"buttonplus/{hub.hub_id}/brightness/+" - self._mqtt_topic_page = f"buttonplus/{hub.hub_id}/page/+" + self._mqtt_topics = [ + (f"buttonplus/{hub.hub_id}/button/+/click", self.mqtt_button_callback), + (f"buttonplus/{hub.hub_id}/button/+/long_press", self.mqtt_button_long_press_callback), + (f"buttonplus/{hub.hub_id}/brightness/+", self.mqtt_brightness_callback), + (f"buttonplus/{hub.hub_id}/page/+", self.mqtt_page_callback), + ] async def _async_update_data(self): """Create MQTT subscriptions for buttonplus""" _LOGGER.debug("Initial data fetch from coordinator") if not self._mqtt_subscribed_buttons: - self.unsubscribe_mqtt = await mqtt.async_subscribe( - self._hass, self._mqtt_topic_buttons, self.mqtt_button_callback, 0 - ) - _LOGGER.debug(f"MQTT subscribed to {self._mqtt_topic_buttons}") - - if not self._mqtt_subscribed_buttons: - self.unsubscribe_mqtt = await mqtt.async_subscribe( - self._hass, - self._mqtt_topic_brightness, - self.mqtt_brightness_callback, - 0, - ) - _LOGGER.debug(f"MQTT subscribed to {self._mqtt_topic_brightness}") - - if not self._mqtt_subscribed_buttons: - self.unsubscribe_mqtt = await mqtt.async_subscribe( - self._hass, self._mqtt_topic_page, self.mqtt_page_callback, 0 - ) - _LOGGER.debug(f"MQTT subscribed to {self._mqtt_topic_page}") + for topic, cb in self._mqtt_topics: + self.unsubscribe_mqtt = await mqtt.async_subscribe( + self._hass, + topic, + cb, + 0 + ) + _LOGGER.debug(f"MQTT subscribed to {topic}") @callback async def mqtt_page_callback(self, message: ReceiveMessage): @@ -92,3 +84,18 @@ async def mqtt_button_callback(self, message: ReceiveMessage): await self.hass.services.async_call( "button", "press", target={"entity_id": entity.entity_id} ) + + @callback + async def mqtt_button_long_press_callback(self, message: ReceiveMessage): + # Handle the message here + _LOGGER.debug(f"Received message on topic {message.topic}: {message.payload}") + match = re.search(r'/(\d+)/long_press', message.topic) + btn_id = int(match.group(1)) if match else None + + entity: ButtonEntity = self.hub.button_entities[str(btn_id)] + + await self.hass.services.async_call( + DOMAIN, + 'long_press', + target={"entity_id": entity.entity_id} + ) diff --git a/custom_components/button_plus/services.yaml b/custom_components/button_plus/services.yaml new file mode 100644 index 0000000..5b9e839 --- /dev/null +++ b/custom_components/button_plus/services.yaml @@ -0,0 +1,10 @@ +long_press: + description: Long press of a button on a Button+ + target: + entity: + domain: button_plus +release: + description: Release of a button on a Button+ + target: + entity: + domain: button_plus diff --git a/custom_components/button_plus/strings.json b/custom_components/button_plus/strings.json new file mode 100644 index 0000000..c3ac391 --- /dev/null +++ b/custom_components/button_plus/strings.json @@ -0,0 +1,60 @@ +{ + "config": { + "abort": { + "mqtt_not_enabled": "The MQTT integration is not enabled. Please add this first here: {mqtt_integration_link}" + }, + "error": { + "cannot_connect": "Failed to connect, see log for more info", + "button_plus_connection": "Error connecting or reading from https://api.button.plus/ See log for more info", + "invalid_ip": "The IP ' {ip} ' is not a valid IPv4 address." + }, + "step": { + "fetch_website": { + "data": { + "cookie": "Auth Cookie", + "email": "Email", + "password": "Password" + }, + "data_description": { + "cookie": "Optional, can be used instead of login. If used, paste the entire cookie content including auth_cookie=" + }, + "description": "Either enter your login info for button plus or enter the Auth Cookie after login in on https://button.plus/" + }, + "manual": { + "data": { + "ip_address": "IP Address" + }, + "data_description": { + "ip_address": "Button+ IP address (as seen on the bottom left of the display)" + }, + "description": "Manually enter Button+ device IP address" + }, + "user": { + "data": { + "broker": "Address to reach the broker." + }, + "data_description": { + "broker": "Leave this unchanged if you're unsure what this is." + }, + "description": "The MQTT broker configured in Home Assistant will be used:\n - Broker: {mqtt_broker}\n - Port:{mqtt_broker_port}\n - Authentication: {mqtt_user}\n\n This broker configuration will be added to each device with the name of this integration; 'ha-button-plus' and can be found in the Button+ interface.\n\n The buttons of the Button+ will be configured to send clicks on a topic in this broker and the integration will sync these with the home assistant button entities. \n\n You can override the MQTT broker endpoint if you want here." + }, + "choose_entry": { + "menu_options": { + "fetch_website": "Fetch all devices from Button.plus website", + "manual": "Manually enter single device by local IP" + }, + "description": "To continue, pick the desired option to setup your Button+ devices." + } + } + }, + "services": { + "long_press": { + "name": "Long press", + "description": "Long press on a button on button+" + }, + "release": { + "name": "Release", + "description": "Release of a button on button+" + } + } +} \ No newline at end of file diff --git a/custom_components/button_plus/translations/en.json b/custom_components/button_plus/translations/en.json index dcd39af..1a60231 100644 --- a/custom_components/button_plus/translations/en.json +++ b/custom_components/button_plus/translations/en.json @@ -46,5 +46,15 @@ "description": "To continue, pick the desired option to setup your Button+ devices." } } + }, + "services": { + "long_press": { + "name": "Long press", + "description": "Long press on a button on button+" + }, + "release": { + "name": "Release", + "description": "Release of a button on button+" + } } }