From 63159dc4b561d95aec67f40b8686aecb4ab9ea83 Mon Sep 17 00:00:00 2001 From: Tom Scholten Date: Tue, 18 Apr 2023 13:19:13 +0200 Subject: [PATCH 01/20] Split prepare (after fork) --- CHANGELOG.md | 7 + README.md | 21 +- custom_components/plugwise/__init__.py | 149 ++++++-- custom_components/plugwise/binary_sensor.py | 143 +------- custom_components/plugwise/config_flow.py | 158 +------- custom_components/plugwise/const.py | 90 ----- custom_components/plugwise/gateway.py | 139 -------- custom_components/plugwise/manifest.json | 8 +- custom_components/plugwise/models.py | 126 +------ custom_components/plugwise/sensor.py | 76 +--- custom_components/plugwise/switch.py | 83 +---- custom_components/plugwise/usb.py | 229 ------------ old_dependabot.yml | 11 - scripts/core-testing.sh | 4 +- tests/components/plugwise/conftest.py | 3 +- tests/components/plugwise/test_config_flow.py | 336 ++---------------- 16 files changed, 188 insertions(+), 1395 deletions(-) delete mode 100644 custom_components/plugwise/gateway.py delete mode 100644 custom_components/plugwise/usb.py delete mode 100644 old_dependabot.yml diff --git a/CHANGELOG.md b/CHANGELOG.md index d18a18e9b..40a88922c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # Changelog +## Versions from 0.4x + +### 0.41x + +- More formal Split between Networked and USB code +- Refactoring where necessary + ## Versions from 0.30 and up ### NEW APR 2023 [0.34.10] Implement latest Core PR's diff --git a/README.md b/README.md index b38562897..0d9fc24f3 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,8 @@ # Plugwise custom_component (BETA) -:no_entry::no_entry::no_entry: If you are **not** using USB based Plugwise equipment or have no intention to beta-test our integration, please defer to the **supported** release of this integration **natively** available in [Home Assistant](https://www.home-assistant.io/integrations/plugwise/)! :no_entry::no_entry::no_entry: +:warning::no_entry::warning: Do **not** install if you are using **USB** see [why](#usb-notes) :warning::no_entry::warning: + +:no_entry::no_entry::no_entry: If you have **no** intention to beta-test our integration, please defer to the **supported** release of this integration **natively** available in [Home Assistant](https://www.home-assistant.io/integrations/plugwise/)! :no_entry::no_entry::no_entry: :warning::warning::warning: Always **read** the [release notes](https://github.com/plugwise/plugwise-beta/releases) before upgrading, in case there are BREAKING changes! **Do note** the release title on alpha releases and only install them if specifically instructed by our team! :warning::warning::warning: @@ -52,7 +54,14 @@ Our [Changelog](CHANGELOG.MD) is available as a [separate file](CHANGELOG.md) in - Power-related - Smile P1 (firmware 2.x, 3.x and 4.x) - Stretch (firmware 2.x and 3.x, legacy Circle's and Stealth's) - - Stick (legacy Circle's, Stealth's and Scan's) + +Additional to the **Core** component we support Homekit emulation, notifications and changing some timing. This will not be upstreamed and is code that remained in our codebase (i.e. denied upstreaming by the Core Team for acceptable reasons though we have some people already using that (mostly by them requested) functionality). + +#### USB notes + +Up to spring of 2023 this `custom_component` supported both Networked and USB Plugwise products, as of that time we have split both the backend (python module) and the frontend into separate instances as per recent discussion with the Core team. The `plugwise` integration in HA Core (and therefore `plugwise-beta`) will remain supporting networked Plugwise products under an envisioned `plugwise_bv` Brand umbrella. This paves the way for the upcoming `plugwise_usb-beta` `custom_integration` to refactor and again upstream to HA Core (which was originally planned but there was no branding umbrella in Core back then). + +As such we ask USB users, who are tied in with the `custom_component` as there is no Core integration available yet, for a little patience so we can split and refactor all repositories without loss of functionality for the end users. For our USB users that will however mean some **breaking changes** or customizing under the hood as the `custom_component` name will change and the appropriate naming in HA will do so accordingly. It is for the best though as this will ensure a way forward to HA Core integration, which has been our goal since starting to write Plugwise supporting code for Home Assistant. ### What can I expect in HA Core from this component @@ -79,21 +88,15 @@ For each Plugwise Smile (i.e. gateway) you will have to add it as an integration - [ ] Click the `Configure` button and enter the Smile ID - [ ] Click Add to see the magic happens -If there is no discovered Smile present or you are using the USB stick: +If there is no discovered Smile present: - [ ] Hit the `+` button in the right lower corner - [ ] Search or browse for 'Plugwise beta' and click it -- [ ] Select the type of integration: Network or USB -- For the Network-selection: - [ ] Enter your Smile IP-address and the 8 character ID of the smile - [ ] Click SUBMIT and FINISH and hopefully the magic happens - [ ] Repeat this process to add more Smiles -- For the USB-selection: -- [ ] Select or enter the USB-path -- [ ] Click SUBMIT and FINISH - The config flow will then continue to ask you if you want to put your Smile and detected other devices in area's and presto, things should be available to configure in lovelace. #### Options diff --git a/custom_components/plugwise/__init__.py b/custom_components/plugwise/__init__.py index d614207a1..433d5aac8 100644 --- a/custom_components/plugwise/__init__.py +++ b/custom_components/plugwise/__init__.py @@ -1,27 +1,136 @@ """Plugwise platform for Home Assistant Core.""" +from __future__ import annotations +from typing import Any + +import voluptuous as vol + +from homeassistant.components.switch import DOMAIN as SWITCH_DOMAIN from homeassistant.config_entries import ConfigEntry -from homeassistant.const import CONF_HOST -from homeassistant.core import HomeAssistant +from homeassistant.const import Platform +from homeassistant.core import HomeAssistant, callback +from homeassistant.helpers import device_registry as dr, entity_registry as er +from plugwise.exceptions import PlugwiseError -from .const import CONF_USB_PATH # pw-beta usb -from .gateway import async_setup_entry_gw, async_unload_entry_gw -from .usb import async_setup_entry_usb, async_unload_entry_usb # pw-beta usb +from .const import ( + COORDINATOR, + DOMAIN, + LOGGER, + PLATFORMS_GATEWAY, + SERVICE_DELETE, + UNDO_UPDATE_LISTENER, +) +from .const import CONF_REFRESH_INTERVAL # pw-beta options +from .coordinator import PlugwiseDataUpdateCoordinator async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: - """Set up Plugwise components from a config entry.""" - if entry.data.get(CONF_HOST): - return await async_setup_entry_gw(hass, entry) - if entry.data.get(CONF_USB_PATH): # pw-beta usb - return await async_setup_entry_usb(hass, entry) # pw-beta usb - return False # pragma: no cover - - -async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: - """Unload the Plugwise components.""" - if entry.data.get(CONF_HOST): - return await async_unload_entry_gw(hass, entry) - if entry.data.get(CONF_USB_PATH): # pw-beta usb - return await async_unload_entry_usb(hass, entry) # pw-beta usb - return False # pragma: no cover + """Set up Plugwise Smiles from a config entry.""" + await er.async_migrate_entries(hass, entry.entry_id, async_migrate_entity_entry) + + cooldown = 1.5 # pw-beta frontend refresh-interval + if ( + custom_refresh := entry.options.get(CONF_REFRESH_INTERVAL) + ) is not None: # pragma: no cover + cooldown = custom_refresh + LOGGER.debug("DUC cooldown interval: %s", cooldown) + + coordinator = PlugwiseDataUpdateCoordinator( + hass, entry, cooldown + ) # pw-beta - cooldown, update_interval as extra + await coordinator.async_config_entry_first_refresh() + # Migrate a changed sensor unique_id + migrate_sensor_entities(hass, coordinator) + + undo_listener = entry.add_update_listener(_update_listener) # pw-beta + + hass.data.setdefault(DOMAIN, {})[entry.entry_id] = { + COORDINATOR: coordinator, # pw-beta + UNDO_UPDATE_LISTENER: undo_listener, # pw-beta + } + + device_registry = dr.async_get(hass) + device_registry.async_get_or_create( + config_entry_id=entry.entry_id, + identifiers={(DOMAIN, str(coordinator.api.gateway_id))}, + manufacturer="Plugwise", + model=coordinator.api.smile_model, + name=coordinator.api.smile_name, + sw_version=coordinator.api.smile_version[0], + ) + + async def delete_notification( + self, + ): # pragma: no cover # pw-beta: HA service - delete_notification + """Service: delete the Plugwise Notification.""" + LOGGER.debug( + "Service delete PW Notification called for %s", coordinator.api.smile_name + ) + try: + deleted = await coordinator.api.delete_notification() + LOGGER.debug("PW Notification deleted: %s", deleted) + except PlugwiseError: + LOGGER.debug( + "Failed to delete the Plugwise Notification for %s", + coordinator.api.smile_name, + ) + + await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS_GATEWAY) + + for component in PLATFORMS_GATEWAY: # pw-beta + if component == Platform.BINARY_SENSOR: + hass.services.async_register( + DOMAIN, SERVICE_DELETE, delete_notification, schema=vol.Schema({}) + ) + + return True + + +async def _update_listener( + hass: HomeAssistant, entry: ConfigEntry +): # pragma: no cover # pw-beta + """Handle options update.""" + await hass.config_entries.async_reload(entry.entry_id) + + +async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry): + """Unload a config entry.""" + if unload_ok := await hass.config_entries.async_unload_platforms( + entry, PLATFORMS_GATEWAY + ): + hass.data[DOMAIN].pop(entry.entry_id) + return unload_ok + + +@callback +def async_migrate_entity_entry(entry: er.RegistryEntry) -> dict[str, Any] | None: + """Migrate Plugwise entity entries. + + - Migrates unique ID from old relay switches to the new unique ID + """ + if entry.domain == SWITCH_DOMAIN and entry.unique_id.endswith("-plug"): + return {"new_unique_id": entry.unique_id.replace("-plug", "-relay")} + + # No migration needed + return None + + +def migrate_sensor_entities( + hass: HomeAssistant, + coordinator: PlugwiseDataUpdateCoordinator, +) -> None: + """Migrate Sensors if needed.""" + ent_reg = er.async_get(hass) + + # Migrate opentherm_outdoor_temperature # pw-beta add to Core + # to opentherm_outdoor_air_temperature sensor + for device_id, device in coordinator.data.devices.items(): + if device["dev_class"] != "heater_central": # pw-beta add to Core + continue + + old_unique_id = f"{device_id}-outdoor_temperature" + if entity_id := ent_reg.async_get_entity_id( + Platform.SENSOR, DOMAIN, old_unique_id + ): + new_unique_id = f"{device_id}-outdoor_air_temperature" + ent_reg.async_update_entity(entity_id, new_unique_id=new_unique_id) diff --git a/custom_components/plugwise/binary_sensor.py b/custom_components/plugwise/binary_sensor.py index c17028df5..3b90b9787 100644 --- a/custom_components/plugwise/binary_sensor.py +++ b/custom_components/plugwise/binary_sensor.py @@ -6,42 +6,14 @@ from homeassistant.components.binary_sensor import BinarySensorEntity from homeassistant.config_entries import ConfigEntry -from homeassistant.const import Platform from homeassistant.core import HomeAssistant -from homeassistant.helpers import entity_platform from homeassistant.helpers.entity_platform import AddEntitiesCallback -from plugwise.nodes import PlugwiseNode # pw-beta usb +from .const import COORDINATOR # pw-beta from .const import DOMAIN, LOGGER, SEVERITIES - -# isort: off -from .const import COORDINATOR, PW_TYPE # pw-beta - -from .const import ( - ATTR_SCAN_DAYLIGHT_MODE, - ATTR_SCAN_RESET_TIMER, - ATTR_SCAN_SENSITIVITY_MODE, - ATTR_SED_CLOCK_INTERVAL, - ATTR_SED_CLOCK_SYNC, - ATTR_SED_MAINTENANCE_INTERVAL, - ATTR_SED_SLEEP_FOR, - ATTR_SED_STAY_ACTIVE, - CB_NEW_NODE, - SERVICE_USB_SCAN_CONFIG, - SERVICE_USB_SCAN_CONFIG_SCHEMA, - SERVICE_USB_SED_BATTERY_CONFIG, - SERVICE_USB_SED_BATTERY_CONFIG_SCHEMA, - STICK, - USB, - USB_MOTION_ID, -) # pw-beta usb - -# isort: on - from .coordinator import PlugwiseDataUpdateCoordinator from .entity import PlugwiseEntity from .models import PW_BINARY_SENSOR_TYPES, PlugwiseBinarySensorEntityDescription -from .usb import PlugwiseUSBEntity # pw-beta PARALLEL_UPDATES = 0 @@ -51,64 +23,7 @@ async def async_setup_entry( config_entry: ConfigEntry, async_add_entities: AddEntitiesCallback, ) -> None: - """Set up the Smile switches from a config entry.""" - if hass.data[DOMAIN][config_entry.entry_id][PW_TYPE] == USB: - return await async_setup_entry_usb(hass, config_entry, async_add_entities) - # Considered default and for earlier setups without usb/network config_flow - return await async_setup_entry_gateway(hass, config_entry, async_add_entities) - - -async def async_setup_entry_usb(hass, config_entry, async_add_entities): # pw-beta """Set up Plugwise binary sensor based on config_entry.""" - api_stick = hass.data[DOMAIN][config_entry.entry_id][STICK] - platform = entity_platform.current_platform.get() - - async def async_add_binary_sensors(mac: str): - """Add plugwise binary sensors for device.""" - entities: list[USBBinarySensor] = [] - entities.extend( - [ - USBBinarySensor(api_stick.devices[mac], description) - for description in PW_BINARY_SENSOR_TYPES - if description.plugwise_api == STICK - and description.key in api_stick.devices[mac].features - ] - ) - if entities: - async_add_entities(entities) - - if USB_MOTION_ID in api_stick.devices[mac].features: - LOGGER.debug("Add binary_sensors for %s", mac) - - # Register services - platform.async_register_entity_service( - SERVICE_USB_SCAN_CONFIG, - SERVICE_USB_SCAN_CONFIG_SCHEMA, - "_service_scan_config", - ) - platform.async_register_entity_service( - SERVICE_USB_SED_BATTERY_CONFIG, - SERVICE_USB_SED_BATTERY_CONFIG_SCHEMA, - "_service_sed_battery_config", - ) - - for mac in hass.data[DOMAIN][config_entry.entry_id][Platform.BINARY_SENSOR]: - hass.async_create_task(async_add_binary_sensors(mac)) - - def discoved_device(mac: str): - """Add binary sensors for newly discovered device.""" - hass.async_create_task(async_add_binary_sensors(mac)) - - # Listen for discovered nodes - api_stick.subscribe_stick_callback(discoved_device, CB_NEW_NODE) - - -async def async_setup_entry_gateway( - hass: HomeAssistant, - config_entry: ConfigEntry, - async_add_entities: AddEntitiesCallback, -) -> None: - """Set up the Smile binary_sensors from a config entry.""" coordinator: PlugwiseDataUpdateCoordinator = hass.data[DOMAIN][ config_entry.entry_id ][COORDINATOR] @@ -198,59 +113,3 @@ def extra_state_attributes(self) -> Mapping[str, Any] | None: ] = f"{msg_type.title()}: {msg}" # pw-beta return attrs - - -# pw-beta -# Github issue #265 -class USBBinarySensor(PlugwiseUSBEntity, BinarySensorEntity): # type: ignore[misc] - """Representation of a Plugwise USB Binary Sensor.""" - - def __init__( - self, node: PlugwiseNode, description: PlugwiseBinarySensorEntityDescription - ) -> None: - """Initialize a binary sensor entity.""" - super().__init__(node, description) - - @property - def is_on(self) -> bool: - """Return true if the binary_sensor is on.""" - # Github issue #265 - return getattr(self._node, self.entity_description.state_request_method) # type: ignore[attr-defined] - - def _service_scan_config(self, **kwargs): - """Service call to configure motion sensor of Scan device.""" - sensitivity_mode = kwargs.get(ATTR_SCAN_SENSITIVITY_MODE) - reset_timer = kwargs.get(ATTR_SCAN_RESET_TIMER) - daylight_mode = kwargs.get(ATTR_SCAN_DAYLIGHT_MODE) - LOGGER.debug( - "Configure Scan device '%s': sensitivity='%s', reset timer='%s', daylight mode='%s'", - self.name, - sensitivity_mode, - str(reset_timer), - str(daylight_mode), - ) - self._node.Configure_scan(reset_timer, sensitivity_mode, daylight_mode) - - def _service_sed_battery_config(self, **kwargs): - """Configure battery powered (sed) device service call.""" - stay_active = kwargs.get(ATTR_SED_STAY_ACTIVE) - sleep_for = kwargs.get(ATTR_SED_SLEEP_FOR) - maintenance_interval = kwargs.get(ATTR_SED_MAINTENANCE_INTERVAL) - clock_sync = kwargs.get(ATTR_SED_CLOCK_SYNC) - clock_interval = kwargs.get(ATTR_SED_CLOCK_INTERVAL) - LOGGER.debug( - "Configure SED device '%s': stay active='%s', sleep for='%s', maintenance interval='%s', clock sync='%s', clock interval='%s'", - self.name, - str(stay_active), - str(sleep_for), - str(maintenance_interval), - str(clock_sync), - str(clock_interval), - ) - self._node.Configure_SED( - stay_active, - maintenance_interval, - sleep_for, - clock_sync, - clock_interval, - ) diff --git a/custom_components/plugwise/config_flow.py b/custom_components/plugwise/config_flow.py index 28164bcb6..da96725bc 100644 --- a/custom_components/plugwise/config_flow.py +++ b/custom_components/plugwise/config_flow.py @@ -4,11 +4,9 @@ import datetime as dt # pw-beta options from typing import Any -import serial.tools.list_ports # pw-beta usb import voluptuous as vol from homeassistant import config_entries -from homeassistant.components import usb # pw-beta usb from homeassistant.components.zeroconf import ZeroconfServiceInfo from homeassistant.config_entries import ConfigEntry, ConfigFlow from homeassistant.const import ( @@ -24,6 +22,7 @@ from homeassistant.data_entry_flow import FlowResult from homeassistant.helpers import config_validation as cv from homeassistant.helpers.aiohttp_client import async_get_clientsession +from plugwise import Smile from plugwise.exceptions import ( ConnectionFailedError, InvalidAuthentication, @@ -33,83 +32,21 @@ UnsupportedDeviceError, ) -# pw-beta Note; the below are explicit through isort -from plugwise.exceptions import NetworkDown # pw-beta usb -from plugwise.exceptions import PortError # pw-beta usb -from plugwise.exceptions import StickInitError # pw-beta usb -from plugwise.exceptions import TimeoutException # pw-beta usb -from plugwise.smile import Smile -from plugwise.stick import Stick # pw-beta usb - from .const import ( - API, COORDINATOR, DEFAULT_PORT, DEFAULT_USERNAME, DOMAIN, FLOW_SMILE, FLOW_STRETCH, - PW_TYPE, SMILE, STRETCH, STRETCH_USERNAME, ZEROCONF_MAP, ) - -# pw-beta Note; the below are explicit through isort from .const import CONF_HOMEKIT_EMULATION # pw-beta option -from .const import CONF_MANUAL_PATH # pw-beta usb from .const import CONF_REFRESH_INTERVAL # pw-beta option -from .const import CONF_USB_PATH # pw-beta usb from .const import DEFAULT_SCAN_INTERVAL # pw-beta option -from .const import FLOW_NET # pw-beta usb -from .const import FLOW_TYPE # pw-beta usb -from .const import FLOW_USB # pw-beta usb -from .const import STICK # pw-beta usb - -CONNECTION_SCHEMA = vol.Schema( - {vol.Required(FLOW_TYPE, default=FLOW_NET): vol.In([FLOW_NET, FLOW_USB])} -) # pw-beta usb - - -@callback -def plugwise_stick_entries(hass): # pw-beta usb - """Return existing connections for Plugwise USB-stick domain.""" - sticks = [] - for entry in hass.config_entries.async_entries(DOMAIN): - if entry.data.get(PW_TYPE) == STICK: - sticks.append(entry.data.get(CONF_USB_PATH)) - return sticks - - -# Github issue: #265 -# Might be a `tuple[dict[str, str], Stick | None]` for typing, but that throws -# Item None of Optional[Any] not having attribute mac [union-attr] -async def validate_usb_connection( - self, device_path=None -) -> tuple[dict[str, str], Any]: # pw-beta usb - """Test if device_path is a real Plugwise USB-Stick.""" - errors = {} - - # Avoid creating a 2nd connection to an already configured stick - if device_path in plugwise_stick_entries(self): - errors[CONF_BASE] = "already_configured" - return errors, None - - api_stick = await self.async_add_executor_job(Stick, device_path) - try: - await self.async_add_executor_job(api_stick.connect) - await self.async_add_executor_job(api_stick.initialize_stick) - await self.async_add_executor_job(api_stick.disconnect) - except PortError: - errors[CONF_BASE] = "cannot_connect" - except StickInitError: - errors[CONF_BASE] = "stick_init" - except NetworkDown: - errors[CONF_BASE] = "network_down" - except TimeoutException: - errors[CONF_BASE] = "network_timeout" - return errors, api_stick def _base_gw_schema( @@ -242,75 +179,9 @@ async def async_step_zeroconf( "product": _product, } ) - return await self.async_step_user_gateway() - - async def async_step_user_usb( - self, user_input: dict[str, Any] | None = None - ) -> FlowResult: # pw-beta usb - """Step when user initializes a integration.""" - errors: dict[str, str] = {} - ports = await self.hass.async_add_executor_job(serial.tools.list_ports.comports) - list_of_ports = [ - f"{p}, s/n: {p.serial_number or 'n/a'}" - + (f" - {p.manufacturer}" if p.manufacturer else "") - for p in ports - ] - list_of_ports.append(CONF_MANUAL_PATH) - - if user_input is not None: - user_input.pop(FLOW_TYPE, None) - user_selection = user_input[CONF_USB_PATH] - - if user_selection == CONF_MANUAL_PATH: - return await self.async_step_manual_path() - - port = ports[list_of_ports.index(user_selection)] - device_path = await self.hass.async_add_executor_job( - usb.get_serial_by_id, port.device - ) - errors, api_stick = await validate_usb_connection(self.hass, device_path) - if not errors: - await self.async_set_unique_id(api_stick.mac) - return self.async_create_entry( - title="Stick", data={CONF_USB_PATH: device_path, PW_TYPE: STICK} - ) - return self.async_show_form( - step_id="user_usb", - data_schema=vol.Schema( - {vol.Required(CONF_USB_PATH): vol.In(list_of_ports)} - ), - errors=errors, - ) - - async def async_step_manual_path( - self, user_input: dict[str, Any] | None = None - ) -> FlowResult: # pw-beta usb - """Step when manual path to device.""" - errors: dict[str, str] = {} - if user_input is not None: - user_input.pop(FLOW_TYPE, None) - device_path = await self.hass.async_add_executor_job( - usb.get_serial_by_id, user_input.get(CONF_USB_PATH) - ) - errors, api_stick = await validate_usb_connection(self.hass, device_path) - if not errors: - await self.async_set_unique_id(api_stick.mac) - return self.async_create_entry( - title="Stick", data={CONF_USB_PATH: device_path} - ) - return self.async_show_form( - step_id="manual_path", - data_schema=vol.Schema( - { - vol.Required( - CONF_USB_PATH, default="/dev/ttyUSB0" or vol.UNDEFINED - ): str - } - ), - errors=errors, - ) + return await self.async_step_user() - async def async_step_user_gateway( + async def async_step_user( self, user_input: dict[str, Any] | None = None ) -> FlowResult: """Handle the initial step when using network/gateway setups.""" @@ -318,7 +189,7 @@ async def async_step_user_gateway( if not user_input: return self.async_show_form( - step_id="user_gateway", + step_id="user", data_schema=_base_gw_schema(self.discovery_info, None), errors=errors, ) @@ -344,7 +215,7 @@ async def async_step_user_gateway( if errors: return self.async_show_form( - step_id="user_gateway", + step_id="user", data_schema=_base_gw_schema(None, user_input), errors=errors, ) @@ -354,27 +225,8 @@ async def async_step_user_gateway( ) self._abort_if_unique_id_configured() - user_input[PW_TYPE] = API return self.async_create_entry(title=api.smile_name, data=user_input) - async def async_step_user( - self, user_input: dict[str, Any] | None = None - ) -> FlowResult: - """Handle the initial step when using network/gateway setups.""" - errors: dict[str, str] = {} - if user_input is not None: - if user_input[FLOW_TYPE] == FLOW_NET: - return await self.async_step_user_gateway() - - if user_input[FLOW_TYPE] == FLOW_USB: - return await self.async_step_user_usb() - - return self.async_show_form( - step_id="user", - data_schema=CONNECTION_SCHEMA, - errors=errors, - ) - @staticmethod @callback def async_get_options_flow( diff --git a/custom_components/plugwise/const.py b/custom_components/plugwise/const.py index 77afe7424..df16cd1d0 100644 --- a/custom_components/plugwise/const.py +++ b/custom_components/plugwise/const.py @@ -3,10 +3,7 @@ import logging from typing import Final -import voluptuous as vol # pw-beta usb - from homeassistant.const import Platform -from homeassistant.helpers import config_validation as cv DOMAIN: Final = "plugwise" @@ -17,19 +14,14 @@ CONF_HOMEKIT_EMULATION: Final = "homekit_emulation" # pw-beta options CONF_REFRESH_INTERVAL: Final = "refresh_interval" # pw-beta options CONF_MANUAL_PATH: Final = "Enter Manually" -GATEWAY: Final = "gateway" -PW_TYPE: Final = "plugwise_type" SMILE: Final = "smile" -STICK: Final = "stick" STRETCH: Final = "stretch" STRETCH_USERNAME: Final = "stretch" -USB: Final = "usb" FLOW_NET: Final = "Network: Smile/Stretch" FLOW_SMILE: Final = "Smile (Adam/Anna/P1)" FLOW_STRETCH: Final = "Stretch (Stretch)" FLOW_TYPE: Final = "flow_type" -FLOW_USB: Final = "USB: Stick" UNDO_UPDATE_LISTENER: Final = "undo_update_listener" @@ -71,85 +63,3 @@ "smile_open_therm": "Adam", "stretch": "Stretch", } - -# --- Const for Plugwise USB-stick. - -PLATFORMS_USB: Final[list[str]] = [ - Platform.BINARY_SENSOR, - Platform.SENSOR, - Platform.SWITCH, -] -CONF_USB_PATH: Final = "usb_path" - -# Callback types -CB_NEW_NODE: Final = "NEW_NODE" -CB_JOIN_REQUEST: Final = "JOIN_REQUEST" - - -# USB generic device constants -USB_AVAILABLE_ID: Final = "available" - -ATTR_MAC_ADDRESS: Final = "mac" - -SERVICE_USB_DEVICE_ADD: Final = "device_add" -SERVICE_USB_DEVICE_REMOVE: Final = "device_remove" -SERVICE_USB_DEVICE_SCHEMA: Final = vol.Schema( - {vol.Required(ATTR_MAC_ADDRESS): cv.string} -) # pw-beta usb - - -# USB Relay device constants -USB_RELAY_ID: Final = "relay" - - -# USB SED (battery powered) device constants -ATTR_SED_STAY_ACTIVE: Final = "stay_active" -ATTR_SED_SLEEP_FOR: Final = "sleep_for" -ATTR_SED_MAINTENANCE_INTERVAL: Final = "maintenance_interval" -ATTR_SED_CLOCK_SYNC: Final = "clock_sync" -ATTR_SED_CLOCK_INTERVAL: Final = "clock_interval" - -SERVICE_USB_SED_BATTERY_CONFIG: Final = "configure_battery_savings" -SERVICE_USB_SED_BATTERY_CONFIG_SCHEMA: Final = { - vol.Required(ATTR_SED_STAY_ACTIVE): vol.All( - vol.Coerce(int), vol.Range(min=1, max=120) - ), - vol.Required(ATTR_SED_SLEEP_FOR): vol.All( - vol.Coerce(int), vol.Range(min=10, max=60) - ), - vol.Required(ATTR_SED_MAINTENANCE_INTERVAL): vol.All( - vol.Coerce(int), vol.Range(min=5, max=1440) - ), - vol.Required(ATTR_SED_CLOCK_SYNC): cv.boolean, - vol.Required(ATTR_SED_CLOCK_INTERVAL): vol.All( - vol.Coerce(int), vol.Range(min=60, max=10080) - ), -} - - -# USB Scan device constants -USB_MOTION_ID: Final = "motion" - -ATTR_SCAN_DAYLIGHT_MODE: Final = "day_light" -ATTR_SCAN_SENSITIVITY_MODE: Final = "sensitivity_mode" -ATTR_SCAN_RESET_TIMER: Final = "reset_timer" - -SCAN_SENSITIVITY_HIGH: Final = "high" -SCAN_SENSITIVITY_MEDIUM: Final = "medium" -SCAN_SENSITIVITY_OFF: Final = "off" -SCAN_SENSITIVITY_MODES = [ - SCAN_SENSITIVITY_HIGH, - SCAN_SENSITIVITY_MEDIUM, - SCAN_SENSITIVITY_OFF, -] - -SERVICE_USB_SCAN_CONFIG: Final = "configure_scan" -SERVICE_USB_SCAN_CONFIG_SCHEMA = ( - { - vol.Required(ATTR_SCAN_SENSITIVITY_MODE): vol.In(SCAN_SENSITIVITY_MODES), - vol.Required(ATTR_SCAN_RESET_TIMER): vol.All( - vol.Coerce(int), vol.Range(min=1, max=240) - ), - vol.Required(ATTR_SCAN_DAYLIGHT_MODE): cv.boolean, - }, -) diff --git a/custom_components/plugwise/gateway.py b/custom_components/plugwise/gateway.py deleted file mode 100644 index 9e079f36f..000000000 --- a/custom_components/plugwise/gateway.py +++ /dev/null @@ -1,139 +0,0 @@ -"""Plugwise platform for Home Assistant Core.""" -from __future__ import annotations - -from typing import Any - -import voluptuous as vol - -from homeassistant.components.switch import DOMAIN as SWITCH_DOMAIN -from homeassistant.config_entries import ConfigEntry -from homeassistant.const import Platform -from homeassistant.core import HomeAssistant, callback -from homeassistant.helpers import device_registry as dr, entity_registry as er -from plugwise.exceptions import PlugwiseError - -from .const import ( - COORDINATOR, - DOMAIN, - GATEWAY, - LOGGER, - PLATFORMS_GATEWAY, - PW_TYPE, - SERVICE_DELETE, - UNDO_UPDATE_LISTENER, -) -from .const import CONF_REFRESH_INTERVAL # pw-beta options -from .coordinator import PlugwiseDataUpdateCoordinator - - -async def async_setup_entry_gw(hass: HomeAssistant, entry: ConfigEntry) -> bool: - """Set up Plugwise Smiles from a config entry.""" - await er.async_migrate_entries(hass, entry.entry_id, async_migrate_entity_entry) - - cooldown = 1.5 # pw-beta frontend refresh-interval - if ( - custom_refresh := entry.options.get(CONF_REFRESH_INTERVAL) - ) is not None: # pragma: no cover - cooldown = custom_refresh - LOGGER.debug("DUC cooldown interval: %s", cooldown) - - coordinator = PlugwiseDataUpdateCoordinator( - hass, entry, cooldown - ) # pw-beta - cooldown, update_interval as extra - await coordinator.async_config_entry_first_refresh() - # Migrate a changed sensor unique_id - migrate_sensor_entities(hass, coordinator) - - undo_listener = entry.add_update_listener(_update_listener) # pw-beta - - hass.data.setdefault(DOMAIN, {})[entry.entry_id] = { - COORDINATOR: coordinator, # pw-beta - PW_TYPE: GATEWAY, # pw-beta - UNDO_UPDATE_LISTENER: undo_listener, # pw-beta - } - - device_registry = dr.async_get(hass) - device_registry.async_get_or_create( - config_entry_id=entry.entry_id, - identifiers={(DOMAIN, str(coordinator.api.gateway_id))}, - manufacturer="Plugwise", - model=coordinator.api.smile_model, - name=coordinator.api.smile_name, - sw_version=coordinator.api.smile_version[0], - ) - - async def delete_notification( - self, - ): # pragma: no cover # pw-beta: HA service - delete_notification - """Service: delete the Plugwise Notification.""" - LOGGER.debug( - "Service delete PW Notification called for %s", coordinator.api.smile_name - ) - try: - deleted = await coordinator.api.delete_notification() - LOGGER.debug("PW Notification deleted: %s", deleted) - except PlugwiseError: - LOGGER.debug( - "Failed to delete the Plugwise Notification for %s", - coordinator.api.smile_name, - ) - - await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS_GATEWAY) - - for component in PLATFORMS_GATEWAY: # pw-beta - if component == Platform.BINARY_SENSOR: - hass.services.async_register( - DOMAIN, SERVICE_DELETE, delete_notification, schema=vol.Schema({}) - ) - - return True - - -async def _update_listener( - hass: HomeAssistant, entry: ConfigEntry -): # pragma: no cover # pw-beta - """Handle options update.""" - await hass.config_entries.async_reload(entry.entry_id) - - -async def async_unload_entry_gw(hass: HomeAssistant, entry: ConfigEntry): - """Unload a config entry.""" - if unload_ok := await hass.config_entries.async_unload_platforms( - entry, PLATFORMS_GATEWAY - ): - hass.data[DOMAIN].pop(entry.entry_id) - return unload_ok - - -@callback -def async_migrate_entity_entry(entry: er.RegistryEntry) -> dict[str, Any] | None: - """Migrate Plugwise entity entries. - - - Migrates unique ID from old relay switches to the new unique ID - """ - if entry.domain == SWITCH_DOMAIN and entry.unique_id.endswith("-plug"): - return {"new_unique_id": entry.unique_id.replace("-plug", "-relay")} - - # No migration needed - return None - - -def migrate_sensor_entities( - hass: HomeAssistant, - coordinator: PlugwiseDataUpdateCoordinator, -) -> None: - """Migrate Sensors if needed.""" - ent_reg = er.async_get(hass) - - # Migrate opentherm_outdoor_temperature # pw-beta add to Core - # to opentherm_outdoor_air_temperature sensor - for device_id, device in coordinator.data.devices.items(): - if device["dev_class"] != "heater_central": # pw-beta add to Core - continue - - old_unique_id = f"{device_id}-outdoor_temperature" - if entity_id := ent_reg.async_get_entity_id( - Platform.SENSOR, DOMAIN, old_unique_id - ): - new_unique_id = f"{device_id}-outdoor_air_temperature" - ent_reg.async_update_entity(entity_id, new_unique_id=new_unique_id) diff --git a/custom_components/plugwise/manifest.json b/custom_components/plugwise/manifest.json index 392390138..01fda7121 100644 --- a/custom_components/plugwise/manifest.json +++ b/custom_components/plugwise/manifest.json @@ -1,13 +1,15 @@ { "domain": "plugwise", "name": "Plugwise Beta", - "after_dependencies": ["usb", "zeroconf"], + "after_dependencies": ["zeroconf"], "codeowners": ["@CoMPaTech", "@bouwew", "@brefra"], "config_flow": true, "documentation": "https://github.com/plugwise/plugwise-beta", "integration_type": "hub", "iot_class": "local_polling", "loggers": ["plugwise"], - "requirements": ["plugwise==0.27.10"], - "version": "0.34.10" + "requirements": [ + "https://test-files.pythonhosted.org/packages/44/ed/69e2a8587b30162372b843d436a5036c25a3bcdff99c423b4b94666b5de3/plugwise-0.31.0a1.tar.gz#plugwise==0.31.a01" + ], + "version": "0.40.0a1" } diff --git a/custom_components/plugwise/models.py b/custom_components/plugwise/models.py index 4b135d555..56c7272da 100644 --- a/custom_components/plugwise/models.py +++ b/custom_components/plugwise/models.py @@ -3,10 +3,7 @@ from dataclasses import dataclass -from homeassistant.components.binary_sensor import ( - BinarySensorDeviceClass, - BinarySensorEntityDescription, -) +from homeassistant.components.binary_sensor import BinarySensorEntityDescription from homeassistant.components.sensor import ( SensorDeviceClass, SensorEntityDescription, @@ -16,20 +13,18 @@ from homeassistant.const import ( LIGHT_LUX, PERCENTAGE, - SIGNAL_STRENGTH_DECIBELS_MILLIWATT, EntityCategory, UnitOfElectricPotential, UnitOfEnergy, UnitOfPower, UnitOfPressure, UnitOfTemperature, - UnitOfTime, UnitOfVolume, UnitOfVolumeFlowRate, ) from homeassistant.helpers.entity import EntityDescription -from .const import SMILE, STICK, USB_MOTION_ID, USB_RELAY_ID +from .const import SMILE @dataclass @@ -77,109 +72,6 @@ class PlugwiseBinarySensorEntityDescription( PW_SENSOR_TYPES: tuple[PlugwiseSensorEntityDescription, ...] = ( - PlugwiseSensorEntityDescription( - plugwise_api=STICK, - key="power_1s", - name="Power usage", - device_class=SensorDeviceClass.POWER, - native_unit_of_measurement=UnitOfPower.WATT, - state_request_method="current_power_usage", - ), - PlugwiseSensorEntityDescription( - plugwise_api=STICK, - key="energy_consumption_today", - name="Energy consumption today", - device_class=SensorDeviceClass.ENERGY, - state_class=SensorStateClass.TOTAL_INCREASING, - native_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR, - state_request_method="energy_consumption_today", - ), - PlugwiseSensorEntityDescription( - plugwise_api=STICK, - key="ping", - name="Ping roundtrip", - icon="mdi:speedometer", - native_unit_of_measurement=UnitOfTime.MILLISECONDS, - state_request_method="ping", - entity_registry_enabled_default=False, - entity_category=EntityCategory.DIAGNOSTIC, - ), - PlugwiseSensorEntityDescription( - plugwise_api=STICK, - key="power_8s", - name="Power usage 8 seconds", - device_class=SensorDeviceClass.POWER, - native_unit_of_measurement=UnitOfPower.WATT, - state_request_method="current_power_usage_8_sec", - entity_registry_enabled_default=False, - ), - PlugwiseSensorEntityDescription( - plugwise_api=STICK, - key="RSSI_in", - name="Inbound RSSI", - device_class=SensorDeviceClass.SIGNAL_STRENGTH, - native_unit_of_measurement=SIGNAL_STRENGTH_DECIBELS_MILLIWATT, - state_request_method="rssi_in", - entity_registry_enabled_default=False, - entity_category=EntityCategory.DIAGNOSTIC, - ), - PlugwiseSensorEntityDescription( - plugwise_api=STICK, - key="RSSI_out", - name="Outbound RSSI", - device_class=SensorDeviceClass.SIGNAL_STRENGTH, - native_unit_of_measurement=SIGNAL_STRENGTH_DECIBELS_MILLIWATT, - state_request_method="rssi_out", - entity_registry_enabled_default=False, - entity_category=EntityCategory.DIAGNOSTIC, - ), - PlugwiseSensorEntityDescription( - plugwise_api=STICK, - key="power_con_cur_hour", - name="Power consumption current hour", - icon="mdi:lightning-bolt", - native_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR, - state_request_method="power_consumption_current_hour", - entity_registry_enabled_default=False, - ), - PlugwiseSensorEntityDescription( - plugwise_api=STICK, - key="power_prod_cur_hour", - name="Power production current hour", - icon="mdi:lightning-bolt", - native_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR, - state_request_method="power_production_current_hour", - entity_registry_enabled_default=False, - ), - PlugwiseSensorEntityDescription( - plugwise_api=STICK, - key="power_con_today", - name="Power consumption today", - icon="mdi:lightning-bolt", - native_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR, - state_request_method="power_consumption_today", - entity_registry_enabled_default=False, - ), - PlugwiseSensorEntityDescription( - plugwise_api=STICK, - key="power_con_prev_hour", - name="Power consumption previous hour", - icon="mdi:lightning-bolt", - native_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR, - state_class=SensorStateClass.TOTAL, - state_request_method="power_consumption_previous_hour", - entity_registry_enabled_default=False, - ), - PlugwiseSensorEntityDescription( - plugwise_api=STICK, - key="power_con_yesterday", - name="Power consumption yesterday", - icon="mdi:lightning-bolt", - native_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR, - state_class=SensorStateClass.TOTAL, - state_request_method="power_consumption_yesterday", - entity_registry_enabled_default=False, - ), PlugwiseSensorEntityDescription( plugwise_api=SMILE, key="setpoint", @@ -565,13 +457,6 @@ class PlugwiseBinarySensorEntityDescription( ) PW_SWITCH_TYPES: tuple[PlugwiseSwitchEntityDescription, ...] = ( - PlugwiseSwitchEntityDescription( - plugwise_api=STICK, - key=USB_RELAY_ID, - device_class=SwitchDeviceClass.OUTLET, - name="Relay state", - state_request_method="relay_state", - ), PlugwiseSwitchEntityDescription( plugwise_api=SMILE, key="dhw_cm_switch", @@ -605,13 +490,6 @@ class PlugwiseBinarySensorEntityDescription( ) PW_BINARY_SENSOR_TYPES: tuple[PlugwiseBinarySensorEntityDescription, ...] = ( - PlugwiseBinarySensorEntityDescription( - plugwise_api=STICK, - key=USB_MOTION_ID, - name="Motion", - device_class=BinarySensorDeviceClass.MOTION, - state_request_method="motion", - ), PlugwiseBinarySensorEntityDescription( plugwise_api=SMILE, key="compressor_state", diff --git a/custom_components/plugwise/sensor.py b/custom_components/plugwise/sensor.py index 5be6e0691..1b63a445f 100644 --- a/custom_components/plugwise/sensor.py +++ b/custom_components/plugwise/sensor.py @@ -3,26 +3,14 @@ from homeassistant.components.sensor import SensorEntity from homeassistant.config_entries import ConfigEntry -from homeassistant.const import Platform from homeassistant.core import HomeAssistant from homeassistant.helpers.entity_platform import AddEntitiesCallback -from plugwise.nodes import PlugwiseNode +from .const import COORDINATOR # pw-beta from .const import DOMAIN, LOGGER - -# isort: off -from .const import COORDINATOR, PW_TYPE # pw-beta -from .const import ( - CB_NEW_NODE, - STICK, - USB, -) # pw-beta usb - -# isort: on from .coordinator import PlugwiseDataUpdateCoordinator from .entity import PlugwiseEntity from .models import PW_SENSOR_TYPES, PlugwiseSensorEntityDescription -from .usb import PlugwiseUSBEntity # pw-beta usb PARALLEL_UPDATES = 0 @@ -31,47 +19,6 @@ async def async_setup_entry( hass: HomeAssistant, config_entry: ConfigEntry, async_add_entities: AddEntitiesCallback, -) -> None: - """Set up the Smile switches from a config entry.""" - if hass.data[DOMAIN][config_entry.entry_id][PW_TYPE] == USB: # pw-beta usb - return await async_setup_entry_usb(hass, config_entry, async_add_entities) - # Considered default and for earlier setups without usb/network config_flow - return await async_setup_entry_gateway(hass, config_entry, async_add_entities) - - -async def async_setup_entry_usb(hass, config_entry, async_add_entities): # pw-beta usb - """Set up Plugwise sensor based on config_entry.""" - api_stick = hass.data[DOMAIN][config_entry.entry_id][STICK] - - async def async_add_sensors(mac: str): - """Add plugwise sensors for device.""" - entities: list[USBSensor] = [] - entities.extend( - [ - USBSensor(api_stick.devices[mac], description) - for description in PW_SENSOR_TYPES - if description.plugwise_api == STICK - and description.key in api_stick.devices[mac].features - ] - ) - if entities: - async_add_entities(entities) - - for mac in hass.data[DOMAIN][config_entry.entry_id][Platform.SENSOR]: - hass.async_create_task(async_add_sensors(mac)) - - def discoved_device(mac: str): - """Add sensors for newly discovered device.""" - hass.async_create_task(async_add_sensors(mac)) - - # Listen for discovered nodes - api_stick.subscribe_stick_callback(discoved_device, CB_NEW_NODE) - - -async def async_setup_entry_gateway( - hass: HomeAssistant, - config_entry: ConfigEntry, - async_add_entities: AddEntitiesCallback, ) -> None: """Set up the Smile sensors from a config entry.""" coordinator = hass.data[DOMAIN][config_entry.entry_id][COORDINATOR] @@ -115,24 +62,3 @@ def __init__( def native_value(self) -> int | float | None: """Return the value reported by the sensor.""" return self.device["sensors"].get(self.entity_description.key) - - -# Github issue #265 -class USBSensor(PlugwiseUSBEntity, SensorEntity): # type: ignore[misc] # pw-beta usb - """Representation of a Plugwise USB sensor.""" - - def __init__( - self, node: PlugwiseNode, description: PlugwiseSensorEntityDescription - ) -> None: - """Initialize sensor entity.""" - super().__init__(node, description) - - @property - def native_value(self) -> float | None: - """Return the native value of the sensor.""" - # Github issue #265 - state_value = getattr(self._node, self.entity_description.state_request_method) # type: ignore[attr-defined] - # /Github issue #265 - if state_value is not None: - return float(round(state_value, 3)) - return None diff --git a/custom_components/plugwise/switch.py b/custom_components/plugwise/switch.py index f25b67b6e..92275ad35 100644 --- a/custom_components/plugwise/switch.py +++ b/custom_components/plugwise/switch.py @@ -5,27 +5,14 @@ from homeassistant.components.switch import SwitchEntity from homeassistant.config_entries import ConfigEntry -from homeassistant.const import Platform from homeassistant.core import HomeAssistant from homeassistant.helpers.entity_platform import AddEntitiesCallback -from plugwise.nodes import PlugwiseNode -from .const import DOMAIN, LOGGER - -# isort: off -from .const import COORDINATOR, PW_TYPE # pw-beta -from .const import ( - CB_NEW_NODE, - SMILE, - STICK, - USB, -) - -# isort: on +from .const import COORDINATOR # pw-beta +from .const import DOMAIN, LOGGER, SMILE from .coordinator import PlugwiseDataUpdateCoordinator from .entity import PlugwiseEntity from .models import PW_SWITCH_TYPES, PlugwiseSwitchEntityDescription -from .usb import PlugwiseUSBEntity # pw-beta usb from .util import plugwise_command @@ -33,47 +20,6 @@ async def async_setup_entry( hass: HomeAssistant, config_entry: ConfigEntry, async_add_entities: AddEntitiesCallback, -) -> None: - """Set up the Smile switches from a config entry.""" - if hass.data[DOMAIN][config_entry.entry_id][PW_TYPE] == USB: # pw-beta usb - return await async_setup_entry_usb(hass, config_entry, async_add_entities) - # Considered default and for earlier setups without usb/network config_flow - return await async_setup_entry_gateway(hass, config_entry, async_add_entities) - - -async def async_setup_entry_usb(hass, config_entry, async_add_entities): # pw-beta usb - """Set up the USB switches from a config entry.""" - api_stick = hass.data[DOMAIN][config_entry.entry_id][STICK] - - async def async_add_switches(mac: str): - """Add plugwise switches.""" - entities: list[USBSwitch] = [] - entities.extend( - [ - USBSwitch(api_stick.devices[mac], description) - for description in PW_SWITCH_TYPES - if description.plugwise_api == STICK - and description.key in api_stick.devices[mac].features - ] - ) - if entities: - async_add_entities(entities) - - for mac in hass.data[DOMAIN][config_entry.entry_id][Platform.SWITCH]: - hass.async_create_task(async_add_switches(mac)) - - def discoved_device(mac: str): - """Add switches for newly discovered device.""" - hass.async_create_task(async_add_switches(mac)) - - # Listen for discovered nodes - api_stick.subscribe_stick_callback(discoved_device, CB_NEW_NODE) - - -async def async_setup_entry_gateway( - hass: HomeAssistant, - config_entry: ConfigEntry, - async_add_entities: AddEntitiesCallback, ) -> None: """Set up the Smile switches from a config entry.""" coordinator = hass.data[DOMAIN][config_entry.entry_id][COORDINATOR] @@ -129,28 +75,3 @@ async def async_turn_off(self, **kwargs: Any) -> None: self.entity_description.key, "off", ) - - -# Github issue #265 -class USBSwitch(PlugwiseUSBEntity, SwitchEntity): # type: ignore[misc] # pw-beta usb - """Representation of a Stick Node switch.""" - - def __init__( - self, node: PlugwiseNode, description: PlugwiseSwitchEntityDescription - ) -> None: - """Initialize a switch entity.""" - super().__init__(node, description) - - @property - def is_on(self) -> bool: - """Return true if the switch is on.""" - # Github issue #265 - return getattr(self._node, self.entity_description.state_request_method) # type: ignore[attr-defined] - - def turn_off(self, **kwargs): - """Instruct the switch to turn off.""" - setattr(self._node, self.entity_description.state_request_method, False) - - def turn_on(self, **kwargs): - """Instruct the switch to turn on.""" - setattr(self._node, self.entity_description.state_request_method, True) diff --git a/custom_components/plugwise/usb.py b/custom_components/plugwise/usb.py deleted file mode 100644 index 380361475..000000000 --- a/custom_components/plugwise/usb.py +++ /dev/null @@ -1,229 +0,0 @@ -"""Support for Plugwise devices connected to a Plugwise USB-stick.""" -import asyncio -import logging - -from homeassistant.config_entries import ConfigEntry -from homeassistant.const import EVENT_HOMEASSISTANT_STOP, Platform -from homeassistant.core import HomeAssistant -from homeassistant.exceptions import ConfigEntryNotReady -from homeassistant.helpers import device_registry as dr -from homeassistant.helpers.entity import Entity -from plugwise.exceptions import ( - CirclePlusError, - NetworkDown, - PortError, - StickInitError, - TimeoutException, -) -from plugwise.nodes import PlugwiseNode -from plugwise.stick import Stick - -from .const import ( - ATTR_MAC_ADDRESS, - CB_JOIN_REQUEST, - CONF_USB_PATH, - DOMAIN, - PLATFORMS_USB, - PW_TYPE, - SERVICE_USB_DEVICE_ADD, - SERVICE_USB_DEVICE_REMOVE, - SERVICE_USB_DEVICE_SCHEMA, - STICK, - UNDO_UPDATE_LISTENER, - USB, - USB_AVAILABLE_ID, - USB_MOTION_ID, - USB_RELAY_ID, -) -from .models import PlugwiseEntityDescription - -_LOGGER = logging.getLogger(__name__) - - -async def async_setup_entry_usb(hass: HomeAssistant, config_entry: ConfigEntry): - """Establish connection with plugwise USB-stick.""" - hass.data.setdefault(DOMAIN, {}) - device_registry = dr.async_get(hass) - - def discover_finished(): - """Create entities for all discovered nodes.""" - _LOGGER.debug( - "Successfully discovered %s out of %s registered nodes", - str(len(api_stick.devices)), - str(api_stick.joined_nodes), - ) - for component in PLATFORMS_USB: - hass.data[DOMAIN][config_entry.entry_id][component] = [] - - for mac, pw_device in api_stick.devices.items(): - # Skip unsupported devices - if pw_device is not None: - if USB_RELAY_ID in pw_device.features: - hass.data[DOMAIN][config_entry.entry_id][Platform.SWITCH].append( - mac - ) - if USB_MOTION_ID in pw_device.features: - hass.data[DOMAIN][config_entry.entry_id][ - Platform.BINARY_SENSOR - ].append(mac) - hass.data[DOMAIN][config_entry.entry_id][Platform.SENSOR].append(mac) - - asyncio.run_coroutine_threadsafe( - hass.config_entries.async_forward_entry_setups(config_entry, PLATFORMS_USB), - hass.loop, - ) - - def add_new_node(mac): - """Add Listener when a new Plugwise node joined the network.""" - device = device_registry.async_get_device({(DOMAIN, mac)}, set()) - hass.components.persistent_notification.async_create( - title="New Plugwise device", - message=( - "A new Plugwise device has been joined : \n\n" - f" - {api_stick.devices[mac].hardware_model} ({mac[-5:]})\n\n" - f"Configure this device at the [device dashboard](/config/devices/device/{device.id})" - ), - ) - - api_stick.auto_update() - - if config_entry.pref_disable_new_entities: - _LOGGER.debug("Configuring stick NOT to accept any new join requests") - api_stick.allow_join_requests(True, False) - else: - _LOGGER.debug("Configuring stick to automatically accept new join requests") - api_stick.allow_join_requests(True, True) - api_stick.subscribe_stick_callback(add_new_node, CB_JOIN_REQUEST) - - def shutdown(event): - hass.async_add_executor_job(api_stick.disconnect) - - api_stick = Stick(config_entry.data[CONF_USB_PATH]) - hass.data[DOMAIN][config_entry.entry_id] = {PW_TYPE: USB, STICK: api_stick} - try: - _LOGGER.debug("Connect to USB-Stick") - await hass.async_add_executor_job(api_stick.connect) - _LOGGER.debug("Initialize USB-stick") - await hass.async_add_executor_job(api_stick.initialize_stick) - _LOGGER.debug("Discover Circle+ node") - await hass.async_add_executor_job(api_stick.initialize_circle_plus) - except PortError: - _LOGGER.error("Connecting to Plugwise USBstick communication failed") - raise ConfigEntryNotReady from PortError - except StickInitError: - _LOGGER.error("Initializing of Plugwise USBstick communication failed") - await hass.async_add_executor_job(api_stick.disconnect) - raise ConfigEntryNotReady from StickInitError - except NetworkDown: - _LOGGER.warning("Plugwise zigbee network down") - await hass.async_add_executor_job(api_stick.disconnect) - raise ConfigEntryNotReady from NetworkDown - except CirclePlusError: - _LOGGER.warning("Failed to connect to Circle+ node") - await hass.async_add_executor_job(api_stick.disconnect) - raise ConfigEntryNotReady from CirclePlusError - except TimeoutException: - _LOGGER.warning("Timeout") - await hass.async_add_executor_job(api_stick.disconnect) - raise ConfigEntryNotReady from TimeoutException - _LOGGER.debug("Start discovery of registered nodes") - api_stick.scan(discover_finished) - - # Listen when EVENT_HOMEASSISTANT_STOP is fired - hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, shutdown) - - # Listen for entry updates - hass.data[DOMAIN][config_entry.entry_id][ - UNDO_UPDATE_LISTENER - ] = config_entry.add_update_listener(_async_update_listener) - - async def device_add(service): - """Manually add device to Plugwise zigbee network.""" - api_stick.node_join(service.data[ATTR_MAC_ADDRESS]) - - async def device_remove(service): - """Manually remove device from Plugwise zigbee network.""" - api_stick.node_unjoin(service.data[ATTR_MAC_ADDRESS]) - _LOGGER.debug( - "Send request to remove device using mac %s from Plugwise network", - service.data[ATTR_MAC_ADDRESS], - ) - device_entry = device_registry.async_get_device( - {(DOMAIN, service.data[ATTR_MAC_ADDRESS])}, set() - ) - if device_entry: - _LOGGER.debug( - "Remove device %s from Home Assistant", service.data[ATTR_MAC_ADDRESS] - ) - device_registry.async_remove_device(device_entry.id) - - hass.services.async_register( - DOMAIN, SERVICE_USB_DEVICE_ADD, device_add, SERVICE_USB_DEVICE_SCHEMA - ) - hass.services.async_register( - DOMAIN, SERVICE_USB_DEVICE_REMOVE, device_remove, SERVICE_USB_DEVICE_SCHEMA - ) - - return True - - -async def async_unload_entry_usb(hass: HomeAssistant, config_entry: ConfigEntry): - """Unload the Plugwise stick connection.""" - - unload_ok = await hass.config_entries.async_unload_platforms( - config_entry, PLATFORMS_USB - ) - hass.data[DOMAIN][config_entry.entry_id][UNDO_UPDATE_LISTENER]() - if unload_ok: - api_stick = hass.data[DOMAIN][config_entry.entry_id]["stick"] - await hass.async_add_executor_job(api_stick.disconnect) - hass.data[DOMAIN].pop(config_entry.entry_id) - return unload_ok - - -async def _async_update_listener(hass: HomeAssistant, config_entry: ConfigEntry): - """Handle options update.""" - await hass.config_entries.async_reload(config_entry.entry_id) - - -class PlugwiseUSBEntity(Entity): - """Base class for Plugwise USB entities.""" - - entity_description: PlugwiseEntityDescription - - # Github issue #265: the entity_description is not working accordingly - def __init__( - self, node: PlugwiseNode, entity_description: PlugwiseEntityDescription - ) -> None: - """Initialize a Pluswise USB entity.""" - self._attr_available = node.available - self._attr_device_info = { - "identifiers": {(DOMAIN, node.mac)}, - "name": f"{node.hardware_model} ({node.mac})", - "manufacturer": "Plugwise", - "model": node.hardware_model, - "sw_version": f"{node.firmware_version}", - } - self._attr_name = f"{entity_description.name} ({node.mac[-5:]})" - # Github issue #265 - self._attr_should_poll = entity_description.should_poll # type: ignore[attr-defined] - # /Github issue #265 - self._attr_unique_id = f"{node.mac}-{entity_description.key}" - self._node = node - self.entity_description = entity_description - self.node_callbacks = (USB_AVAILABLE_ID, entity_description.key) - - async def async_added_to_hass(self): - """Subscribe for updates.""" - for node_callback in self.node_callbacks: - self._node.subscribe_callback(self.sensor_update, node_callback) - - async def async_will_remove_from_hass(self): - """Unsubscribe to updates.""" - for node_callback in self.node_callbacks: - self._node.unsubscribe_callback(self.sensor_update, node_callback) - - def sensor_update(self, state): - """Handle status update of Entity.""" - self.schedule_update_ha_state() - self._attr_available = self._node.available diff --git a/old_dependabot.yml b/old_dependabot.yml deleted file mode 100644 index 639cd13ee..000000000 --- a/old_dependabot.yml +++ /dev/null @@ -1,11 +0,0 @@ -# To get started with Dependabot version updates, you'll need to specify which -# package ecosystems to update and where the package manifests are located. -# Please see the documentation for all configuration options: -# https://help.github.com/github/administering-a-repository/configuration-options-for-dependency-updates - -# version: 2 -# updates: -# - package-ecosystem: "github-actions" -# directory: "/" -# schedule: -# interval: "daily" diff --git a/scripts/core-testing.sh b/scripts/core-testing.sh index 716ed3199..9dd2701da 100755 --- a/scripts/core-testing.sh +++ b/scripts/core-testing.sh @@ -182,7 +182,9 @@ if [ -z "${GITHUB_ACTIONS}" ] || [ "$1" == "pip_prep" ] ; then echo " - ruff" pip install --upgrade -q ruff echo "" - module=$(grep require ../custom_components/plugwise/manifest.json | cut -f 4 -d '"') + # When using test.py prettier makes multi-line, so use jq + module=$(jq '.requirements[]' ../custom_components/plugwise/manifest.json | tr -d '"') + #module=$(grep require ../custom_components/plugwise/manifest.json | cut -f 4 -d '"') echo "Checking manifest for current python-plugwise to install: ${module}" echo "" pip install --upgrade -q --disable-pip-version-check "${module}" diff --git a/tests/components/plugwise/conftest.py b/tests/components/plugwise/conftest.py index ae3963886..880715820 100644 --- a/tests/components/plugwise/conftest.py +++ b/tests/components/plugwise/conftest.py @@ -8,7 +8,7 @@ import pytest -from homeassistant.components.plugwise.const import API, DOMAIN, PW_TYPE +from homeassistant.components.plugwise.const import DOMAIN from homeassistant.const import ( CONF_HOST, CONF_MAC, @@ -39,7 +39,6 @@ def mock_config_entry() -> MockConfigEntry: CONF_PASSWORD: "test-password", CONF_PORT: 80, CONF_USERNAME: "smile", - PW_TYPE: API, }, unique_id="smile98765", ) diff --git a/tests/components/plugwise/test_config_flow.py b/tests/components/plugwise/test_config_flow.py index ba24ff3c4..cb4b44fc0 100644 --- a/tests/components/plugwise/test_config_flow.py +++ b/tests/components/plugwise/test_config_flow.py @@ -2,23 +2,13 @@ from unittest.mock import AsyncMock, MagicMock, patch import pytest -import serial.tools.list_ports -from voluptuous.error import MultipleInvalid from homeassistant.components import zeroconf -from homeassistant.components.plugwise.config_flow import CONF_MANUAL_PATH from homeassistant.components.plugwise.const import ( - API, CONF_HOMEKIT_EMULATION, CONF_REFRESH_INTERVAL, - CONF_USB_PATH, DEFAULT_PORT, DOMAIN, - FLOW_NET, - FLOW_TYPE, - FLOW_USB, - PW_TYPE, - STICK, ) from homeassistant.components.zeroconf import ZeroconfServiceInfo from homeassistant.config_entries import SOURCE_USER, SOURCE_ZEROCONF @@ -38,10 +28,6 @@ InvalidAuthentication, InvalidSetupError, InvalidXMLError, - NetworkDown, - ResponseError, - StickInitError, - TimeoutException, UnsupportedDeviceError, ) @@ -52,8 +38,6 @@ TEST_HOSTNAME2 = "stretchabc" TEST_PASSWORD = "test_password" TEST_PORT = 81 -TEST_USBPORT = "/dev/ttyUSB1" -TEST_USBPORT2 = "/dev/ttyUSB2" TEST_USERNAME = "smile" TEST_USERNAME2 = "stretch" @@ -123,66 +107,11 @@ def mock_smile(): smile_mock.ConnectionFailedError = ConnectionFailedError smile_mock.InvalidAuthentication = InvalidAuthentication smile_mock.InvalidXMLError = InvalidXMLError - smile_mock.ResponseError = ResponseError smile_mock.UnsupportedDeviceError = UnsupportedDeviceError smile_mock.return_value.connect.return_value = True yield smile_mock.return_value -def com_port(): - """Mock of a serial port.""" - port = serial.tools.list_ports_common.ListPortInfo(TEST_USBPORT) - port.serial_number = "1234" - port.manufacturer = "Virtual serial port" - port.device = TEST_USBPORT - port.description = "Some serial port" - return port - - -async def test_form_flow_gateway( - hass: HomeAssistant, - mock_setup_entry: AsyncMock, - mock_smile_config_flow: MagicMock, -) -> None: - """Test we get the form for Plugwise Gateway product type.""" - result = await hass.config_entries.flow.async_init( - DOMAIN, context={CONF_SOURCE: SOURCE_USER} - ) - assert result.get("type") == FlowResultType.FORM - assert result.get("errors") == {} - assert result.get("step_id") == "user" - assert "flow_id" in result - - result = await hass.config_entries.flow.async_configure( - result["flow_id"], user_input={FLOW_TYPE: FLOW_NET} - ) - assert result.get("type") == FlowResultType.FORM - assert result.get("errors") == {} - assert result.get("step_id") == "user_gateway" - - -async def test_form_flow_usb( - hass: HomeAssistant, - mock_setup_entry: AsyncMock, - mock_smile_config_flow: MagicMock, -) -> None: - """Test we get the form for Plugwise USB product type.""" - result = await hass.config_entries.flow.async_init( - DOMAIN, context={CONF_SOURCE: SOURCE_USER} - ) - assert result.get("type") == FlowResultType.FORM - assert result.get("errors") == {} - assert result.get("step_id") == "user" - assert "flow_id" in result - - result = await hass.config_entries.flow.async_configure( - result["flow_id"], user_input={FLOW_TYPE: FLOW_USB} - ) - assert result.get("type") == FlowResultType.FORM - assert result.get("errors") == {} - assert result.get("step_id") == "user_usb" - - async def test_form( hass: HomeAssistant, mock_setup_entry: AsyncMock, @@ -190,12 +119,11 @@ async def test_form( ) -> None: """Test the full user configuration flow.""" result = await hass.config_entries.flow.async_init( - DOMAIN, context={CONF_SOURCE: SOURCE_USER}, data={FLOW_TYPE: FLOW_NET} + DOMAIN, context={CONF_SOURCE: SOURCE_USER} ) assert result.get("type") == FlowResultType.FORM assert result.get("errors") == {} - assert result.get("step_id") == "user_gateway" - assert "flow_id" in result + assert result.get("step_id") == "user" result2 = await hass.config_entries.flow.async_configure( result["flow_id"], @@ -213,7 +141,6 @@ async def test_form( CONF_PASSWORD: TEST_PASSWORD, CONF_PORT: DEFAULT_PORT, CONF_USERNAME: TEST_USERNAME, - PW_TYPE: API, } assert len(mock_setup_entry.mock_calls) == 1 @@ -242,7 +169,7 @@ async def test_zeroconf_form( ) assert result.get("type") == FlowResultType.FORM assert result.get("errors") == {} - assert result.get("step_id") == "user_gateway" + assert result.get("step_id") == "user" assert "flow_id" in result result2 = await hass.config_entries.flow.async_configure( @@ -258,7 +185,6 @@ async def test_zeroconf_form( CONF_PASSWORD: TEST_PASSWORD, CONF_PORT: DEFAULT_PORT, CONF_USERNAME: TEST_USERNAME, - PW_TYPE: API, } assert len(mock_setup_entry.mock_calls) == 1 @@ -278,7 +204,7 @@ async def test_zeroconf_stretch_form( ) assert result.get("type") == FlowResultType.FORM assert result.get("errors") == {} - assert result.get("step_id") == "user_gateway" + assert result.get("step_id") == "user" assert "flow_id" in result result2 = await hass.config_entries.flow.async_configure( @@ -294,7 +220,6 @@ async def test_zeroconf_stretch_form( CONF_PASSWORD: TEST_PASSWORD, CONF_PORT: DEFAULT_PORT, CONF_USERNAME: TEST_USERNAME2, - PW_TYPE: API, } assert len(mock_setup_entry.mock_calls) == 1 @@ -324,7 +249,7 @@ async def test_zeroconf_abort_anna_with_adam(hass: HomeAssistant) -> None: data=TEST_DISCOVERY_ANNA, ) assert result.get("type") == FlowResultType.FORM - assert result.get("step_id") == "user_gateway" + assert result.get("step_id") == "user" flows_in_progress = hass.config_entries.flow.async_progress() assert len(flows_in_progress) == 1 @@ -338,7 +263,7 @@ async def test_zeroconf_abort_anna_with_adam(hass: HomeAssistant) -> None: ) assert result2.get("type") == FlowResultType.FORM - assert result2.get("step_id") == "user_gateway" + assert result2.get("step_id") == "user" flows_in_progress = hass.config_entries.flow.async_progress() assert len(flows_in_progress) == 1 @@ -409,7 +334,6 @@ async def test_zercoconf_discovery_update_configuration( (InvalidAuthentication, "invalid_auth"), (InvalidSetupError, "invalid_setup"), (InvalidXMLError, "response_error"), - (ResponseError, "response_error"), (RuntimeError, "unknown"), (UnsupportedDeviceError, "unsupported"), ], @@ -433,16 +357,13 @@ async def test_flow_errors( mock_smile_config_flow.connect.side_effect = side_effect result2 = await hass.config_entries.flow.async_configure( - result["flow_id"], user_input={FLOW_TYPE: FLOW_NET} - ) - result3 = await hass.config_entries.flow.async_configure( - result2["flow_id"], + result["flow_id"], user_input={CONF_HOST: TEST_HOST, CONF_PASSWORD: TEST_PASSWORD}, ) - assert result3.get("type") == FlowResultType.FORM - assert result3.get("errors") == {"base": reason} - assert result3.get("step_id") == "user_gateway" + assert result2.get("type") == FlowResultType.FORM + assert result2.get("errors") == {"base": reason} + assert result2.get("step_id") == "user" assert len(mock_setup_entry.mock_calls) == 0 assert len(mock_smile_config_flow.connect.mock_calls) == 1 @@ -460,7 +381,6 @@ async def test_flow_errors( CONF_PASSWORD: TEST_PASSWORD, CONF_PORT: DEFAULT_PORT, CONF_USERNAME: TEST_USERNAME, - PW_TYPE: API, } assert len(mock_setup_entry.mock_calls) == 1 @@ -470,7 +390,8 @@ async def test_flow_errors( async def test_form_invalid_setup(hass, mock_smile): """Test we handle invalid setup.""" result = await hass.config_entries.flow.async_init( - DOMAIN, context={CONF_SOURCE: SOURCE_USER}, data={FLOW_TYPE: FLOW_NET} + DOMAIN, + context={CONF_SOURCE: SOURCE_USER}, ) mock_smile.connect.side_effect = InvalidSetupError @@ -488,7 +409,8 @@ async def test_form_invalid_setup(hass, mock_smile): async def test_form_invalid_auth(hass, mock_smile): """Test we handle invalid auth.""" result = await hass.config_entries.flow.async_init( - DOMAIN, context={CONF_SOURCE: SOURCE_USER}, data={FLOW_TYPE: FLOW_NET} + DOMAIN, + context={CONF_SOURCE: SOURCE_USER}, ) mock_smile.connect.side_effect = InvalidAuthentication @@ -506,7 +428,8 @@ async def test_form_invalid_auth(hass, mock_smile): async def test_form_cannot_connect(hass, mock_smile): """Test we handle cannot connect error.""" result = await hass.config_entries.flow.async_init( - DOMAIN, context={CONF_SOURCE: SOURCE_USER}, data={FLOW_TYPE: FLOW_NET} + DOMAIN, + context={CONF_SOURCE: SOURCE_USER}, ) mock_smile.connect.side_effect = ConnectionFailedError @@ -524,7 +447,8 @@ async def test_form_cannot_connect(hass, mock_smile): async def test_form_cannot_connect_port(hass, mock_smile): """Test we handle cannot connect to port error.""" result = await hass.config_entries.flow.async_init( - DOMAIN, context={CONF_SOURCE: SOURCE_USER}, data={FLOW_TYPE: FLOW_NET} + DOMAIN, + context={CONF_SOURCE: SOURCE_USER}, ) mock_smile.connect.side_effect = ConnectionFailedError @@ -546,7 +470,8 @@ async def test_form_cannot_connect_port(hass, mock_smile): async def test_form_other_problem(hass, mock_smile): """Test we handle cannot connect error.""" result = await hass.config_entries.flow.async_init( - DOMAIN, context={CONF_SOURCE: SOURCE_USER}, data={FLOW_TYPE: FLOW_NET} + DOMAIN, + context={CONF_SOURCE: SOURCE_USER}, ) mock_smile.connect.side_effect = TimeoutError @@ -598,224 +523,3 @@ async def test_options_flow_thermo(hass, mock_smile_anna_2) -> None: CONF_REFRESH_INTERVAL: 3.0, CONF_SCAN_INTERVAL: 60, } - - -@patch("serial.tools.list_ports.comports", MagicMock(return_value=[com_port()])) -@patch("plugwise.Stick.connect", MagicMock(return_value=None)) -@patch("plugwise.Stick.initialize_stick", MagicMock(return_value=None)) -@patch("plugwise.Stick.disconnect", MagicMock(return_value=None)) -async def test_user_flow_select(hass): - """Test user flow when USB-stick is selected from list.""" - port = com_port() - port_select = f"{port}, s/n: {port.serial_number} - {port.manufacturer}" - - result = await hass.config_entries.flow.async_init( - DOMAIN, context={CONF_SOURCE: SOURCE_USER}, data={FLOW_TYPE: FLOW_USB} - ) - result = await hass.config_entries.flow.async_configure( - result["flow_id"], user_input={CONF_USB_PATH: port_select} - ) - assert result["type"] == FlowResultType.CREATE_ENTRY - assert result["data"] == {PW_TYPE: STICK, CONF_USB_PATH: TEST_USBPORT} - - # Retry to ensure configuring the same port is not allowed - result = await hass.config_entries.flow.async_init( - DOMAIN, context={CONF_SOURCE: SOURCE_USER}, data={FLOW_TYPE: FLOW_USB} - ) - result = await hass.config_entries.flow.async_configure( - result["flow_id"], user_input={CONF_USB_PATH: port_select} - ) - assert result["type"] == FlowResultType.FORM - assert result["errors"] == {"base": "already_configured"} - - -async def test_user_flow_manual_selected_show_form(hass): - """Test user step form when manual path is selected.""" - result = await hass.config_entries.flow.async_init( - DOMAIN, context={CONF_SOURCE: SOURCE_USER}, data={FLOW_TYPE: FLOW_USB} - ) - result = await hass.config_entries.flow.async_configure( - result["flow_id"], - user_input={CONF_USB_PATH: CONF_MANUAL_PATH}, - ) - assert result["type"] == FlowResultType.FORM - assert result["step_id"] == "manual_path" - - -async def test_user_flow_manual(hass): - """Test user flow when USB-stick is manually entered.""" - result = await hass.config_entries.flow.async_init( - DOMAIN, context={CONF_SOURCE: SOURCE_USER}, data={FLOW_TYPE: FLOW_USB} - ) - result = await hass.config_entries.flow.async_configure( - result["flow_id"], - user_input={CONF_USB_PATH: CONF_MANUAL_PATH}, - ) - - with patch( - "homeassistant.components.plugwise.config_flow.Stick", - ) as usb_mock: - usb_mock.return_value.connect = MagicMock(return_value=True) - usb_mock.return_value.initialize_stick = MagicMock(return_value=True) - usb_mock.return_value.disconnect = MagicMock(return_value=True) - usb_mock.return_value.mac = "01:23:45:67:AB" - - result = await hass.config_entries.flow.async_configure( - result["flow_id"], - user_input={CONF_USB_PATH: TEST_USBPORT2}, - ) - assert result["type"] == FlowResultType.CREATE_ENTRY - assert result["data"] == {CONF_USB_PATH: TEST_USBPORT2} - - -async def test_invalid_connection(hass): - """Test invalid connection.""" - result = await hass.config_entries.flow.async_init( - DOMAIN, context={CONF_SOURCE: SOURCE_USER}, data={FLOW_TYPE: FLOW_USB} - ) - result = await hass.config_entries.flow.async_configure( - result["flow_id"], - user_input={CONF_USB_PATH: CONF_MANUAL_PATH}, - ) - - result = await hass.config_entries.flow.async_configure( - result["flow_id"], - {CONF_USB_PATH: "/dev/null"}, - ) - - assert result["type"] == FlowResultType.FORM - assert result["errors"] == {"base": "cannot_connect"} - - -async def test_empty_connection(hass): - """Test empty connection.""" - result = await hass.config_entries.flow.async_init( - DOMAIN, context={CONF_SOURCE: SOURCE_USER}, data={FLOW_TYPE: FLOW_USB} - ) - result = await hass.config_entries.flow.async_configure( - result["flow_id"], - user_input={CONF_USB_PATH: CONF_MANUAL_PATH}, - ) - - try: - result = await hass.config_entries.flow.async_configure( - result["flow_id"], - {CONF_USB_PATH: None}, - ) - pytest.fail("Empty connection was accepted") - except MultipleInvalid: - assert True - - assert result["type"] == FlowResultType.FORM - assert result["errors"] == {} - - -@patch("plugwise.Stick.connect", MagicMock(return_value=None)) -@patch("plugwise.Stick.initialize_stick", MagicMock(side_effect=(StickInitError))) -async def test_failed_initialization(hass): - """Test we handle failed initialization of Plugwise USB-stick.""" - result = await hass.config_entries.flow.async_init( - DOMAIN, - context={CONF_SOURCE: SOURCE_USER}, - data={FLOW_TYPE: FLOW_USB}, - ) - result = await hass.config_entries.flow.async_configure( - result["flow_id"], - user_input={CONF_USB_PATH: CONF_MANUAL_PATH}, - ) - result = await hass.config_entries.flow.async_configure( - result["flow_id"], - user_input={CONF_USB_PATH: "/dev/null"}, - ) - assert result["type"] == "form" - assert result["errors"] == {"base": "stick_init"} - - -@patch("plugwise.Stick.connect", MagicMock(return_value=None)) -@patch("plugwise.Stick.initialize_stick", MagicMock(side_effect=(NetworkDown))) -async def test_network_down_exception(hass): - """Test we handle network_down exception.""" - result = await hass.config_entries.flow.async_init( - DOMAIN, - context={CONF_SOURCE: SOURCE_USER}, - data={FLOW_TYPE: FLOW_USB}, - ) - result = await hass.config_entries.flow.async_configure( - result["flow_id"], - user_input={CONF_USB_PATH: CONF_MANUAL_PATH}, - ) - result = await hass.config_entries.flow.async_configure( - result["flow_id"], - user_input={CONF_USB_PATH: "/dev/null"}, - ) - assert result["type"] == "form" - assert result["errors"] == {"base": "network_down"} - - -@patch("plugwise.Stick.connect", MagicMock(return_value=None)) -@patch("plugwise.Stick.initialize_stick", MagicMock(side_effect=(TimeoutException))) -async def test_timeout_exception(hass): - """Test we handle time exception.""" - result = await hass.config_entries.flow.async_init( - DOMAIN, - context={CONF_SOURCE: SOURCE_USER}, - data={FLOW_TYPE: FLOW_USB}, - ) - result = await hass.config_entries.flow.async_configure( - result["flow_id"], - user_input={CONF_USB_PATH: CONF_MANUAL_PATH}, - ) - result = await hass.config_entries.flow.async_configure( - result["flow_id"], - user_input={CONF_USB_PATH: "/dev/null"}, - ) - assert result["type"] == "form" - assert result["errors"] == {"base": "network_timeout"} - - -async def test_options_flow_stick(hass) -> None: - """Test config flow options lack for stick environments.""" - entry = MockConfigEntry( - domain=DOMAIN, - title=CONF_NAME, - data={FLOW_TYPE: FLOW_USB}, - ) - hass.data[DOMAIN] = {entry.entry_id: {"api_stick": MagicMock()}} - entry.add_to_hass(hass) - - with patch( - "homeassistant.components.plugwise.async_setup_entry", return_value=True - ): - assert await hass.config_entries.async_setup(entry.entry_id) - await hass.async_block_till_done() - - result = await hass.config_entries.options.async_init(entry.entry_id) - - assert result["type"] == FlowResultType.FORM - assert result["step_id"] == "none" - - -async def test_options_flow_stick_with_input(hass) -> None: - """Test config flow options lack for stick environments.""" - entry = MockConfigEntry( - domain=DOMAIN, - title=CONF_NAME, - data={FLOW_TYPE: FLOW_USB}, - ) - hass.data[DOMAIN] = {entry.entry_id: {"api_stick": MagicMock()}} - entry.add_to_hass(hass) - - with patch( - "homeassistant.components.plugwise.async_setup_entry", return_value=True - ): - assert await hass.config_entries.async_setup(entry.entry_id) - await hass.async_block_till_done() - - result = await hass.config_entries.options.async_init(entry.entry_id) - result = await hass.config_entries.options.async_configure( - result["flow_id"], - user_input={CONF_USB_PATH: TEST_USBPORT2}, - ) - - assert result["type"] == FlowResultType.CREATE_ENTRY - assert result["title"] == "" From e681e241474e5ca72dafbee2775005c488692a6c Mon Sep 17 00:00:00 2001 From: Tom Scholten Date: Tue, 18 Apr 2023 15:53:59 +0200 Subject: [PATCH 02/20] Updates --- README.md | 8 +++++--- custom_components/plugwise/manifest.json | 2 +- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 0d9fc24f3..73b09932e 100644 --- a/README.md +++ b/README.md @@ -142,8 +142,10 @@ As things like async were in high demand from HA Core, desired by the original a With the three combined forces we now support, maintain and improve on: -- `plugwise-beta` (this repository) for beta-testing new features to go into the `plugwise`-integration for HA -- [`python-plugwise`](https://github.com/plugwise/python-plugwise) for connecting to Plugwise products -- [`progress`](https://github.com/plugwise/progress) showing what are the differences between HA-core and this `custom_component` on [our progress page](https://plugwise.github.io/progress/) +- `plugwise-beta` (this repository) for beta-testing new features to go into the Core `plugwise`-integration for HA +- [`python-plugwise`](https://github.com/plugwise/python-plugwise-usb) for connecting to Networked Plugwise products +- `plugwise_usb-beta` (the USB repository) for beta-testing new features to eventually go upstream to Core into the `plugwise_usb`-integration for HA +- [`python-plugwise-usb`](https://github.com/plugwise/python-plugwise-usb) for connecting to Plugwise products via USB +- [`progress`](https://github.com/plugwise/progress) showing what are the differences between HA-core and the network `custom_component` on [our progress page](https://plugwise.github.io/progress/) (marked as todo for USB as well) And yes anna-ha with haanna (to some degree) support Anna v1.8 - but they don't support Adam nor the Smile P1. diff --git a/custom_components/plugwise/manifest.json b/custom_components/plugwise/manifest.json index 01fda7121..645f1b519 100644 --- a/custom_components/plugwise/manifest.json +++ b/custom_components/plugwise/manifest.json @@ -9,7 +9,7 @@ "iot_class": "local_polling", "loggers": ["plugwise"], "requirements": [ - "https://test-files.pythonhosted.org/packages/44/ed/69e2a8587b30162372b843d436a5036c25a3bcdff99c423b4b94666b5de3/plugwise-0.31.0a1.tar.gz#plugwise==0.31.a01" + "https://test-files.pythonhosted.org/packages/44/ed/69e2a8587b30162372b843d436a5036c25a3bcdff99c423b4b94666b5de3/plugwise-0.31.0a1.tar.gz#plugwise==0.31.0a1" ], "version": "0.40.0a1" } From 6e5447ffb5dd22d882a9cf71152ad69b780df729 Mon Sep 17 00:00:00 2001 From: Tom Scholten Date: Tue, 18 Apr 2023 16:20:56 +0200 Subject: [PATCH 03/20] Remove pyserial --- scripts/core-testing.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/core-testing.sh b/scripts/core-testing.sh index 9dd2701da..3b69df4c5 100755 --- a/scripts/core-testing.sh +++ b/scripts/core-testing.sh @@ -17,7 +17,7 @@ set -e # Which packages to install (to prevent installing all test requirements) # actual package version ARE verified (i.e. grepped) from requirements_test_all # separate packages with | -pip_packages="fnvhash|lru-dict|voluptuous|aiohttp_cors|pyroute2|sqlalchemy|zeroconf|pyserial|pytest-socket|pre-commit|paho-mqtt|numpy|pydantic" +pip_packages="fnvhash|lru-dict|voluptuous|aiohttp_cors|pyroute2|sqlalchemy|zeroconf|pytest-socket|pre-commit|paho-mqtt|numpy|pydantic" echo "" echo "Checking for necessary tools and preparing setup:" @@ -163,7 +163,7 @@ fi # core_prep if [ -z "${GITHUB_ACTIONS}" ] || [ "$1" == "pip_prep" ] ; then cd "${coredir}" || exit if [ -z "${GITHUB_ACTIONS}" ] ; then - echo "Activating venv and installing selected test modules (zeroconf,pyserial, etc)" + echo "Activating venv and installing selected test modules (zeroconf, etc)" echo "" # shellcheck source=/dev/null . venv/bin/activate From 9ccbbbab08f99c3c46b3bbd91c872fd918072109 Mon Sep 17 00:00:00 2001 From: Tom Scholten Date: Fri, 21 Apr 2023 18:38:21 +0200 Subject: [PATCH 04/20] Add distinguishing header --- README.md | 2 +- hacs.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 73b09932e..a02a2ab14 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# Plugwise custom_component (BETA) +# Plugwise Smile/Stretch custom_component (BETA) :warning::no_entry::warning: Do **not** install if you are using **USB** see [why](#usb-notes) :warning::no_entry::warning: diff --git a/hacs.json b/hacs.json index 4bd48d6e3..c2164fbc5 100644 --- a/hacs.json +++ b/hacs.json @@ -1,5 +1,5 @@ { - "name": "Plugwise beta custom component", + "name": "Plugwise Smile/Stretch beta", "domains": [ "binary_sensor", "climate", From 3f66efab2efcc56c6913c0bfa4fe9628db6aabae Mon Sep 17 00:00:00 2001 From: Tom Scholten Date: Fri, 21 Apr 2023 18:44:44 +0200 Subject: [PATCH 05/20] Bump version --- custom_components/plugwise/manifest.json | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/custom_components/plugwise/manifest.json b/custom_components/plugwise/manifest.json index 645f1b519..53766365c 100644 --- a/custom_components/plugwise/manifest.json +++ b/custom_components/plugwise/manifest.json @@ -8,8 +8,6 @@ "integration_type": "hub", "iot_class": "local_polling", "loggers": ["plugwise"], - "requirements": [ - "https://test-files.pythonhosted.org/packages/44/ed/69e2a8587b30162372b843d436a5036c25a3bcdff99c423b4b94666b5de3/plugwise-0.31.0a1.tar.gz#plugwise==0.31.0a1" - ], + "requirements": ["plugwise==0.31.0"], "version": "0.40.0a1" } From 9ae3c6b2aebaded57755cad8f4a14c5515862c83 Mon Sep 17 00:00:00 2001 From: Bouwe Date: Wed, 19 Apr 2023 19:10:59 +0200 Subject: [PATCH 06/20] Clean out plugwise_api keys --- custom_components/plugwise/models.py | 86 +--------------------------- 1 file changed, 3 insertions(+), 83 deletions(-) diff --git a/custom_components/plugwise/models.py b/custom_components/plugwise/models.py index 56c7272da..1bcc1439d 100644 --- a/custom_components/plugwise/models.py +++ b/custom_components/plugwise/models.py @@ -28,21 +28,7 @@ @dataclass -class PlugwiseRequiredKeysMixin: - """Mixin for required keys.""" - - plugwise_api: str - - -@dataclass -class PlugwiseEntityDescription(EntityDescription, PlugwiseRequiredKeysMixin): - """Generic Plugwise entity description.""" - - -@dataclass -class PlugwiseSensorEntityDescription( - SensorEntityDescription, PlugwiseEntityDescription -): +class PlugwiseSensorEntityDescription(SensorEntityDescription): """Describes Plugwise sensor entity.""" should_poll: bool = False @@ -51,9 +37,7 @@ class PlugwiseSensorEntityDescription( @dataclass -class PlugwiseSwitchEntityDescription( - SwitchEntityDescription, PlugwiseEntityDescription -): +class PlugwiseSwitchEntityDescription(SwitchEntityDescription): """Describes Plugwise switch entity.""" should_poll: bool = False @@ -61,9 +45,7 @@ class PlugwiseSwitchEntityDescription( @dataclass -class PlugwiseBinarySensorEntityDescription( - BinarySensorEntityDescription, PlugwiseEntityDescription -): +class PlugwiseBinarySensorEntityDescription(BinarySensorEntityDescription): """Describes Plugwise binary sensor entity.""" icon_off: str | None = None @@ -73,7 +55,6 @@ class PlugwiseBinarySensorEntityDescription( PW_SENSOR_TYPES: tuple[PlugwiseSensorEntityDescription, ...] = ( PlugwiseSensorEntityDescription( - plugwise_api=SMILE, key="setpoint", translation_key="setpoint", device_class=SensorDeviceClass.TEMPERATURE, @@ -81,7 +62,6 @@ class PlugwiseBinarySensorEntityDescription( native_unit_of_measurement=UnitOfTemperature.CELSIUS, ), PlugwiseSensorEntityDescription( - plugwise_api=SMILE, key="cooling_setpoint", translation_key="cooling_setpoint", device_class=SensorDeviceClass.TEMPERATURE, @@ -89,7 +69,6 @@ class PlugwiseBinarySensorEntityDescription( native_unit_of_measurement=UnitOfTemperature.CELSIUS, ), PlugwiseSensorEntityDescription( - plugwise_api=SMILE, key="heating_setpoint", translation_key="heating_setpoint", device_class=SensorDeviceClass.TEMPERATURE, @@ -97,7 +76,6 @@ class PlugwiseBinarySensorEntityDescription( native_unit_of_measurement=UnitOfTemperature.CELSIUS, ), PlugwiseSensorEntityDescription( - plugwise_api=SMILE, key="temperature", translation_key="temperature", device_class=SensorDeviceClass.TEMPERATURE, @@ -105,7 +83,6 @@ class PlugwiseBinarySensorEntityDescription( native_unit_of_measurement=UnitOfTemperature.CELSIUS, ), PlugwiseSensorEntityDescription( - plugwise_api=SMILE, key="intended_boiler_temperature", translation_key="intended_boiler_temperature", device_class=SensorDeviceClass.TEMPERATURE, @@ -113,7 +90,6 @@ class PlugwiseBinarySensorEntityDescription( native_unit_of_measurement=UnitOfTemperature.CELSIUS, ), PlugwiseSensorEntityDescription( - plugwise_api=SMILE, key="temperature_difference", translation_key="temperature_difference", entity_category=EntityCategory.DIAGNOSTIC, @@ -121,7 +97,6 @@ class PlugwiseBinarySensorEntityDescription( icon="mdi:temperature-kelvin", ), PlugwiseSensorEntityDescription( - plugwise_api=SMILE, key="outdoor_temperature", translation_key="outdoor_temperature", device_class=SensorDeviceClass.TEMPERATURE, @@ -129,7 +104,6 @@ class PlugwiseBinarySensorEntityDescription( suggested_display_precision=1, ), PlugwiseSensorEntityDescription( - plugwise_api=SMILE, key="outdoor_air_temperature", translation_key="outdoor_air_temperature", device_class=SensorDeviceClass.TEMPERATURE, @@ -138,7 +112,6 @@ class PlugwiseBinarySensorEntityDescription( suggested_display_precision=1, ), PlugwiseSensorEntityDescription( - plugwise_api=SMILE, key="water_temperature", translation_key="water_temperature", device_class=SensorDeviceClass.TEMPERATURE, @@ -146,7 +119,6 @@ class PlugwiseBinarySensorEntityDescription( native_unit_of_measurement=UnitOfTemperature.CELSIUS, ), PlugwiseSensorEntityDescription( - plugwise_api=SMILE, key="return_temperature", translation_key="return_temperature", device_class=SensorDeviceClass.TEMPERATURE, @@ -154,14 +126,12 @@ class PlugwiseBinarySensorEntityDescription( native_unit_of_measurement=UnitOfTemperature.CELSIUS, ), PlugwiseSensorEntityDescription( - plugwise_api=SMILE, key="electricity_consumed", translation_key="electricity_consumed", device_class=SensorDeviceClass.POWER, native_unit_of_measurement=UnitOfPower.WATT, ), PlugwiseSensorEntityDescription( - plugwise_api=SMILE, key="electricity_produced", translation_key="electricity_produced", device_class=SensorDeviceClass.POWER, @@ -170,7 +140,6 @@ class PlugwiseBinarySensorEntityDescription( ), # Does not exist in Core - related to P1v2 PlugwiseSensorEntityDescription( - plugwise_api=SMILE, key="electricity_consumed_point", translation_key="electricity_consumed_point", device_class=SensorDeviceClass.POWER, @@ -178,35 +147,30 @@ class PlugwiseBinarySensorEntityDescription( ), # Does not exist in Core PlugwiseSensorEntityDescription( - plugwise_api=SMILE, key="electricity_produced_point", translation_key="electricity_produced_point", device_class=SensorDeviceClass.POWER, native_unit_of_measurement=UnitOfPower.WATT, ), PlugwiseSensorEntityDescription( - plugwise_api=SMILE, key="electricity_consumed_interval", translation_key="electricity_consumed_interval", icon="mdi:lightning-bolt", native_unit_of_measurement=UnitOfEnergy.WATT_HOUR, ), PlugwiseSensorEntityDescription( - plugwise_api=SMILE, key="electricity_consumed_peak_interval", translation_key="electricity_consumed_peak_interval", icon="mdi:lightning-bolt", native_unit_of_measurement=UnitOfEnergy.WATT_HOUR, ), PlugwiseSensorEntityDescription( - plugwise_api=SMILE, key="electricity_consumed_off_peak_interval", translation_key="electricity_consumed_off_peak_interval", icon="mdi:lightning-bolt", native_unit_of_measurement=UnitOfEnergy.WATT_HOUR, ), PlugwiseSensorEntityDescription( - plugwise_api=SMILE, key="electricity_produced_interval", translation_key="electricity_produced_interval", icon="mdi:lightning-bolt", @@ -214,35 +178,30 @@ class PlugwiseBinarySensorEntityDescription( entity_registry_enabled_default=False, ), PlugwiseSensorEntityDescription( - plugwise_api=SMILE, key="electricity_produced_peak_interval", translation_key="electricity_produced_peak_interval", icon="mdi:lightning-bolt", native_unit_of_measurement=UnitOfEnergy.WATT_HOUR, ), PlugwiseSensorEntityDescription( - plugwise_api=SMILE, key="electricity_produced_off_peak_interval", translation_key="electricity_produced_off_peak_interval", icon="mdi:lightning-bolt", native_unit_of_measurement=UnitOfEnergy.WATT_HOUR, ), PlugwiseSensorEntityDescription( - plugwise_api=SMILE, key="electricity_consumed_off_peak_point", translation_key="electricity_consumed_off_peak_point", device_class=SensorDeviceClass.POWER, native_unit_of_measurement=UnitOfPower.WATT, ), PlugwiseSensorEntityDescription( - plugwise_api=SMILE, key="electricity_consumed_peak_point", translation_key="electricity_consumed_peak_point", device_class=SensorDeviceClass.POWER, native_unit_of_measurement=UnitOfPower.WATT, ), PlugwiseSensorEntityDescription( - plugwise_api=SMILE, key="electricity_consumed_off_peak_cumulative", translation_key="electricity_consumed_off_peak_cumulative", device_class=SensorDeviceClass.ENERGY, @@ -250,7 +209,6 @@ class PlugwiseBinarySensorEntityDescription( state_class=SensorStateClass.TOTAL_INCREASING, ), PlugwiseSensorEntityDescription( - plugwise_api=SMILE, key="electricity_consumed_peak_cumulative", translation_key="electricity_consumed_peak_cumulative", device_class=SensorDeviceClass.ENERGY, @@ -258,21 +216,18 @@ class PlugwiseBinarySensorEntityDescription( state_class=SensorStateClass.TOTAL_INCREASING, ), PlugwiseSensorEntityDescription( - plugwise_api=SMILE, key="electricity_produced_off_peak_point", translation_key="electricity_produced_off_peak_point", device_class=SensorDeviceClass.POWER, native_unit_of_measurement=UnitOfPower.WATT, ), PlugwiseSensorEntityDescription( - plugwise_api=SMILE, key="electricity_produced_peak_point", translation_key="electricity_produced_peak_point", device_class=SensorDeviceClass.POWER, native_unit_of_measurement=UnitOfPower.WATT, ), PlugwiseSensorEntityDescription( - plugwise_api=SMILE, key="electricity_produced_off_peak_cumulative", translation_key="electricity_produced_off_peak_cumulative", device_class=SensorDeviceClass.ENERGY, @@ -280,7 +235,6 @@ class PlugwiseBinarySensorEntityDescription( state_class=SensorStateClass.TOTAL_INCREASING, ), PlugwiseSensorEntityDescription( - plugwise_api=SMILE, key="electricity_produced_peak_cumulative", translation_key="electricity_produced_peak_cumulative", native_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR, @@ -288,7 +242,6 @@ class PlugwiseBinarySensorEntityDescription( state_class=SensorStateClass.TOTAL_INCREASING, ), PlugwiseSensorEntityDescription( - plugwise_api=SMILE, key="electricity_phase_one_consumed", translation_key="electricity_phase_one_consumed", name="Electricity phase one consumed", @@ -296,42 +249,36 @@ class PlugwiseBinarySensorEntityDescription( native_unit_of_measurement=UnitOfPower.WATT, ), PlugwiseSensorEntityDescription( - plugwise_api=SMILE, key="electricity_phase_two_consumed", translation_key="electricity_phase_two_consumed", device_class=SensorDeviceClass.POWER, native_unit_of_measurement=UnitOfPower.WATT, ), PlugwiseSensorEntityDescription( - plugwise_api=SMILE, key="electricity_phase_three_consumed", translation_key="electricity_phase_three_consumed", device_class=SensorDeviceClass.POWER, native_unit_of_measurement=UnitOfPower.WATT, ), PlugwiseSensorEntityDescription( - plugwise_api=SMILE, key="electricity_phase_one_produced", translation_key="electricity_phase_one_produced", device_class=SensorDeviceClass.POWER, native_unit_of_measurement=UnitOfPower.WATT, ), PlugwiseSensorEntityDescription( - plugwise_api=SMILE, key="electricity_phase_two_produced", translation_key="electricity_phase_two_produced", device_class=SensorDeviceClass.POWER, native_unit_of_measurement=UnitOfPower.WATT, ), PlugwiseSensorEntityDescription( - plugwise_api=SMILE, key="electricity_phase_three_produced", translation_key="electricity_phase_three_produced", device_class=SensorDeviceClass.POWER, native_unit_of_measurement=UnitOfPower.WATT, ), PlugwiseSensorEntityDescription( - plugwise_api=SMILE, key="voltage_phase_one", translation_key="voltage_phase_one", device_class=SensorDeviceClass.VOLTAGE, @@ -339,7 +286,6 @@ class PlugwiseBinarySensorEntityDescription( entity_registry_enabled_default=False, ), PlugwiseSensorEntityDescription( - plugwise_api=SMILE, key="voltage_phase_two", translation_key="voltage_phase_two", device_class=SensorDeviceClass.VOLTAGE, @@ -347,7 +293,6 @@ class PlugwiseBinarySensorEntityDescription( entity_registry_enabled_default=False, ), PlugwiseSensorEntityDescription( - plugwise_api=SMILE, key="voltage_phase_three", translation_key="voltage_phase_three", device_class=SensorDeviceClass.VOLTAGE, @@ -355,14 +300,12 @@ class PlugwiseBinarySensorEntityDescription( entity_registry_enabled_default=False, ), PlugwiseSensorEntityDescription( - plugwise_api=SMILE, key="gas_consumed_interval", translation_key="gas_consumed_interval", icon="mdi:meter-gas", native_unit_of_measurement=UnitOfVolumeFlowRate.CUBIC_METERS_PER_HOUR, ), PlugwiseSensorEntityDescription( - plugwise_api=SMILE, key="gas_consumed_cumulative", translation_key="gas_consumed_cumulative", device_class=SensorDeviceClass.GAS, @@ -370,14 +313,12 @@ class PlugwiseBinarySensorEntityDescription( state_class=SensorStateClass.TOTAL_INCREASING, ), PlugwiseSensorEntityDescription( - plugwise_api=SMILE, key="net_electricity_point", translation_key="net_electricity_point", device_class=SensorDeviceClass.POWER, native_unit_of_measurement=UnitOfPower.WATT, ), PlugwiseSensorEntityDescription( - plugwise_api=SMILE, key="net_electricity_cumulative", translation_key="net_electricity_cumulative", device_class=SensorDeviceClass.ENERGY, @@ -385,7 +326,6 @@ class PlugwiseBinarySensorEntityDescription( state_class=SensorStateClass.TOTAL, ), PlugwiseSensorEntityDescription( - plugwise_api=SMILE, key="battery", translation_key="battery", device_class=SensorDeviceClass.BATTERY, @@ -393,14 +333,12 @@ class PlugwiseBinarySensorEntityDescription( native_unit_of_measurement=PERCENTAGE, ), PlugwiseSensorEntityDescription( - plugwise_api=SMILE, key="illuminance", translation_key="illuminance", device_class=SensorDeviceClass.ILLUMINANCE, native_unit_of_measurement=LIGHT_LUX, ), PlugwiseSensorEntityDescription( - plugwise_api=SMILE, key="modulation_level", translation_key="modulation_level", entity_category=EntityCategory.DIAGNOSTIC, @@ -408,7 +346,6 @@ class PlugwiseBinarySensorEntityDescription( icon="mdi:percent", ), PlugwiseSensorEntityDescription( - plugwise_api=SMILE, key="valve_position", translation_key="valve_position", icon="mdi:valve", @@ -416,7 +353,6 @@ class PlugwiseBinarySensorEntityDescription( native_unit_of_measurement=PERCENTAGE, ), PlugwiseSensorEntityDescription( - plugwise_api=SMILE, key="water_pressure", translation_key="water_pressure", device_class=SensorDeviceClass.PRESSURE, @@ -424,14 +360,12 @@ class PlugwiseBinarySensorEntityDescription( native_unit_of_measurement=UnitOfPressure.BAR, ), PlugwiseSensorEntityDescription( - plugwise_api=SMILE, key="relative_humidity", translation_key="relative_humidity", device_class=SensorDeviceClass.HUMIDITY, native_unit_of_measurement=PERCENTAGE, ), PlugwiseSensorEntityDescription( - plugwise_api=SMILE, key="dhw_temperature", translation_key="dhw_temperature", device_class=SensorDeviceClass.TEMPERATURE, @@ -439,7 +373,6 @@ class PlugwiseBinarySensorEntityDescription( native_unit_of_measurement=UnitOfTemperature.CELSIUS, ), PlugwiseSensorEntityDescription( - plugwise_api=SMILE, key="domestic_hot_water_setpoint", translation_key="domestic_hot_water_setpoint", device_class=SensorDeviceClass.TEMPERATURE, @@ -447,7 +380,6 @@ class PlugwiseBinarySensorEntityDescription( native_unit_of_measurement=UnitOfTemperature.CELSIUS, ), PlugwiseSensorEntityDescription( - plugwise_api=SMILE, key="maximum_boiler_temperature", translation_key="maximum_boiler_temperature", device_class=SensorDeviceClass.TEMPERATURE, @@ -458,7 +390,6 @@ class PlugwiseBinarySensorEntityDescription( PW_SWITCH_TYPES: tuple[PlugwiseSwitchEntityDescription, ...] = ( PlugwiseSwitchEntityDescription( - plugwise_api=SMILE, key="dhw_cm_switch", translation_key="dhw_cm_switch", icon="mdi:water-plus", @@ -466,7 +397,6 @@ class PlugwiseBinarySensorEntityDescription( entity_category=EntityCategory.CONFIG, ), PlugwiseSwitchEntityDescription( - plugwise_api=SMILE, key="lock", translation_key="lock", icon="mdi:lock", @@ -474,13 +404,11 @@ class PlugwiseBinarySensorEntityDescription( entity_category=EntityCategory.CONFIG, ), PlugwiseSwitchEntityDescription( - plugwise_api=SMILE, key="relay", translation_key="relay", device_class=SwitchDeviceClass.SWITCH, ), PlugwiseSwitchEntityDescription( - plugwise_api=SMILE, key="cooling_enabled", translation_key="cooling_enabled", icon="mdi:snowflake-thermometer", @@ -491,7 +419,6 @@ class PlugwiseBinarySensorEntityDescription( PW_BINARY_SENSOR_TYPES: tuple[PlugwiseBinarySensorEntityDescription, ...] = ( PlugwiseBinarySensorEntityDescription( - plugwise_api=SMILE, key="compressor_state", translation_key="compressor_state", icon="mdi:hvac", @@ -499,14 +426,12 @@ class PlugwiseBinarySensorEntityDescription( entity_category=EntityCategory.DIAGNOSTIC, ), PlugwiseBinarySensorEntityDescription( - plugwise_api=SMILE, key="cooling_enabled", translation_key="cooling_enabled", icon="mdi:snowflake-thermometer", entity_category=EntityCategory.DIAGNOSTIC, ), PlugwiseBinarySensorEntityDescription( - plugwise_api=SMILE, key="dhw_state", translation_key="dhw_state", icon="mdi:water-pump", @@ -514,7 +439,6 @@ class PlugwiseBinarySensorEntityDescription( entity_category=EntityCategory.DIAGNOSTIC, ), PlugwiseBinarySensorEntityDescription( - plugwise_api=SMILE, key="flame_state", translation_key="flame_state", icon="mdi:fire", @@ -522,7 +446,6 @@ class PlugwiseBinarySensorEntityDescription( entity_category=EntityCategory.DIAGNOSTIC, ), PlugwiseBinarySensorEntityDescription( - plugwise_api=SMILE, key="heating_state", translation_key="heating_state", icon="mdi:radiator", @@ -530,7 +453,6 @@ class PlugwiseBinarySensorEntityDescription( entity_category=EntityCategory.DIAGNOSTIC, ), PlugwiseBinarySensorEntityDescription( - plugwise_api=SMILE, key="cooling_state", translation_key="cooling_state", icon="mdi:snowflake", @@ -538,7 +460,6 @@ class PlugwiseBinarySensorEntityDescription( entity_category=EntityCategory.DIAGNOSTIC, ), PlugwiseBinarySensorEntityDescription( - plugwise_api=SMILE, key="slave_boiler_state", translation_key="slave_boiler_state", icon="mdi:fire", @@ -546,7 +467,6 @@ class PlugwiseBinarySensorEntityDescription( entity_category=EntityCategory.DIAGNOSTIC, ), PlugwiseBinarySensorEntityDescription( - plugwise_api=SMILE, key="plugwise_notification", translation_key="plugwise_notification", icon="mdi:mailbox-up-outline", From 608c36ec06c60b24062eaf29831b34dceacdee5a Mon Sep 17 00:00:00 2001 From: Bouwe Date: Wed, 19 Apr 2023 19:16:12 +0200 Subject: [PATCH 07/20] Switch: remove plugwise_api-related --- custom_components/plugwise/switch.py | 1 - 1 file changed, 1 deletion(-) diff --git a/custom_components/plugwise/switch.py b/custom_components/plugwise/switch.py index 92275ad35..733584018 100644 --- a/custom_components/plugwise/switch.py +++ b/custom_components/plugwise/switch.py @@ -29,7 +29,6 @@ async def async_setup_entry( if ( "switches" not in device or description.key not in device["switches"] - or description.plugwise_api != SMILE ): continue entities.append(PlugwiseSwitchEntity(coordinator, device_id, description)) From 47415903a9fe0adeb5ac6ead3ffb93578ea97789 Mon Sep 17 00:00:00 2001 From: Bouwe Date: Wed, 19 Apr 2023 19:24:11 +0200 Subject: [PATCH 08/20] Remove unused imports --- custom_components/plugwise/models.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/custom_components/plugwise/models.py b/custom_components/plugwise/models.py index 1bcc1439d..40e6d4ea5 100644 --- a/custom_components/plugwise/models.py +++ b/custom_components/plugwise/models.py @@ -22,9 +22,6 @@ UnitOfVolume, UnitOfVolumeFlowRate, ) -from homeassistant.helpers.entity import EntityDescription - -from .const import SMILE @dataclass From dbec198c3e40496a08b456b3ee9907a17152b819 Mon Sep 17 00:00:00 2001 From: Bouwe Date: Wed, 19 Apr 2023 19:25:29 +0200 Subject: [PATCH 09/20] Remove unused import II --- custom_components/plugwise/switch.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/custom_components/plugwise/switch.py b/custom_components/plugwise/switch.py index 733584018..c369b09e5 100644 --- a/custom_components/plugwise/switch.py +++ b/custom_components/plugwise/switch.py @@ -9,7 +9,7 @@ from homeassistant.helpers.entity_platform import AddEntitiesCallback from .const import COORDINATOR # pw-beta -from .const import DOMAIN, LOGGER, SMILE +from .const import DOMAIN, LOGGER from .coordinator import PlugwiseDataUpdateCoordinator from .entity import PlugwiseEntity from .models import PW_SWITCH_TYPES, PlugwiseSwitchEntityDescription From 250dfb2e615079d95b07fa3da437bccb609c4de0 Mon Sep 17 00:00:00 2001 From: Bouwe Date: Wed, 19 Apr 2023 19:30:38 +0200 Subject: [PATCH 10/20] Blacked --- custom_components/plugwise/switch.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/custom_components/plugwise/switch.py b/custom_components/plugwise/switch.py index c369b09e5..a65ff9565 100644 --- a/custom_components/plugwise/switch.py +++ b/custom_components/plugwise/switch.py @@ -26,10 +26,7 @@ async def async_setup_entry( entities: list[PlugwiseSwitchEntity] = [] for device_id, device in coordinator.data.devices.items(): for description in PW_SWITCH_TYPES: - if ( - "switches" not in device - or description.key not in device["switches"] - ): + if "switches" not in device or description.key not in device["switches"]: continue entities.append(PlugwiseSwitchEntity(coordinator, device_id, description)) LOGGER.debug("Add %s switch", description.key) From 338331c26ccdb2b45c13802bc8d85d1394c1d7b0 Mon Sep 17 00:00:00 2001 From: Bouwe Date: Wed, 19 Apr 2023 19:33:07 +0200 Subject: [PATCH 11/20] Clean up strings.json --- custom_components/plugwise/strings.json | 19 ------------------- 1 file changed, 19 deletions(-) diff --git a/custom_components/plugwise/strings.json b/custom_components/plugwise/strings.json index ec43e8596..fc29356d4 100644 --- a/custom_components/plugwise/strings.json +++ b/custom_components/plugwise/strings.json @@ -19,13 +19,6 @@ "config": { "step": { "user": { - "title": "Plugwise connection type", - "description": "Please select:", - "data": { - "flow_type": "Connection type" - } - }, - "user_gateway": { "title": "Connect to the Plugwise Adam/Smile/Stretch", "description": "Please enter:", "data": { @@ -34,18 +27,6 @@ "host": "IP-address", "port": "Port number" } - }, - "user_usb": { - "title": "Connect to Plugwise Stick", - "description": "Please enter:", - "data": { - "usb_path": "USB-path" - } - }, - "manual_path": { - "data": { - "usb_path": "USB-path" - } } }, "error": { From ce25de07db88744175d57d1e6cb3b51d88a25d44 Mon Sep 17 00:00:00 2001 From: Bouwe Date: Wed, 19 Apr 2023 19:42:46 +0200 Subject: [PATCH 12/20] Clean out services.yaml --- custom_components/plugwise/services.yaml | 55 +----------------------- 1 file changed, 1 insertion(+), 54 deletions(-) diff --git a/custom_components/plugwise/services.yaml b/custom_components/plugwise/services.yaml index 9f214ef6e..295ccc99c 100644 --- a/custom_components/plugwise/services.yaml +++ b/custom_components/plugwise/services.yaml @@ -1,56 +1,3 @@ delete_notification: description: Delete the Plugwise Notification(s). -device_add: - description: Manually add a new plugwise device. - fields: - mac: - description: The full 16 character MAC address of the plugwise device. - example: 0123456789ABCDEF -device_remove: - description: Remove and un-register specified device (MAC) from plugwise network. - fields: - mac: - description: The full 16 character MAC address of the plugwise device. - example: 0123456789ABCDEF -configure_scan: - description: > - Configure the motion settings for a Plugwise Scan device. - The new configuration will be send soon as the Scan devices is awake to receive configuration changes. For quick activation press the local button to awake the device. - fields: - entity_id: - description: Entity id of the Plugwise Scan motion sensor. - example: binary_sensor.motion_AB123 - sensitivity_mode: - description: Scan motion sensitivity mode (high/medium/off). - example: medium - reset_timer: - description: > - Number of minutes the Scan waits after no motion detected to set state back to off. - Valid range is 1 minute up to 240 minutes (4 hours). - example: 5 - day_light: - description: Daylight override to only report motion when light-level is below calibrated level. - example: False -configure_battery_savings: - description: > - Configure the battery saving settings for battery powered Plugwise devices. - The new configuration will be sent soon as the Plugwise device notifies Home Assistant it is awake to receive configuration changes. For quick reception of the configuration press the local button to wake up the device manually. - fields: - entity_id: - description: Entity id of the battery powered Plugwise device. - example: binary_sensor.motion_AB123 - stay_active: - description: Duration in seconds the device will be awake. A high value will drain the battery. - example: 10 - sleep_for: - description: Duration in minutes the device will be in sleeping mode and not able to respond any command. - example: 60 - maintenance_interval: - description: Interval in minutes the node will wake up and notify it is able to receive (maintenance) commands. - example: 1440 - clock_sync: - description: Enable or disable the synchronization of the internal clock. - example: False - clock_interval: - description: Interval the device will synchronize its internal clock. Only useful if clock_sync is set to True. - example: 10080 + From 9abad4f8e5c5f4c4a73e1c3e6a9963f5486acbd0 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Wed, 19 Apr 2023 17:43:17 +0000 Subject: [PATCH 13/20] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- custom_components/plugwise/services.yaml | 1 - 1 file changed, 1 deletion(-) diff --git a/custom_components/plugwise/services.yaml b/custom_components/plugwise/services.yaml index 295ccc99c..c16dca07b 100644 --- a/custom_components/plugwise/services.yaml +++ b/custom_components/plugwise/services.yaml @@ -1,3 +1,2 @@ delete_notification: description: Delete the Plugwise Notification(s). - From 798c1b318b7924d6a138776bbeda7b92155a489d Mon Sep 17 00:00:00 2001 From: Bouwe Date: Wed, 19 Apr 2023 20:13:16 +0200 Subject: [PATCH 14/20] Remove state_request_method keys --- custom_components/plugwise/models.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/custom_components/plugwise/models.py b/custom_components/plugwise/models.py index 40e6d4ea5..dc55226e9 100644 --- a/custom_components/plugwise/models.py +++ b/custom_components/plugwise/models.py @@ -30,7 +30,6 @@ class PlugwiseSensorEntityDescription(SensorEntityDescription): should_poll: bool = False state_class: str | None = SensorStateClass.MEASUREMENT - state_request_method: str | None = None @dataclass @@ -38,7 +37,6 @@ class PlugwiseSwitchEntityDescription(SwitchEntityDescription): """Describes Plugwise switch entity.""" should_poll: bool = False - state_request_method: str | None = None @dataclass @@ -46,8 +44,7 @@ class PlugwiseBinarySensorEntityDescription(BinarySensorEntityDescription): """Describes Plugwise binary sensor entity.""" icon_off: str | None = None - should_poll: bool = False - state_request_method: str | None = None + should_poll: bool = flame_state PW_SENSOR_TYPES: tuple[PlugwiseSensorEntityDescription, ...] = ( From 31776e67dcb6031f9f04a505030f3ff832f06af4 Mon Sep 17 00:00:00 2001 From: Bouwe Date: Wed, 19 Apr 2023 20:15:15 +0200 Subject: [PATCH 15/20] Remove should_poll keys --- custom_components/plugwise/models.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/custom_components/plugwise/models.py b/custom_components/plugwise/models.py index dc55226e9..4b9c580d7 100644 --- a/custom_components/plugwise/models.py +++ b/custom_components/plugwise/models.py @@ -28,7 +28,6 @@ class PlugwiseSensorEntityDescription(SensorEntityDescription): """Describes Plugwise sensor entity.""" - should_poll: bool = False state_class: str | None = SensorStateClass.MEASUREMENT @@ -36,15 +35,12 @@ class PlugwiseSensorEntityDescription(SensorEntityDescription): class PlugwiseSwitchEntityDescription(SwitchEntityDescription): """Describes Plugwise switch entity.""" - should_poll: bool = False - @dataclass class PlugwiseBinarySensorEntityDescription(BinarySensorEntityDescription): """Describes Plugwise binary sensor entity.""" icon_off: str | None = None - should_poll: bool = flame_state PW_SENSOR_TYPES: tuple[PlugwiseSensorEntityDescription, ...] = ( From c00f90fe516f4cba6148e0dc5b8876be69476154 Mon Sep 17 00:00:00 2001 From: Bouwe Date: Wed, 19 Apr 2023 20:25:39 +0200 Subject: [PATCH 16/20] Update README --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index a02a2ab14..d4222902c 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Plugwise Smile/Stretch custom_component (BETA) -:warning::no_entry::warning: Do **not** install if you are using **USB** see [why](#usb-notes) :warning::no_entry::warning: +:warning::no_entry::warning: Do **not** install if you are using **USB**, this functionality has been moved to [https://github.com/plugwise/plugwise_usb-beta], see [why](#usb-notes) :warning::no_entry::warning: :no_entry::no_entry::no_entry: If you have **no** intention to beta-test our integration, please defer to the **supported** release of this integration **natively** available in [Home Assistant](https://www.home-assistant.io/integrations/plugwise/)! :no_entry::no_entry::no_entry: From cf7400e00c29ca5bcfd6d63c943277033a59f015 Mon Sep 17 00:00:00 2001 From: Bouwe Date: Wed, 19 Apr 2023 20:26:47 +0200 Subject: [PATCH 17/20] Better wording --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index d4222902c..776c6a9c7 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Plugwise Smile/Stretch custom_component (BETA) -:warning::no_entry::warning: Do **not** install if you are using **USB**, this functionality has been moved to [https://github.com/plugwise/plugwise_usb-beta], see [why](#usb-notes) :warning::no_entry::warning: +:warning::no_entry::warning: Do **not** use this custom_component for **USB**, this functionality has been moved to [https://github.com/plugwise/plugwise_usb-beta], see [why](#usb-notes) :warning::no_entry::warning: :no_entry::no_entry::no_entry: If you have **no** intention to beta-test our integration, please defer to the **supported** release of this integration **natively** available in [Home Assistant](https://www.home-assistant.io/integrations/plugwise/)! :no_entry::no_entry::no_entry: From c2322262629a235fc5ae10ce6f86e9937cba6ac8 Mon Sep 17 00:00:00 2001 From: Bouwe Date: Wed, 19 Apr 2023 20:27:36 +0200 Subject: [PATCH 18/20] Shorten link --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 776c6a9c7..2225833d5 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Plugwise Smile/Stretch custom_component (BETA) -:warning::no_entry::warning: Do **not** use this custom_component for **USB**, this functionality has been moved to [https://github.com/plugwise/plugwise_usb-beta], see [why](#usb-notes) :warning::no_entry::warning: +:warning::no_entry::warning: Do **not** use this custom_component for **USB**, this functionality has been moved to (plugwise_usb)[https://github.com/plugwise/plugwise_usb-beta], see [why](#usb-notes) :warning::no_entry::warning: :no_entry::no_entry::no_entry: If you have **no** intention to beta-test our integration, please defer to the **supported** release of this integration **natively** available in [Home Assistant](https://www.home-assistant.io/integrations/plugwise/)! :no_entry::no_entry::no_entry: From f0c65c8337b44e3656ac6bdae045bf56e88d8e7d Mon Sep 17 00:00:00 2001 From: Bouwe Date: Wed, 19 Apr 2023 20:28:22 +0200 Subject: [PATCH 19/20] Fix formatting --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 2225833d5..06be33281 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Plugwise Smile/Stretch custom_component (BETA) -:warning::no_entry::warning: Do **not** use this custom_component for **USB**, this functionality has been moved to (plugwise_usb)[https://github.com/plugwise/plugwise_usb-beta], see [why](#usb-notes) :warning::no_entry::warning: +:warning::no_entry::warning: Do **not** use this custom_component for **USB**, this functionality has been moved to [plugwise_usb](https://github.com/plugwise/plugwise_usb-beta), see [why](#usb-notes) :warning::no_entry::warning: :no_entry::no_entry::no_entry: If you have **no** intention to beta-test our integration, please defer to the **supported** release of this integration **natively** available in [Home Assistant](https://www.home-assistant.io/integrations/plugwise/)! :no_entry::no_entry::no_entry: From bacf2c79fbed2c940f36ec3c224c7de61facbab9 Mon Sep 17 00:00:00 2001 From: Bouwe Date: Wed, 19 Apr 2023 20:29:32 +0200 Subject: [PATCH 20/20] Shorter --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 06be33281..3fc6c8801 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Plugwise Smile/Stretch custom_component (BETA) -:warning::no_entry::warning: Do **not** use this custom_component for **USB**, this functionality has been moved to [plugwise_usb](https://github.com/plugwise/plugwise_usb-beta), see [why](#usb-notes) :warning::no_entry::warning: +:warning::no_entry::warning: Do **not** use this custom_component for **USB**, this functionality was moved to [plugwise_usb](https://github.com/plugwise/plugwise_usb-beta), see [why](#usb-notes) :warning::no_entry::warning: :no_entry::no_entry::no_entry: If you have **no** intention to beta-test our integration, please defer to the **supported** release of this integration **natively** available in [Home Assistant](https://www.home-assistant.io/integrations/plugwise/)! :no_entry::no_entry::no_entry: