diff --git a/CHANGELOG.md b/CHANGELOG.md index 5588bdbd..75e9af57 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # Changelog +## v0.59.0(a0) + +- Test new feature: pairing of Plus-device (untested!!) + ## v0.58.2 - 2026-01-29 - Update plugwise_usb to [v0.47.2](https://github.com/plugwise/python-plugwise-usb/releases/tag/v0.47.2) fixing a bug. diff --git a/custom_components/plugwise_usb/__init__.py b/custom_components/plugwise_usb/__init__.py index 8a35c6b5..bab857ce 100644 --- a/custom_components/plugwise_usb/__init__.py +++ b/custom_components/plugwise_usb/__init__.py @@ -106,6 +106,8 @@ async def async_node_discovered(node_event: NodeEvent, mac: str) -> None: await api_stick.disconnect() raise ConfigEntryNotReady("Failed to connect to Circle+") from exc + _LOGGER.info("Discovery of the Plugwise network coordinator has finished") + # Load platforms to allow them to register for node events await hass.config_entries.async_forward_entry_setups( config_entry, PLUGWISE_USB_PLATFORMS @@ -152,9 +154,11 @@ async def disable_production(call: ServiceCall) -> bool: while True: await asyncio.sleep(1) + _LOGGER.debug("Discovering network...") if api_stick.network_discovered: break + _LOGGER.debug("INIT done.") return True diff --git a/custom_components/plugwise_usb/config_flow.py b/custom_components/plugwise_usb/config_flow.py index 654c1863..01177269 100644 --- a/custom_components/plugwise_usb/config_flow.py +++ b/custom_components/plugwise_usb/config_flow.py @@ -2,20 +2,33 @@ from __future__ import annotations -from typing import Any +from typing import Any, Final from plugwise_usb import Stick -from plugwise_usb.exceptions import StickError +from plugwise_usb.exceptions import NodeError, StickError import voluptuous as vol from homeassistant.components import usb -from homeassistant.config_entries import SOURCE_USER, ConfigFlow +from homeassistant.config_entries import ( + SOURCE_USER, + ConfigEntry, + ConfigFlow, + ConfigFlowResult, + OptionsFlow, +) from homeassistant.const import CONF_BASE from homeassistant.core import callback from homeassistant.data_entry_flow import FlowResult +from homeassistant.exceptions import HomeAssistantError import serial.tools.list_ports from .const import CONF_MANUAL_PATH, CONF_USB_PATH, DOMAIN, MANUAL_PATH +from .coordinator import PlugwiseUSBDataUpdateCoordinator +from .util import validate_mac + +type PlugwiseUSBConfigEntry = ConfigEntry[PlugwiseUSBDataUpdateCoordinator] + +CONF_ZIGBEE_MAC: Final[str] = "zigbee_mac" @callback @@ -123,3 +136,42 @@ async def async_step_manual_path( ), errors=errors, ) + + @staticmethod + @callback + def async_get_options_flow( + config_entry: PlugwiseUSBConfigEntry + ) -> PlugwiseUSBOptionsFlowHandler: + """Get the options flow for this handler.""" + return PlugwiseUSBOptionsFlowHandler(config_entry) + + +class PlugwiseUSBOptionsFlowHandler(OptionsFlow): + """Plugwise USB options flow.""" + + def __init__(self, config_entry: ConfigEntry) -> None: + """Initialize options flow.""" + self.coordinator = self.config_entry.runtime_data + + async def async_step_init( + self, user_input: dict[str, Any] | None = None + ) -> ConfigFlowResult | None: + """Handle the input of the plus-device MAC address.""" + errors: dict[str, str] = {} + if user_input is not None: + mac = user_input["zigbee_mac"] + if validate_mac(mac): + try: + self.coordinator.api_stick.plus_pair_request(mac) + except NodeError as exc: + raise HomeAssistantError(f"Pairing of Plus-device {mac} failed") from exc + return self.async_create_entry(title="", data=user_input) + + errors[CONF_BASE] = "invalid_mac" + + return self.async_show_form( + step_id="init", + data_schema=vol.Schema({vol.Required(CONF_ZIGBEE_MAC): str}), + errors=errors, + ) + diff --git a/custom_components/plugwise_usb/manifest.json b/custom_components/plugwise_usb/manifest.json index d822359d..44c18f28 100644 --- a/custom_components/plugwise_usb/manifest.json +++ b/custom_components/plugwise_usb/manifest.json @@ -9,6 +9,6 @@ "iot_class": "local_polling", "issue_tracker": "https://github.com/plugwise/python-plugwise-usb/issues", "loggers": ["plugwise_usb"], - "requirements": ["plugwise-usb==0.47.2"], - "version": "0.58.2" + "requirements": ["plugwise-usb@git+https://github.com/plugwise/python-plugwise-usb@pair-plus#plugwise-usb==0.48.0a1"], + "version": "0.59.0a0" } diff --git a/custom_components/plugwise_usb/strings.json b/custom_components/plugwise_usb/strings.json index bb1ecefc..a9952a68 100644 --- a/custom_components/plugwise_usb/strings.json +++ b/custom_components/plugwise_usb/strings.json @@ -22,6 +22,20 @@ "stick_init": "Initialization of Plugwise USB-stick failed" } }, + "options": { + "step": { + "init": { + "title": "Pair Plus-device", + "description": "Please enter the ZigBee MAC address:", + "data": { + "zigbee_mac": "16-bit MAC" + } + } + }, + "error": { + "invalid_mac": "MAC is invalid, please retry" + } + }, "services": { "enable_production":{ "name": "Enable production logging", @@ -235,3 +249,4 @@ } } } + diff --git a/custom_components/plugwise_usb/util.py b/custom_components/plugwise_usb/util.py new file mode 100644 index 00000000..e0aaadc1 --- /dev/null +++ b/custom_components/plugwise_usb/util.py @@ -0,0 +1,18 @@ +"""Plugwise USB helper functions.""" + +from __future__ import annotations + +import re + + +def validate_mac(mac: str) -> bool: + """Validate the supplied string is in a ZigBee MAC address format.""" + try: + if not re.match("^[A-F0-9]+$", mac): + return False + except TypeError: + return False + + if len(mac) != 16: + return False + return True