diff --git a/CHANGELOG.md b/CHANGELOG.md index 68f29331..2335c807 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,11 @@ ## Versions from 0.4x +### v0.54.2 + +- Bump plugwise to [v0.44.3](https://github.com/plugwise/python-plugwise-usb/releases/tag/v0.44.3) +- Implement button to enable auto joining through CirclePlus device page + ### v0.54.1 - 2025-06-11 - Implement solution for Issue #259 via plugwise to [v0.44.2](https://github.com/plugwise/python-plugwise-usb/releases/tag/v0.44.2) diff --git a/custom_components/plugwise_usb/__init__.py b/custom_components/plugwise_usb/__init__.py index 4dba6600..5634b952 100644 --- a/custom_components/plugwise_usb/__init__.py +++ b/custom_components/plugwise_usb/__init__.py @@ -18,7 +18,6 @@ DOMAIN, NODES, PLUGWISE_USB_PLATFORMS, - SERVICE_AUTO_JOIN, SERVICE_DISABLE_PRODUCTION, SERVICE_ENABLE_PRODUCTION, SERVICE_ENERGY_RESET, @@ -112,23 +111,6 @@ async def async_node_discovered(node_event: NodeEvent, mac: str) -> None: config_entry, PLUGWISE_USB_PLATFORMS ) - # Listen for entry updates - config_entry.async_on_unload(config_entry.add_update_listener(update_listener)) - - async def enable_auto_joining(call: ServiceCall) -> bool: - """Enable auto-joining of devices to Plugwise zigbee network.""" - if not api_stick.accept_join_request: - raise HomeAssistantError( - "Device joining is blocked, enable newly added entities" - + " under System options for the Integration" - ) - - try: - result = await api_stick.set_accept_join_request(True) - except (NodeError, StickError) as exc: - raise HomeAssistantError(f"Failed to enable auto-joining: {exc}") from exc - return result - async def reset_energy_logs(call: ServiceCall) -> bool: """Reset energylog collection for a Node.""" mac = call.data[ATTR_MAC] @@ -162,7 +144,6 @@ async def disable_production(call: ServiceCall) -> bool: ) from exc return result - hass.services.async_register(DOMAIN, SERVICE_AUTO_JOIN, enable_auto_joining) hass.services.async_register( DOMAIN, SERVICE_ENABLE_PRODUCTION, enable_production, SERVICE_USB_DEVICE_SCHEMA ) @@ -184,30 +165,10 @@ async def disable_production(call: ServiceCall) -> bool: await asyncio.sleep(1) if api_stick.network_discovered: break - # Enable/disable automatic joining of available devices when the network is up - if config_entry.pref_disable_new_entities: - _LOGGER.debug("Configuring Circle + NOT to accept any new join requests") - try: - await api_stick.set_accept_join_request(False) - except StickError as exc: - raise HomeAssistantError(f"Disabling auto-joining failed: {exc}") from exc - else: - _LOGGER.debug("Configuring Circle + to automatically accept new join requests") - try: - await api_stick.set_accept_join_request(True) - except StickError as exc: - raise HomeAssistantError(f"Enabling auto-joining failed: {exc}") from exc return True -async def update_listener( - hass: HomeAssistant, config_entry: PlugwiseUSBConfigEntry -) -> None: - """Handle options update.""" - await hass.config_entries.async_reload(config_entry.entry_id) - - async def async_unload_entry( hass: HomeAssistant, config_entry: PlugwiseUSBConfigEntry ) -> bool: diff --git a/custom_components/plugwise_usb/button.py b/custom_components/plugwise_usb/button.py new file mode 100644 index 00000000..0d534493 --- /dev/null +++ b/custom_components/plugwise_usb/button.py @@ -0,0 +1,115 @@ +"""Plugwise USB Button component for HomeAssistant.""" + +from __future__ import annotations + +from dataclasses import dataclass +from datetime import timedelta +import logging + +from homeassistant.components.button import ( + ButtonEntity, + ButtonEntityDescription, +) +from homeassistant.const import EntityCategory, Platform +from homeassistant.core import HomeAssistant +from homeassistant.helpers.entity_platform import AddEntitiesCallback +from plugwise_usb.api import NodeEvent, NodeFeature + +from .const import NODES, STICK, UNSUB_NODE_LOADED +from .coordinator import PlugwiseUSBConfigEntry, PlugwiseUSBDataUpdateCoordinator +from .entity import PlugwiseUSBEntity, PlugwiseUSBEntityDescription + +_LOGGER = logging.getLogger(__name__) +PARALLEL_UPDATES = 2 +SCAN_INTERVAL = timedelta(seconds=30) + + +@dataclass(kw_only=True) +class PlugwiseButtonEntityDescription( + PlugwiseUSBEntityDescription, ButtonEntityDescription +): + """Describes Plugwise button entity.""" + + async_button_fn: str = "" + + +BUTTON_TYPES: tuple[PlugwiseButtonEntityDescription, ...] = ( + PlugwiseButtonEntityDescription( + key="enable_auto_join", + translation_key="enable_auto_join", + entity_category=EntityCategory.CONFIG, + async_button_fn="enable_auto_join", + node_feature=NodeFeature.CIRCLEPLUS, + ), +) + + +async def async_setup_entry( + _hass: HomeAssistant, + config_entry: PlugwiseUSBConfigEntry, + async_add_entities: AddEntitiesCallback, +) -> None: + """Set up the USB buttons from a config entry.""" + + async def async_add_button(node_event: NodeEvent, mac: str) -> None: + """Initialize DUC for button.""" + if node_event != NodeEvent.LOADED: + return + entities: list[PlugwiseUSBEntity] = [] + if (node_duc := config_entry.runtime_data[NODES].get(mac)) is not None: + _LOGGER.debug("Add button entities for %s | duc=%s", mac, node_duc.name) + entities.extend( + [ + PlugwiseUSBButtonEntity(node_duc, entity_description) + for entity_description in BUTTON_TYPES + if entity_description.node_feature in node_duc.node.features + ] + ) + if entities: + async_add_entities(entities) + + api_stick = config_entry.runtime_data[STICK] + + # Listen for loaded nodes + config_entry.runtime_data[Platform.BUTTON] = {} + config_entry.runtime_data[Platform.BUTTON][UNSUB_NODE_LOADED] = ( + api_stick.subscribe_to_node_events( + async_add_button, + (NodeEvent.LOADED,), + ) + ) + + # load any current nodes + for mac, node in api_stick.nodes.items(): + if node.is_loaded: + await async_add_button(NodeEvent.LOADED, mac) + + +async def async_unload_entry( + _hass: HomeAssistant, + config_entry: PlugwiseUSBConfigEntry, +) -> None: + """Unload a config entry.""" + config_entry.runtime_data[Platform.BUTTON][UNSUB_NODE_LOADED]() + + +class PlugwiseUSBButtonEntity(PlugwiseUSBEntity, ButtonEntity): + """Representation of a Plugwise USB Data Update Coordinator button.""" + + def __init__( + self, + node_duc: PlugwiseUSBDataUpdateCoordinator, + entity_description: PlugwiseButtonEntityDescription, + ) -> None: + """Initialize a button entity.""" + super().__init__(node_duc, entity_description) + self.async_button_fn = getattr( + node_duc.node, entity_description.async_button_fn + ) + + async def async_press(self) -> None: + """Button was pressed.""" + await self.async_button_fn() + + async def async_added_to_hass(self): + """Subscribe for push updates.""" diff --git a/custom_components/plugwise_usb/const.py b/custom_components/plugwise_usb/const.py index f2c93989..ade90b65 100644 --- a/custom_components/plugwise_usb/const.py +++ b/custom_components/plugwise_usb/const.py @@ -27,9 +27,9 @@ Platform.NUMBER, Platform.SENSOR, Platform.SWITCH, + Platform.BUTTON, ] CONF_USB_PATH: Final[str] = "usb_path" -SERVICE_AUTO_JOIN: Final[str] = "enable_auto_joining" SERVICE_DISABLE_PRODUCTION: Final[str] = "disable_production" SERVICE_ENABLE_PRODUCTION: Final[str] = "enable_production" SERVICE_ENERGY_RESET: Final[str] = "reset_energy_logs" diff --git a/custom_components/plugwise_usb/manifest.json b/custom_components/plugwise_usb/manifest.json index 05eb0bb8..80cee8db 100644 --- a/custom_components/plugwise_usb/manifest.json +++ b/custom_components/plugwise_usb/manifest.json @@ -8,6 +8,6 @@ "integration_type": "hub", "iot_class": "local_polling", "loggers": ["plugwise_usb"], - "requirements": ["plugwise-usb==0.44.2"], - "version": "0.54.1" + "requirements": ["plugwise-usb==0.44.3"], + "version": "0.54.2" } diff --git a/custom_components/plugwise_usb/services.yaml b/custom_components/plugwise_usb/services.yaml index 45e8c61a..543cdbb4 100644 --- a/custom_components/plugwise_usb/services.yaml +++ b/custom_components/plugwise_usb/services.yaml @@ -1,5 +1,3 @@ -enable_auto_joining: - description: First enable auto-joining, then reset the Plugwise devices enable_production: description: "Enable production logging for a Plugwise USB node by MAC address" fields: diff --git a/custom_components/plugwise_usb/strings.json b/custom_components/plugwise_usb/strings.json index f93a4238..2448e87f 100644 --- a/custom_components/plugwise_usb/strings.json +++ b/custom_components/plugwise_usb/strings.json @@ -23,10 +23,6 @@ } }, "services": { - "enable_auto_joining": { - "name": "Enable Plugwise device auto-joining (temporarily)", - "description": "First enable this service then reset the Plugwise device(s) to join" - }, "enable_production":{ "name": "Enable production logging", "description": "Enter the mac of the Node: (data = mac: 0123456789ABCDEF)", @@ -190,6 +186,11 @@ "sed_clock_interval": { "name": "Clock interval" } + }, + "button": { + "enable_auto_join": { + "name": "Enable Auto-joining (temporarily)" + } } } } diff --git a/custom_components/plugwise_usb/translations/en.json b/custom_components/plugwise_usb/translations/en.json index be2a863f..0033aa24 100644 --- a/custom_components/plugwise_usb/translations/en.json +++ b/custom_components/plugwise_usb/translations/en.json @@ -23,10 +23,6 @@ } }, "services": { - "enable_auto_joining": { - "name": "Enable Plugwise device auto-joining (temporarily)", - "description": "First enable this service then reset the Plugwise device(s) to join" - }, "enable_production":{ "name": "Enable production logging", "description": "Enter the mac of the Node: (data = mac: 0123456789ABCDEF)", @@ -190,6 +186,11 @@ "sed_clock_interval": { "name": "Clock interval" } + }, + "button": { + "enable_auto_join": { + "name": "Enable Auto-joining (temporarily)" + } } } } diff --git a/custom_components/plugwise_usb/translations/nl.json b/custom_components/plugwise_usb/translations/nl.json index 79e945c8..8cead0e2 100644 --- a/custom_components/plugwise_usb/translations/nl.json +++ b/custom_components/plugwise_usb/translations/nl.json @@ -23,10 +23,6 @@ } }, "services": { - "enable_auto_joining": { - "name": "Zet Plugwise auto-toevoegen aan (tijdelijk)", - "description": "Voer eerst deze service uit en reset dan het toe te voegen Plugwise apparaat" - }, "enable_production":{ "name": "Zet productie-loggen aan", "description": "Voer het mac-adres van de Node in: (data = mac: 0123456789ABSDEF)", @@ -190,6 +186,11 @@ "sed_clock_interval": { "name": "Klokinterval" } + }, + "button": { + "enable_auto_join": { + "name": "Zet Plugwise auto-toevoegen aan (tijdelijk)" + } } } } diff --git a/pyproject.toml b/pyproject.toml index 462bd70f..741c700b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "plugwise_usb-beta" -version = "0.54.0" +version = "0.54.2" description = "Plugwise USB custom_component (BETA)" readme = "README.md" requires-python = ">=3.13"