From 3e641b3ef2b0717a61bfb4a1675ebfbe7c11bbda Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=98yvind=20Matheson=20Wergeland?= Date: Wed, 22 Nov 2023 21:38:13 +0100 Subject: [PATCH] =?UTF-8?q?Add=20Nob=C3=B8=20Hub=20week=20profiles=20and?= =?UTF-8?q?=20global=20override=20(#80866)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * * Nobø Ecohub select entities - Week profiles - Global overrides * Set integration_type * Typing * Remove translations * Translation fixes - Moved strings.select.json into strings.json - Added translation keys for select entities - Use shared translation keys for global override states * Use DeviceInfo object * Revert temperature name - uses device class name * Fix updated checks * Improve error handling (preparing for Silver level) * Review --- .coveragerc | 1 + homeassistant/components/nobo_hub/__init__.py | 31 +--- .../components/nobo_hub/manifest.json | 1 + homeassistant/components/nobo_hub/select.py | 170 ++++++++++++++++++ .../components/nobo_hub/strings.json | 16 ++ 5 files changed, 191 insertions(+), 28 deletions(-) create mode 100644 homeassistant/components/nobo_hub/select.py diff --git a/.coveragerc b/.coveragerc index 0d7c895f3f3..aed63e42938 100644 --- a/.coveragerc +++ b/.coveragerc @@ -830,6 +830,7 @@ omit = homeassistant/components/noaa_tides/sensor.py homeassistant/components/nobo_hub/__init__.py homeassistant/components/nobo_hub/climate.py + homeassistant/components/nobo_hub/select.py homeassistant/components/nobo_hub/sensor.py homeassistant/components/norway_air/air_quality.py homeassistant/components/notify_events/notify.py diff --git a/homeassistant/components/nobo_hub/__init__.py b/homeassistant/components/nobo_hub/__init__.py index bc2c328d647..6c77f98d1b1 100644 --- a/homeassistant/components/nobo_hub/__init__.py +++ b/homeassistant/components/nobo_hub/__init__.py @@ -4,26 +4,12 @@ from __future__ import annotations from pynobo import nobo from homeassistant.config_entries import ConfigEntry -from homeassistant.const import ( - ATTR_NAME, - CONF_IP_ADDRESS, - EVENT_HOMEASSISTANT_STOP, - Platform, -) +from homeassistant.const import CONF_IP_ADDRESS, EVENT_HOMEASSISTANT_STOP, Platform from homeassistant.core import HomeAssistant -from homeassistant.helpers import device_registry as dr -from .const import ( - ATTR_HARDWARE_VERSION, - ATTR_SERIAL, - ATTR_SOFTWARE_VERSION, - CONF_AUTO_DISCOVERED, - CONF_SERIAL, - DOMAIN, - NOBO_MANUFACTURER, -) +from .const import CONF_AUTO_DISCOVERED, CONF_SERIAL, DOMAIN -PLATFORMS = [Platform.CLIMATE, Platform.SENSOR] +PLATFORMS = [Platform.CLIMATE, Platform.SELECT, Platform.SENSOR] async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: @@ -37,17 +23,6 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: hass.data.setdefault(DOMAIN, {}) - # Register hub as device - dev_reg = dr.async_get(hass) - dev_reg.async_get_or_create( - config_entry_id=entry.entry_id, - identifiers={(DOMAIN, hub.hub_info[ATTR_SERIAL])}, - manufacturer=NOBO_MANUFACTURER, - name=hub.hub_info[ATTR_NAME], - model=f"Nobø Ecohub ({hub.hub_info[ATTR_HARDWARE_VERSION]})", - sw_version=hub.hub_info[ATTR_SOFTWARE_VERSION], - ) - async def _async_close(event): """Close the Nobø Ecohub socket connection when HA stops.""" await hub.stop() diff --git a/homeassistant/components/nobo_hub/manifest.json b/homeassistant/components/nobo_hub/manifest.json index 4e6009ce6d7..9ddbed7dadc 100644 --- a/homeassistant/components/nobo_hub/manifest.json +++ b/homeassistant/components/nobo_hub/manifest.json @@ -4,6 +4,7 @@ "codeowners": ["@echoromeo", "@oyvindwe"], "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/nobo_hub", + "integration_type": "hub", "iot_class": "local_push", "requirements": ["pynobo==1.6.0"] } diff --git a/homeassistant/components/nobo_hub/select.py b/homeassistant/components/nobo_hub/select.py new file mode 100644 index 00000000000..b386e158420 --- /dev/null +++ b/homeassistant/components/nobo_hub/select.py @@ -0,0 +1,170 @@ +"""Python Control of Nobø Hub - Nobø Energy Control.""" +from __future__ import annotations + +from pynobo import nobo + +from homeassistant.components.select import SelectEntity +from homeassistant.config_entries import ConfigEntry +from homeassistant.const import ATTR_NAME +from homeassistant.core import HomeAssistant, callback +from homeassistant.exceptions import HomeAssistantError +from homeassistant.helpers.device_registry import DeviceInfo +from homeassistant.helpers.entity_platform import AddEntitiesCallback + +from .const import ( + ATTR_HARDWARE_VERSION, + ATTR_SERIAL, + ATTR_SOFTWARE_VERSION, + CONF_OVERRIDE_TYPE, + DOMAIN, + NOBO_MANUFACTURER, + OVERRIDE_TYPE_NOW, +) + + +async def async_setup_entry( + hass: HomeAssistant, + config_entry: ConfigEntry, + async_add_entities: AddEntitiesCallback, +) -> None: + """Set up any temperature sensors connected to the Nobø Ecohub.""" + + # Setup connection with hub + hub: nobo = hass.data[DOMAIN][config_entry.entry_id] + + override_type = ( + nobo.API.OVERRIDE_TYPE_NOW + if config_entry.options.get(CONF_OVERRIDE_TYPE) == OVERRIDE_TYPE_NOW + else nobo.API.OVERRIDE_TYPE_CONSTANT + ) + + entities: list[SelectEntity] = [ + NoboProfileSelector(zone_id, hub) for zone_id in hub.zones + ] + entities.append(NoboGlobalSelector(hub, override_type)) + async_add_entities(entities, True) + + +class NoboGlobalSelector(SelectEntity): + """Global override selector for Nobø Ecohub.""" + + _attr_has_entity_name = True + _attr_translation_key = "global_override" + _attr_device_class = "nobo_hub__override" + _attr_should_poll = False + _modes = { + nobo.API.OVERRIDE_MODE_NORMAL: "none", + nobo.API.OVERRIDE_MODE_AWAY: "away", + nobo.API.OVERRIDE_MODE_COMFORT: "comfort", + nobo.API.OVERRIDE_MODE_ECO: "eco", + } + _attr_options = list(_modes.values()) + _attr_current_option: str + + def __init__(self, hub: nobo, override_type) -> None: + """Initialize the global override selector.""" + self._nobo = hub + self._attr_unique_id = hub.hub_serial + self._override_type = override_type + self._attr_device_info = DeviceInfo( + identifiers={(DOMAIN, hub.hub_serial)}, + name=hub.hub_info[ATTR_NAME], + manufacturer=NOBO_MANUFACTURER, + model=f"Nobø Ecohub ({hub.hub_info[ATTR_HARDWARE_VERSION]})", + sw_version=hub.hub_info[ATTR_SOFTWARE_VERSION], + ) + + async def async_added_to_hass(self) -> None: + """Register callback from hub.""" + self._nobo.register_callback(self._after_update) + + async def async_will_remove_from_hass(self) -> None: + """Deregister callback from hub.""" + self._nobo.deregister_callback(self._after_update) + + async def async_select_option(self, option: str) -> None: + """Set override.""" + mode = [k for k, v in self._modes.items() if v == option][0] + try: + await self._nobo.async_create_override( + mode, self._override_type, nobo.API.OVERRIDE_TARGET_GLOBAL + ) + except Exception as exp: + raise HomeAssistantError from exp + + async def async_update(self) -> None: + """Fetch new state data for this zone.""" + self._read_state() + + @callback + def _read_state(self) -> None: + for override in self._nobo.overrides.values(): + if override["target_type"] == nobo.API.OVERRIDE_TARGET_GLOBAL: + self._attr_current_option = self._modes[override["mode"]] + break + + @callback + def _after_update(self, hub) -> None: + self._read_state() + self.async_write_ha_state() + + +class NoboProfileSelector(SelectEntity): + """Week profile selector for Nobø zones.""" + + _attr_translation_key = "week_profile" + _attr_has_entity_name = True + _attr_should_poll = False + _profiles: dict[int, str] = {} + _attr_options: list[str] = [] + _attr_current_option: str + + def __init__(self, zone_id: str, hub: nobo) -> None: + """Initialize the week profile selector.""" + self._id = zone_id + self._nobo = hub + self._attr_unique_id = f"{hub.hub_serial}:{zone_id}:profile" + self._attr_device_info = DeviceInfo( + identifiers={(DOMAIN, f"{hub.hub_serial}:{zone_id}")}, + name=hub.zones[zone_id][ATTR_NAME], + via_device=(DOMAIN, hub.hub_info[ATTR_SERIAL]), + suggested_area=hub.zones[zone_id][ATTR_NAME], + ) + + async def async_added_to_hass(self) -> None: + """Register callback from hub.""" + self._nobo.register_callback(self._after_update) + + async def async_will_remove_from_hass(self) -> None: + """Deregister callback from hub.""" + self._nobo.deregister_callback(self._after_update) + + async def async_select_option(self, option: str) -> None: + """Set week profile.""" + week_profile_id = [k for k, v in self._profiles.items() if v == option][0] + try: + await self._nobo.async_update_zone( + self._id, week_profile_id=week_profile_id + ) + except Exception as exp: + raise HomeAssistantError from exp + + async def async_update(self) -> None: + """Fetch new state data for this zone.""" + self._read_state() + + @callback + def _read_state(self) -> None: + self._profiles = { + profile["week_profile_id"]: profile["name"].replace("\xa0", " ") + for profile in self._nobo.week_profiles.values() + } + self._attr_options = sorted(self._profiles.values()) + self._attr_current_option = self._profiles[ + self._nobo.zones[self._id]["week_profile_id"] + ] + + @callback + def _after_update(self, hub) -> None: + self._read_state() + self.async_write_ha_state() diff --git a/homeassistant/components/nobo_hub/strings.json b/homeassistant/components/nobo_hub/strings.json index cfa339c98df..28be01862e9 100644 --- a/homeassistant/components/nobo_hub/strings.json +++ b/homeassistant/components/nobo_hub/strings.json @@ -40,5 +40,21 @@ "description": "Select override type \"Now\" to end override on next week profile change." } } + }, + "entity": { + "select": { + "global_override": { + "name": "global override", + "state": { + "away": "[%key:component::climate::entity_component::_::state_attributes::preset_mode::state::away%]", + "comfort": "[%key:component::climate::entity_component::_::state_attributes::preset_mode::state::comfort%]", + "eco": "[%key:component::climate::entity_component::_::state_attributes::preset_mode::state::eco%]", + "none": "[%key:component::climate::entity_component::_::state_attributes::preset_mode::state::none%]" + } + }, + "week_profile": { + "name": "week profile" + } + } } }