diff --git a/README.md b/README.md index 8ba9df2..5297c1f 100755 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# Wiser Home Assistant Integration v3.4.1 +# Wiser Home Assistant Integration v3.4.2 [![hacs_badge](https://img.shields.io/badge/HACS-Default-orange.svg?style=for-the-badge)](https://github.com/hacs/integration) [![downloads](https://shields.io/github/downloads/asantaga/wiserHomeAssistantPlatform/latest/total?style=for-the-badge)](https://github.com/asantaga/wiserHomeAssistantPlatform) @@ -10,6 +10,8 @@ It also supports some European versions of the Wiser Hub under the Schneider Ele For the latest version of the Wiser Home Assistant Platform please install via HACS. If you want bleeding edge then checkout the dev branch, or look out for beta releases via HACS. Depending on what you choose you may need to use the Manual Code Installation as described in the Wiki. +**This integration requires a minimum HA version of 2023.12.** + Detailed information about this integration has now been moved to our [Wiki pages](https://github.com/asantaga/wiserHomeAssistantPlatform/wiki) For more information checkout the AMAZING community thread available on @@ -22,6 +24,15 @@ For more information checkout the AMAZING community thread available on ## Change log +- v3.4.2 + - Reverted to using aiohttp for communication and resolved issues caused by HA2023.12 + - Bumped api to v1.5.5 + - Fixed issue where hub communication would error due to command characters in payload (issue [#418](https://github.com/asantaga/wiserHomeAssistantPlatform/issues/418)) + - Updated schedule card to allow hiding of hot water schedule (issue [#415](https://github.com/asantaga/wiserHomeAssistantPlatform/issues/415)) + - Included version in card resources to improve updating of new versions + - Added more v2 hub features and attributes + - Improved error handling/logging when hub offline and command is issued + - v3.4.1 - Corrected error deleting schedule - Handle space at end of secret key and prevent error (issue [#409](https://github.com/asantaga/wiserHomeAssistantPlatform/issues/409)) diff --git a/custom_components/wiser/__init__.py b/custom_components/wiser/__init__.py index bddd562..11758d9 100755 --- a/custom_components/wiser/__init__.py +++ b/custom_components/wiser/__init__.py @@ -67,7 +67,6 @@ async def async_setup_entry(hass: HomeAssistant, config_entry): # Register custom cards cards = WiserCardRegistration(hass) await cards.async_register() - await cards.async_remove_gzip_files() _LOGGER.info( f"Wiser Component Setup Completed ({coordinator.wiserhub.system.name})" diff --git a/custom_components/wiser/button.py b/custom_components/wiser/button.py index 6f6099e..08ea467 100644 --- a/custom_components/wiser/button.py +++ b/custom_components/wiser/button.py @@ -6,7 +6,7 @@ from homeassistant.util import dt as dt_util from homeassistant.helpers.update_coordinator import CoordinatorEntity -from .helpers import get_device_name, get_unique_id, get_identifier +from .helpers import get_device_name, get_unique_id, get_identifier, hub_error_handler from .const import ( DATA, @@ -90,6 +90,7 @@ class WiserBoostAllHeatingButton(WiserButton): def __init__(self, data) -> None: super().__init__(data, "Boost All Heating") + @hub_error_handler async def async_press(self): boost_time = self._data.boost_time boost_temp = self._data.boost_temp @@ -105,6 +106,7 @@ class WiserCancelHeatingOverridesButton(WiserButton): def __init__(self, data) -> None: super().__init__(data, "Cancel All Heating Overrides") + @hub_error_handler async def async_press(self): await self._data.wiserhub.system.cancel_all_overrides() await self.async_force_update() @@ -118,6 +120,7 @@ class WiserBoostHotWaterButton(WiserButton): def __init__(self, data) -> None: super().__init__(data, "Boost Hot Water") + @hub_error_handler async def async_press(self): boost_time = self._data.hw_boost_time await self._data.wiserhub.hotwater.boost(boost_time) @@ -132,6 +135,7 @@ class WiserCancelHotWaterOverridesButton(WiserButton): def __init__(self, data) -> None: super().__init__(data, "Cancel Hot Water Overrides") + @hub_error_handler async def async_press(self): await self._data.wiserhub.hotwater.cancel_overrides() await self.async_force_update() @@ -145,6 +149,7 @@ class WiserOverrideHotWaterButton(WiserButton): def __init__(self, data) -> None: super().__init__(data, "Toggle Hot Water") + @hub_error_handler async def async_press(self): await self._data.wiserhub.hotwater.override_state( "Off" if self._data.wiserhub.hotwater.current_state == "On" else "On" @@ -163,6 +168,7 @@ def __init__(self, data, moment_id) -> None: data, f"Moments {data.wiserhub.moments.get_by_id(moment_id).name}" ) + @hub_error_handler async def async_press(self): await self._data.wiserhub.moments.get_by_id(self._moment_id).activate() await self.async_force_update() diff --git a/custom_components/wiser/climate.py b/custom_components/wiser/climate.py index fa197c8..67b8e76 100755 --- a/custom_components/wiser/climate.py +++ b/custom_components/wiser/climate.py @@ -32,6 +32,7 @@ from .helpers import ( get_device_name, get_identifier, + hub_error_handler, ) from .schedules import WiserScheduleEntity @@ -207,6 +208,7 @@ def name(self): """Return Name of device.""" return f"{get_device_name(self._data, self._actuator_id)} Floor Temp" + @hub_error_handler async def async_set_temperature(self, **kwargs) -> None: """Set new target temperature.""" if ( @@ -353,6 +355,7 @@ def hvac_modes(self): """Return the list of available operation modes.""" return self._hvac_modes_list + @hub_error_handler async def async_set_hvac_mode(self, hvac_mode): """Set new operation mode.""" _LOGGER.debug(f"Setting HVAC mode to {hvac_mode} for {self._room.name}") @@ -399,6 +402,7 @@ def preset_modes(self): """Return the list of available preset modes.""" return self._room.available_presets + @hub_error_handler async def async_set_preset_mode(self, preset_mode: str) -> None: """Async call to set preset mode .""" _LOGGER.debug(f"Setting Preset Mode {preset_mode} for {self._room.name}") @@ -452,6 +456,51 @@ def extra_state_attributes(self): attrs["control_direction"] = self._room.control_direction attrs["displayed_setpoint"] = self._room.displayed_setpoint + # Added by LGO + # Climate capabilities only with Hub Vé + if self._room.capabilities: + attrs["heating_supported"] = self._room.capabilities.heating_supported + attrs["cooling_supported"] = self._room.capabilities.cooling_supported + attrs[ + "minimum_heat_set_point" + ] = self._room.capabilities.minimum_heat_set_point + attrs[ + "maximum_heat_set_point" + ] = self._room.capabilities.maximum_heat_set_point + attrs[ + "minimum_cool_set_point" + ] = self._room.capabilities.minimum_cool_set_point + attrs[ + "maximum_cool_set_point" + ] = self._room.capabilities.maximum_cool_set_point + attrs["setpoint_step"] = self._room.capabilities.setpoint_step + attrs["ambient_temperature"] = self._room.capabilities.ambient_temperature + attrs["temperature_control"] = self._room.capabilities.temperature_control + attrs[ + "open_window_detection" + ] = self._room.capabilities.open_window_detection + attrs[ + "hydronic_channel_selection" + ] = self._room.capabilities.hydronic_channel_selection + attrs["on_off_supported"] = self._room.capabilities.on_off_supported + + # Summer comfort + + attrs["include_in_summer_comfort"] = self._room.include_in_summer_comfort + attrs["floor_sensor_state"] = self._room.floor_sensor_state + + # occupancy + + attrs["occupancy_capable"] = self._room.occupancy_capable + if self._room.occupancy_capable: + attrs["occupancy"] = self._room.occupancy + attrs["occupied_heating_set_point"] = self._room.occupied_heating_set_point + attrs[ + "unoccupied_heating_set_point" + ] = self._room.unoccupied_heating_set_point + + # End Added by LGO + # Room can have no schedule if self._room.schedule: attrs["schedule_id"] = self._room.schedule.id @@ -507,6 +556,7 @@ def target_temperature_low(self) -> float | None: """ return self._room.passive_mode_lower_temp + @hub_error_handler async def async_set_temperature(self, **kwargs): """Set new target temperatures.""" if self._room.is_passive_mode and not self._room.is_boosted: @@ -553,6 +603,7 @@ def unique_id(self): f"{self._data.wiserhub.system.name}-WiserRoom-{self._room_id}-{self.name}" ) + @hub_error_handler @callback async def async_boost_heating( self, time_period: int, temperature_delta=0, temperature=0 diff --git a/custom_components/wiser/const.py b/custom_components/wiser/const.py index f3c5c05..9722143 100755 --- a/custom_components/wiser/const.py +++ b/custom_components/wiser/const.py @@ -5,12 +5,24 @@ Angelosantagata@gmail.com """ +VERSION = "3.4.1" DOMAIN = "wiser" DATA_WISER_CONFIG = "wiser_config" URL_BASE = "/wiser" -WISER_CARD_FILENAMES = ["wiser-schedule-card.js", "wiser-zigbee-card.js"] -VERSION = "3.4.1" +WISER_CARDS = [ + { + "name": "Wiser Schedule Card", + "filename": "wiser-schedule-card.js", + "version": "1.3.3", + }, + { + "name": "Wiser Zigbee Card", + "filename": "wiser-zigbee-card.js", + "version": "2.1.1", + }, +] + WISER_PLATFORMS = [ "climate", "sensor", diff --git a/custom_components/wiser/coordinator.py b/custom_components/wiser/coordinator.py index f9f54d8..fd877f0 100644 --- a/custom_components/wiser/coordinator.py +++ b/custom_components/wiser/coordinator.py @@ -1,43 +1,36 @@ +from dataclasses import dataclass from datetime import datetime, timedelta import logging -from dataclasses import dataclass - -from homeassistant.config_entries import ConfigEntry - -from homeassistant.core import HomeAssistant -from homeassistant.helpers.dispatcher import async_dispatcher_send -from homeassistant.helpers.aiohttp_client import async_get_clientsession -from homeassistant.helpers.update_coordinator import DataUpdateCoordinator - -from homeassistant.const import ( - CONF_HOST, - CONF_PASSWORD, - CONF_SCAN_INTERVAL, -) from aioWiserHeatAPI.wiserhub import ( - TEMP_MINIMUM, TEMP_MAXIMUM, + TEMP_MINIMUM, WiserAPI, - WiserHubConnectionError, WiserHubAuthenticationError, + WiserHubConnectionError, WiserHubRESTError, ) +from homeassistant.config_entries import ConfigEntry +from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_SCAN_INTERVAL +from homeassistant.core import HomeAssistant +from homeassistant.helpers.dispatcher import async_dispatcher_send +from homeassistant.helpers.update_coordinator import DataUpdateCoordinator + from .const import ( CONF_AUTOMATIONS_PASSIVE, CONF_AUTOMATIONS_PASSIVE_TEMP_INCREMENT, - CONF_RESTORE_MANUAL_TEMP_OPTION, - CONF_SETPOINT_MODE, - CUSTOM_DATA_STORE, - DEFAULT_PASSIVE_TEMP_INCREMENT, - DEFAULT_SETPOINT_MODE, CONF_HEATING_BOOST_TEMP, CONF_HEATING_BOOST_TIME, CONF_HW_BOOST_TIME, + CONF_RESTORE_MANUAL_TEMP_OPTION, + CONF_SETPOINT_MODE, + CUSTOM_DATA_STORE, DEFAULT_BOOST_TEMP, DEFAULT_BOOST_TEMP_TIME, + DEFAULT_PASSIVE_TEMP_INCREMENT, DEFAULT_SCAN_INTERVAL, + DEFAULT_SETPOINT_MODE, DOMAIN, MIN_SCAN_INTERVAL, ) @@ -119,7 +112,6 @@ def __init__(self, hass: HomeAssistant, config_entry: ConfigEntry) -> None: self.wiserhub = WiserAPI( host=config_entry.data[CONF_HOST], secret=str(config_entry.data[CONF_PASSWORD]).strip(), - session=async_get_clientsession(hass), extra_config_file=hass.config.config_dir + CUSTOM_DATA_STORE, enable_automations=self.enable_automations_passive_mode, ) diff --git a/custom_components/wiser/cover.py b/custom_components/wiser/cover.py index 1d31d1c..caf43f8 100644 --- a/custom_components/wiser/cover.py +++ b/custom_components/wiser/cover.py @@ -15,19 +15,13 @@ CoverEntity, CoverEntityFeature, ) - from homeassistant.core import HomeAssistant, callback from homeassistant.helpers.update_coordinator import CoordinatorEntity +from .const import DATA, DOMAIN, MANUFACTURER_SCHNEIDER +from .helpers import get_device_name, get_identifier, hub_error_handler from .schedules import WiserScheduleEntity -from .const import ( - DATA, - DOMAIN, - MANUFACTURER_SCHNEIDER, -) -from .helpers import get_device_name, get_identifier - MANUFACTURER = MANUFACTURER_SCHNEIDER _LOGGER = logging.getLogger(__name__) @@ -39,7 +33,12 @@ | CoverEntityFeature.STOP ) -TILT_SUPPORT_FLAGS = (CoverEntityFeature.OPEN_TILT | CoverEntityFeature.CLOSE_TILT | CoverEntityFeature.SET_TILT_POSITION | CoverEntityFeature.STOP_TILT) +TILT_SUPPORT_FLAGS = ( + CoverEntityFeature.OPEN_TILT + | CoverEntityFeature.CLOSE_TILT + | CoverEntityFeature.SET_TILT_POSITION + | CoverEntityFeature.STOP_TILT +) async def async_setup_entry(hass: HomeAssistant, config_entry, async_add_entities): @@ -83,7 +82,7 @@ def _handle_coordinator_update(self) -> None: @property def supported_features(self): """Flag supported features.""" - if self._device.is_tilt_supported: + if self._device.drive_config.tilt_enabled: return SUPPORT_FLAGS + TILT_SUPPORT_FLAGS return SUPPORT_FLAGS @@ -94,7 +93,9 @@ def device_info(self): "name": get_device_name(self._data, self._device_id), "identifiers": {(DOMAIN, get_identifier(self._data, self._device_id))}, "manufacturer": MANUFACTURER, - "model": self._data.wiserhub.devices.get_by_id(self._device_id).product_type, + "model": self._data.wiserhub.devices.get_by_id( + self._device_id + ).product_type, "via_device": (DOMAIN, self._data.wiserhub.system.name), } @@ -116,7 +117,9 @@ def current_cover_position(self): @property def current_cover_tilt_position(self) -> int | None: """Return current position of cover tilt.""" - return self._device.current_tilt + """ If tilt feauture is enabled""" + if self._device.drive_config.tilt_enabled: + return self._device.current_tilt @property def is_closed(self): @@ -159,6 +162,10 @@ def extra_state_attributes(self): # Settings attrs["shutter_id"] = self._device_id + # features supported + attrs["is_lift_position_supported"] = self._device.is_lift_position_supported + attrs["is_tilt_supported"] = self._device.is_tilt_supported + attrs["away_mode_action"] = self._device.away_mode_action attrs["mode"] = self._device.mode attrs["lift_open_time"] = self._device.drive_config.open_time @@ -184,7 +191,7 @@ def extra_state_attributes(self): attrs["target_lift"] = self._device.target_lift attrs["scheduled_lift"] = self._device.scheduled_lift - if self._device.is_tilt_supported: + if self._device.drive_config.tilt_enabled: # Tilt settings attrs["current_tilt"] = self._device.current_tilt attrs["manual_tilt"] = self._device.manual_tilt @@ -194,6 +201,11 @@ def extra_state_attributes(self): attrs["tilt_angle_open"] = self._device.drive_config.tilt_angle_open attrs["tilt_movement"] = self._device.tilt_movement + # Summer comfort Added LGO + attrs["respect_summer_comfort"] = self._device.respect_summer_comfort + attrs["summer_comfort_lift"] = self._device.summer_comfort_lift + attrs["summer_comfort_tilt"] = self._device.summer_comfort_tilt + # Schedule attrs["schedule_id"] = self._device.schedule_id if self._device.schedule: @@ -205,6 +217,7 @@ def extra_state_attributes(self): return attrs + @hub_error_handler async def async_set_cover_position(self, **kwargs): """Move the cover to a specific position.""" position = kwargs[ATTR_POSITION] @@ -212,24 +225,28 @@ async def async_set_cover_position(self, **kwargs): await self._device.open(position) await self.async_force_update() + @hub_error_handler async def async_close_cover(self, **kwargs): - """Close shutter""" + """Close shutter.""" _LOGGER.debug(f"Closing {self.name}") await self._device.close() await self.async_force_update() + @hub_error_handler async def async_open_cover(self, **kwargs): - """Close shutter""" + """Open shutter.""" _LOGGER.debug(f"Opening {self.name}") await self._device.open() await self.async_force_update() + @hub_error_handler async def async_stop_cover(self, **kwargs): - """Stop shutter""" + """Stop shutter.""" _LOGGER.debug(f"Stopping {self.name}") await self._device.stop() await self.async_force_update() + @hub_error_handler async def async_set_cover_tilt_position(self, **kwargs: Any) -> None: """Move the cover tilt to a specific position.""" position = kwargs[ATTR_TILT_POSITION] @@ -237,19 +254,22 @@ async def async_set_cover_tilt_position(self, **kwargs: Any) -> None: await self._device.open_tilt(position) await self.async_force_update() + @hub_error_handler async def async_close_cover_tilt(self, **kwargs): - """Close shutter""" + """Close shutter tilt.""" _LOGGER.debug(f"Closing tilt {self.name}") await self._device.close_tilt() await self.async_force_update() + @hub_error_handler async def async_open_cover_tilt(self, **kwargs: Any) -> None: - """Open the cover tilt.""" + """Open shutter tilt.""" await self._device.open_tilt() await self.async_force_update() + @hub_error_handler async def async_stop_cover_tilt(self, **kwargs): - """Stop shutter""" + """Stop shutter tilt.""" _LOGGER.debug(f"Stopping tilt {self.name}") await self._device.stop_tilt() await self.async_force_update() diff --git a/custom_components/wiser/frontend/__init__.py b/custom_components/wiser/frontend/__init__.py index 651994b..3f0fb2c 100644 --- a/custom_components/wiser/frontend/__init__.py +++ b/custom_components/wiser/frontend/__init__.py @@ -4,7 +4,7 @@ from homeassistant.helpers.event import async_call_later -from ..const import URL_BASE, WISER_CARD_FILENAMES +from ..const import URL_BASE, WISER_CARDS _LOGGER = logging.getLogger(__name__) @@ -20,7 +20,7 @@ async def async_register(self): # install card resources async def async_register_wiser_path(self): - # Register custom cards path + # Register custom cards path if not already registered self.hass.http.register_static_path( URL_BASE, self.hass.config.path("custom_components/wiser/frontend"), @@ -41,29 +41,76 @@ async def check_lovelace_resources_loaded(now): async def async_register_wiser_cards(self): _LOGGER.debug("Installing Lovelace resources for Wiser cards") - for card_filename in WISER_CARD_FILENAMES: - url = f"{URL_BASE}/{card_filename}" - resource_loaded = [ - res["url"] - for res in self.hass.data["lovelace"]["resources"].async_items() - if res["url"] == url - ] - if not resource_loaded: - resource_id = await self.hass.data["lovelace"][ - "resources" - ].async_create_item({"res_type": "module", "url": url}) + + # Get resources already registered + wiser_resources = [ + resource + for resource in self.hass.data["lovelace"]["resources"].async_items() + if resource["url"].startswith(URL_BASE) + ] + + for card in WISER_CARDS: + url = f"{URL_BASE}/{card.get('filename')}" + + card_registered = False + + for res in wiser_resources: + if self.get_resource_path(res["url"]) == url: + card_registered = True + # check version + if self.get_resource_version(res["url"]) != card.get("version"): + # Update card version + _LOGGER.debug( + "Updating %s to version %s", + card.get("name"), + card.get("version"), + ) + await self.hass.data["lovelace"]["resources"].async_update_item( + res.get("id"), + { + "res_type": "module", + "url": url + "?v=" + card.get("version"), + }, + ) + # Remove old gzipped files + await self.async_remove_gzip_files() + else: + _LOGGER.debug( + "%s already registered as version %s", + card.get("name"), + card.get("version"), + ) + + if not card_registered: + _LOGGER.debug( + "Registering %s as version %s", + card.get("name"), + card.get("version"), + ) + await self.hass.data["lovelace"]["resources"].async_create_item( + {"res_type": "module", "url": url + "?v=" + card.get("version")} + ) + + def get_resource_path(self, url: str): + return url.split("?")[0] + + def get_resource_version(self, url: str): + try: + return url.split("?")[1].replace("v=", "") + except Exception: + return 0 async def async_unregister(self): # Unload lovelace module resource if self.hass.data["lovelace"]["mode"] == "storage": - for card_filename in WISER_CARD_FILENAMES: - url = f"{URL_BASE}/{card_filename}" + for card in WISER_CARDS: + url = f"{URL_BASE}/{card.get('filename')}" wiser_resources = [ resource for resource in self.hass.data["lovelace"][ "resources" ].async_items() - if resource["url"] == url + if str(resource["url"]).startswith(url) ] for resource in wiser_resources: await self.hass.data["lovelace"]["resources"].async_delete_item( @@ -83,5 +130,5 @@ async def async_remove_gzip_files(self): ): _LOGGER.debug(f"Removing older gzip file - {file}") os.remove(f"{path}/{file}") - except: + except Exception: pass diff --git a/custom_components/wiser/frontend/wiser-schedule-card.js b/custom_components/wiser/frontend/wiser-schedule-card.js index cf5c44c..dce875f 100644 --- a/custom_components/wiser/frontend/wiser-schedule-card.js +++ b/custom_components/wiser/frontend/wiser-schedule-card.js @@ -81,7 +81,7 @@ const he=e=>t=>"function"==typeof t?((e,t)=>(customElements.define(e,t),t))(e,t) * Copyright 2017 Google LLC * SPDX-License-Identifier: BSD-3-Clause */ -function ye(e,t,i){let o,n=e;return"object"==typeof e?(n=e.slot,o=e):o={flatten:t},i?function(e){const{slot:t,selector:i}=null!=e?e:{};return fe({descriptor:o=>({get(){var o;const n="slot"+(t?`[name=${t}]`:":not([name])"),r=null===(o=this.renderRoot)||void 0===o?void 0:o.querySelector(n),d=null!=r?xe(r,e):[];return i?d.filter((e=>e.matches(i))):d},enumerable:!0,configurable:!0})})}({slot:n,flatten:t,selector:i}):fe({descriptor:e=>({get(){var e,t;const i="slot"+(n?`[name=${n}]`:":not([name])"),r=null===(e=this.renderRoot)||void 0===e?void 0:e.querySelector(i);return null!==(t=null==r?void 0:r.assignedNodes(o))&&void 0!==t?t:[]},enumerable:!0,configurable:!0})})}var we,Ee;!function(e){e.language="language",e.system="system",e.comma_decimal="comma_decimal",e.decimal_comma="decimal_comma",e.space_comma="space_comma",e.none="none"}(we||(we={})),function(e){e.language="language",e.system="system",e.am_pm="12",e.twenty_four="24"}(Ee||(Ee={}));var Se=function(e,t,i,o){o=o||{},i=null==i?{}:i;var n=new Event(t,{bubbles:void 0===o.bubbles||o.bubbles,cancelable:Boolean(o.cancelable),composed:void 0===o.composed||o.composed});return n.detail=i,e.dispatchEvent(n),n};function Ce(e,t,i){if(t.has("config")||i)return!0;if(e.config.entity){var o=t.get("hass");return!o||o.states[e.config.entity]!==e.hass.states[e.config.entity]}return!1}const Ae="1.3.2",Ie=86400;var Te,Oe,$e,ke;!function(e){e.Heating="mdi:radiator",e.OnOff="mdi:power-socket-uk",e.Shutters="mdi:blinds",e.Lighting="mdi:lightbulb-outline"}(Te||(Te={})),function(e){e.Overview="OVERVIEW",e.ScheduleEdit="SCHEDULE_EDIT",e.ScheduleCopy="SCHEDULE_COPY",e.ScheduleAdd="SCHEDULE_ADD",e.ScheduleRename="SCHEDULE_RENAME"}(Oe||(Oe={})),function(e){e.Heating="19",e.OnOff="Off",e.Lighting="0",e.Shutters="100"}($e||($e={})),function(e){e.Heating="°C",e.OnOff="",e.Lighting="%",e.Shutters="%"}(ke||(ke={}));const Re=["Heating","OnOff","Lighting","Shutters"],Le=["Lighting","Shutters"],De=["Weekdays","Weekend"],Fe=["Monday","Tuesday","Wednesday","Thursday","Friday"],Me=["Saturday","Sunday"],Ne=Fe.concat(Me),ze=["Sunrise","Sunset"];var Pe;!function(e){e.Sunrise="3000",e.Sunset="4000"}(Pe||(Pe={}));var He={version:"Version",invalid_configuration:"Invalid configuration",no_schedules:"No Schedules Found",name_required:"Name is required"},Be={actions:{copy:"Copy",files:"Files",rename:"Rename",add:"Add",view:"View",add_schedule:"Add Schedule"},labels:{setting:"Setting",name:"Name",assigns:"Assigns",start:"Start",end:"End",to:"to"},days:{monday:"Monday",tuesday:"Tuesday",wednesday:"Wednesday",thursday:"Thursday",friday:"Friday",saturday:"Saturday",sunday:"Sunday",weekdays:"Weekdays",weekend:"Weekend",all:"All",short:{monday:"Mon",tuesday:"Tue",wednesday:"Wed",thursday:"Thu",friday:"Fri",saturday:"Sat",sunday:"Sun"}},headings:{schedule_actions:"Schedule Actions",schedule_type:"Schedule Type",schedule_id:"Schedule Id",schedule_name:"Schedule Name",schedule_assignment:"Schedule Assignment",not_assigned:"(Not Assigned)",rename_schedule:"Rename Schedule",copy_schedule:"Copy Schedule",delete_schedule:"Delete Schedule"},helpers:{enter_new_name:"Enter the new name for the Schedule",select_copy_schedule:"Select the schedule below to copy to",delete_schedule_confirm:"Are you sure you wish to delete the schedule",select_a_schedule:"Select a schedule to view",add_schedule:"Select the schedule type and enter a name for the schedule to create"}},Ve={common:He,wiser:Be},Ue={version:"Déclinaison",invalid_configuration:"Configuration Invalide",no_schedules:"Aucun Programme Trouvé",name_required:"Nom est obligatoire"},je={actions:{copy:"Copie",files:"Fichier",rename:"Renommer",add:"Ajouter",view:"Voir",add_schedule:"Ajouter un Programme"},labels:{setting:"Paramètre",name:"Nom",assigns:"Attribuers",start:"Début",end:"Fin",to:"à"},days:{monday:"Lundi",tuesday:"Mardi",wednesday:"Mercredi",thursday:"Jeudi",friday:"Vendredi",saturday:"Samedi",sunday:"Dimanche",weekdays:"Lun à Ven",weekend:"Sam et Dim",all:"Toute",short:{monday:"Lun",tuesday:"Mar",wednesday:"Mer",thursday:"Jeu",friday:"Ven",saturday:"Sam",sunday:"Dim"}},headings:{schedule_actions:"Programmer des Actions",schedule_type:"Type de Programme",schedule_id:"Numéro de Programme",schedule_name:"Nom de Programme",schedule_assignment:"Attribuer de Programme",not_assigned:"(Non Attribué)",rename_schedule:"Renommer le Programme",copy_schedule:"Copier le Programme",delete_schedule:"Supprimer le Programme"},helpers:{enter_new_name:"Entrez un nom pour le Programme",select_copy_schedule:"Sélectionnez le calendrier ci-dessous pour le copier",delete_schedule_confirm:"Êtes-vous sûr de vouloir effacer ce programme",select_a_schedule:"Sélectionner un programme à afficher",add_schedule:"Sélectionnez le type de programme et entrez un nom pour la programme à créer"}},We={common:Ue,wiser:je};const Ye={en:Object.freeze({__proto__:null,common:He,wiser:Be,default:Ve}),fr:Object.freeze({__proto__:null,common:Ue,wiser:je,default:We})};function Ge(e,t="",i=""){const o=(localStorage.getItem("selectedLanguage")||"en").replace(/['"]+/g,"").replace("-","_");let n;try{n=e.split(".").reduce(((e,t)=>e[t]),Ye[o]),n||(n=e.split(".").reduce(((e,t)=>e[t]),Ye.en))}catch(t){try{n=e.split(".").reduce(((e,t)=>e[t]),Ye.en)}catch(e){n=""}}return void 0===n&&(n=e.split(".").reduce(((e,t)=>e[t]),Ye.en)),""!==t&&""!==i&&(n=n.replace(t,i)),n}const Xe=h` +function ye(e,t,i){let o,n=e;return"object"==typeof e?(n=e.slot,o=e):o={flatten:t},i?function(e){const{slot:t,selector:i}=null!=e?e:{};return fe({descriptor:o=>({get(){var o;const n="slot"+(t?`[name=${t}]`:":not([name])"),r=null===(o=this.renderRoot)||void 0===o?void 0:o.querySelector(n),d=null!=r?xe(r,e):[];return i?d.filter((e=>e.matches(i))):d},enumerable:!0,configurable:!0})})}({slot:n,flatten:t,selector:i}):fe({descriptor:e=>({get(){var e,t;const i="slot"+(n?`[name=${n}]`:":not([name])"),r=null===(e=this.renderRoot)||void 0===e?void 0:e.querySelector(i);return null!==(t=null==r?void 0:r.assignedNodes(o))&&void 0!==t?t:[]},enumerable:!0,configurable:!0})})}var we,Ee;!function(e){e.language="language",e.system="system",e.comma_decimal="comma_decimal",e.decimal_comma="decimal_comma",e.space_comma="space_comma",e.none="none"}(we||(we={})),function(e){e.language="language",e.system="system",e.am_pm="12",e.twenty_four="24"}(Ee||(Ee={}));var Se=function(e,t,i,o){o=o||{},i=null==i?{}:i;var n=new Event(t,{bubbles:void 0===o.bubbles||o.bubbles,cancelable:Boolean(o.cancelable),composed:void 0===o.composed||o.composed});return n.detail=i,e.dispatchEvent(n),n};function Ce(e,t,i){if(t.has("config")||i)return!0;if(e.config.entity){var o=t.get("hass");return!o||o.states[e.config.entity]!==e.hass.states[e.config.entity]}return!1}const Ae="1.3.3",Ie=86400;var Te,Oe,$e,ke;!function(e){e.Heating="mdi:radiator",e.OnOff="mdi:power-socket-uk",e.Shutters="mdi:blinds",e.Lighting="mdi:lightbulb-outline"}(Te||(Te={})),function(e){e.Overview="OVERVIEW",e.ScheduleEdit="SCHEDULE_EDIT",e.ScheduleCopy="SCHEDULE_COPY",e.ScheduleAdd="SCHEDULE_ADD",e.ScheduleRename="SCHEDULE_RENAME"}(Oe||(Oe={})),function(e){e.Heating="19",e.OnOff="Off",e.Lighting="0",e.Shutters="100"}($e||($e={})),function(e){e.Heating="°C",e.OnOff="",e.Lighting="%",e.Shutters="%"}(ke||(ke={}));const Re=["Heating","OnOff","Lighting","Shutters"],Le=["Lighting","Shutters"],De=["Weekdays","Weekend"],Fe=["Monday","Tuesday","Wednesday","Thursday","Friday"],Me=["Saturday","Sunday"],Ne=Fe.concat(Me),ze=["Sunrise","Sunset"];var Pe;!function(e){e.Sunrise="3000",e.Sunset="4000"}(Pe||(Pe={}));var He={version:"Version",invalid_configuration:"Invalid configuration",no_schedules:"No Schedules Found",name_required:"Name is required"},Be={actions:{copy:"Copy",files:"Files",rename:"Rename",add:"Add",view:"View",add_schedule:"Add Schedule"},labels:{setting:"Setting",name:"Name",assigns:"Assigns",start:"Start",end:"End",to:"to"},days:{monday:"Monday",tuesday:"Tuesday",wednesday:"Wednesday",thursday:"Thursday",friday:"Friday",saturday:"Saturday",sunday:"Sunday",weekdays:"Weekdays",weekend:"Weekend",all:"All",short:{monday:"Mon",tuesday:"Tue",wednesday:"Wed",thursday:"Thu",friday:"Fri",saturday:"Sat",sunday:"Sun"}},headings:{schedule_actions:"Schedule Actions",schedule_type:"Schedule Type",schedule_id:"Schedule Id",schedule_name:"Schedule Name",schedule_assignment:"Schedule Assignment",not_assigned:"(Not Assigned)",rename_schedule:"Rename Schedule",copy_schedule:"Copy Schedule",delete_schedule:"Delete Schedule"},helpers:{enter_new_name:"Enter the new name for the Schedule",select_copy_schedule:"Select the schedule below to copy to",delete_schedule_confirm:"Are you sure you wish to delete the schedule",select_a_schedule:"Select a schedule to view",add_schedule:"Select the schedule type and enter a name for the schedule to create"}},Ve={common:He,wiser:Be},Ue={version:"Déclinaison",invalid_configuration:"Configuration Invalide",no_schedules:"Aucun Programme Trouvé",name_required:"Nom est obligatoire"},je={actions:{copy:"Copie",files:"Fichier",rename:"Renommer",add:"Ajouter",view:"Voir",add_schedule:"Ajouter un Programme"},labels:{setting:"Paramètre",name:"Nom",assigns:"Attribuers",start:"Début",end:"Fin",to:"à"},days:{monday:"Lundi",tuesday:"Mardi",wednesday:"Mercredi",thursday:"Jeudi",friday:"Vendredi",saturday:"Samedi",sunday:"Dimanche",weekdays:"Lun à Ven",weekend:"Sam et Dim",all:"Toute",short:{monday:"Lun",tuesday:"Mar",wednesday:"Mer",thursday:"Jeu",friday:"Ven",saturday:"Sam",sunday:"Dim"}},headings:{schedule_actions:"Programmer des Actions",schedule_type:"Type de Programme",schedule_id:"Numéro de Programme",schedule_name:"Nom de Programme",schedule_assignment:"Attribuer de Programme",not_assigned:"(Non Attribué)",rename_schedule:"Renommer le Programme",copy_schedule:"Copier le Programme",delete_schedule:"Supprimer le Programme"},helpers:{enter_new_name:"Entrez un nom pour le Programme",select_copy_schedule:"Sélectionnez le calendrier ci-dessous pour le copier",delete_schedule_confirm:"Êtes-vous sûr de vouloir effacer ce programme",select_a_schedule:"Sélectionner un programme à afficher",add_schedule:"Sélectionnez le type de programme et entrez un nom pour la programme à créer"}},We={common:Ue,wiser:je};const Ye={en:Object.freeze({__proto__:null,common:He,wiser:Be,default:Ve}),fr:Object.freeze({__proto__:null,common:Ue,wiser:je,default:We})};function Ge(e,t="",i=""){const o=(localStorage.getItem("selectedLanguage")||"en").replace(/['"]+/g,"").replace("-","_");let n;try{n=e.split(".").reduce(((e,t)=>e[t]),Ye[o]),n||(n=e.split(".").reduce(((e,t)=>e[t]),Ye.en))}catch(t){try{n=e.split(".").reduce(((e,t)=>e[t]),Ye.en)}catch(e){n=""}}return void 0===n&&(n=e.split(".").reduce(((e,t)=>e[t]),Ye.en)),""!==t&&""!==i&&(n=n.replace(t,i)),n}const Xe=h` .card-header { display: flex; justify-content: space-between; @@ -188,12 +188,12 @@ function ye(e,t,i){let o,n=e;return"object"==typeof e?(n=e.slot,o=e):o={flatten: ${this.supported_schedule_types.map((e=>this.renderScheduleItemsByType(e)))} ${this.renderAddScheduleButton()} - `:U` ${this._showWarning(Ge("wiser.common.no_schedules"))} `:U``}_showWarning(e){return U` ${e} `}renderScheduleItemsByType(e){var t,i;const o=this.schedule_list.filter((t=>t.Type===e));return o.length>0?U` + `:U` ${this._showWarning(Ge("wiser.common.no_schedules"))} `:U``}_showWarning(e){return U` ${e} `}renderScheduleItemsByType(e){var t,i,o,n=this.schedule_list.filter((t=>t.Type===e));return(null===(t=this.config)||void 0===t?void 0:t.hide_hw_schedule)&&(n=n.filter((e=>1e3!=e.Id))),n.length>0?U`
${e}
- ${"list"==(null===(t=this.config)||void 0===t?void 0:t.view_type)?this.renderScheduleList(o):(null===(i=this.config)||void 0===i?void 0:i.show_schedule_id)?o.sort(((e,t)=>e.Id-t.Id)).map((e=>this.renderScheduleItem(e))):o.map((e=>this.renderScheduleItem(e)))} + ${"list"==(null===(i=this.config)||void 0===i?void 0:i.view_type)?this.renderScheduleList(n):(null===(o=this.config)||void 0===o?void 0:o.show_schedule_id)?n.sort(((e,t)=>e.Id-t.Id)).map((e=>this.renderScheduleItem(e))):n.map((e=>this.renderScheduleItem(e)))}
@@ -2426,7 +2426,7 @@ const $n=h`.mdc-floating-label{-moz-osx-font-smoothing:grayscale;-webkit-font-sm * Copyright 2021 Google LLC * SPDX-License-Identifier: BSD-3-Clause */ -function(e){return class extends e{createRenderRoot(){const e=this.constructor,{registry:t,elementDefinitions:i,shadowRootOptions:o}=e;i&&!t&&(e.registry=new CustomElementRegistry,Object.entries(i).forEach((([t,i])=>e.registry.define(t,i))));const n=this.renderOptions.creationScope=this.attachShadow({...o,customElements:e.registry});return p(n,this.constructor.elementStyles),n}}}(le)){constructor(){super(...arguments),this._initialized=!1}setConfig(e){this._config=e,this.loadCardHelpers()}shouldUpdate(){return this._initialized||this._initialize(),!0}get _name(){var e;return(null===(e=this._config)||void 0===e?void 0:e.name)||""}get _hub(){var e;return(null===(e=this._config)||void 0===e?void 0:e.hub)||""}get _selected_schedule(){var e;return(null===(e=this._config)||void 0===e?void 0:e.selected_schedule)||""}get _theme_colors(){var e;return(null===(e=this._config)||void 0===e?void 0:e.theme_colors)||!1}get _show_badges(){var e;return(null===(e=this._config)||void 0===e?void 0:e.show_badges)||!1}get _show_schedule_id(){var e;return(null===(e=this._config)||void 0===e?void 0:e.show_schedule_id)||!1}get _display_only(){var e;return(null===(e=this._config)||void 0===e?void 0:e.display_only)||!1}get _hide_schedule_info(){var e;return(null===(e=this._config)||void 0===e?void 0:e.hide_schedule_info)||!1}get _hide_assignments(){var e;return(null===(e=this._config)||void 0===e?void 0:e.hide_assignments)||!1}get _admin_only(){var e;return(null===(e=this._config)||void 0===e?void 0:e.admin_only)||!1}get _view_type(){var e,t;return(null===(e=this._config)||void 0===e?void 0:e.view_type)?null===(t=this._config)||void 0===t?void 0:t.view_type:"default"}get _hide_card_borders(){var e;return(null===(e=this._config)||void 0===e?void 0:e.hide_card_borders)||!1}async loadData(){var e;this.hass&&(this._hubs=await(e=this.hass,e.callWS({type:"wiser/hubs"})),this._schedules=await Ke(this.hass,this._hub?this._hub:this._hubs[0]))}render(){return this.hass&&this._helpers&&this._config&&this._hubs&&this._schedules?U` +function(e){return class extends e{createRenderRoot(){const e=this.constructor,{registry:t,elementDefinitions:i,shadowRootOptions:o}=e;i&&!t&&(e.registry=new CustomElementRegistry,Object.entries(i).forEach((([t,i])=>e.registry.define(t,i))));const n=this.renderOptions.creationScope=this.attachShadow({...o,customElements:e.registry});return p(n,this.constructor.elementStyles),n}}}(le)){constructor(){super(...arguments),this._initialized=!1}setConfig(e){this._config=e,this.loadCardHelpers()}shouldUpdate(){return this._initialized||this._initialize(),!0}get _name(){var e;return(null===(e=this._config)||void 0===e?void 0:e.name)||""}get _hub(){var e;return(null===(e=this._config)||void 0===e?void 0:e.hub)||""}get _selected_schedule(){var e;return(null===(e=this._config)||void 0===e?void 0:e.selected_schedule)||""}get _theme_colors(){var e;return(null===(e=this._config)||void 0===e?void 0:e.theme_colors)||!1}get _show_badges(){var e;return(null===(e=this._config)||void 0===e?void 0:e.show_badges)||!1}get _show_schedule_id(){var e;return(null===(e=this._config)||void 0===e?void 0:e.show_schedule_id)||!1}get _display_only(){var e;return(null===(e=this._config)||void 0===e?void 0:e.display_only)||!1}get _hide_schedule_info(){var e;return(null===(e=this._config)||void 0===e?void 0:e.hide_schedule_info)||!1}get _hide_assignments(){var e;return(null===(e=this._config)||void 0===e?void 0:e.hide_assignments)||!1}get _hide_hw_schedule(){var e;return(null===(e=this._config)||void 0===e?void 0:e.hide_hw_schedule)||!1}get _admin_only(){var e;return(null===(e=this._config)||void 0===e?void 0:e.admin_only)||!1}get _view_type(){var e,t;return(null===(e=this._config)||void 0===e?void 0:e.view_type)?null===(t=this._config)||void 0===t?void 0:t.view_type:"default"}get _hide_card_borders(){var e;return(null===(e=this._config)||void 0===e?void 0:e.hide_card_borders)||!1}async loadData(){var e;this.hass&&(this._hubs=await(e=this.hass,e.callWS({type:"wiser/hubs"})),this._schedules=await Ke(this.hass,this._hub?this._hub:this._hubs[0]))}render(){return this.hass&&this._helpers&&this._config&&this._hubs&&this._schedules?U` + + +

Default View Options

None: """Set new value.""" _LOGGER.debug(f"Setting {self._name} to {value}C") @@ -218,6 +219,7 @@ def native_value(self): """Return device value""" return self._value + @hub_error_handler async def async_set_native_value(self, value: float) -> None: """Set new value.""" _LOGGER.debug(f"Setting {self._name} to {value}C") diff --git a/custom_components/wiser/select.py b/custom_components/wiser/select.py index 40142fa..20c1f48 100644 --- a/custom_components/wiser/select.py +++ b/custom_components/wiser/select.py @@ -6,7 +6,7 @@ MANUFACTURER, ) -from .helpers import get_device_name, get_unique_id, get_identifier +from .helpers import get_device_name, get_unique_id, get_identifier, hub_error_handler from .schedules import WiserScheduleEntity from homeassistant.components.select import SelectEntity @@ -75,6 +75,7 @@ def options(self) -> list[str]: def current_option(self) -> str: return self._device.mode + @hub_error_handler async def async_select_option(self, option: str) -> None: _LOGGER.debug(f"Setting {self.name} to {option}") if option in self._options: diff --git a/custom_components/wiser/sensor.py b/custom_components/wiser/sensor.py index 28c2d63..491b8bb 100755 --- a/custom_components/wiser/sensor.py +++ b/custom_components/wiser/sensor.py @@ -103,11 +103,27 @@ async def async_setup_entry(hass: HomeAssistant, config_entry, async_add_entitie for power_tag in data.wiserhub.devices.power_tags.all: wiser_sensors.extend( [ - WiserLTSPowerSensor(data, power_tag.id, sensor_type="Power", name="Power"), - WiserLTSPowerSensor(data, power_tag.id, sensor_type="Energy", name="Energy Delivered"), - WiserLTSPowerSensor(data, power_tag.id, sensor_type="EnergyReceived", name="Energy Received"), - WiserCurrentVoltageSensor(data, power_tag.id, sensor_type="Voltage"), - WiserCurrentVoltageSensor(data, power_tag.id, sensor_type="Current") + WiserLTSPowerSensor( + data, power_tag.id, sensor_type="Power", name="Power" + ), + WiserLTSPowerSensor( + data, + power_tag.id, + sensor_type="Energy", + name="Energy Delivered", + ), + WiserLTSPowerSensor( + data, + power_tag.id, + sensor_type="EnergyReceived", + name="Energy Received", + ), + WiserCurrentVoltageSensor( + data, power_tag.id, sensor_type="Voltage" + ), + WiserCurrentVoltageSensor( + data, power_tag.id, sensor_type="Current" + ), ] ) @@ -424,7 +440,9 @@ def extra_state_attributes(self): # Show status info if exists if self._data.wiserhub.status: attrs["uptime"] = self._data.wiserhub.status.uptime - attrs["last_reset_reason"] = self._data.wiserhub.status.last_reset_reason + attrs[ + "last_reset_reason" + ] = self._data.wiserhub.status.last_reset_reason # Other if self._sensor_type == "RoomStat": @@ -610,6 +628,7 @@ def extra_state_attributes(self): class WiserCurrentVoltageSensor(WiserSensor): """Sensor for voltage of equipment devices""" + def __init__(self, data, device_id, sensor_type="") -> None: super().__init__(data, device_id, sensor_type) self._device = data.wiserhub.devices.get_by_id(device_id) @@ -868,13 +887,9 @@ def _handle_coordinator_update(self) -> None: """Fetch new state data for the sensor.""" super()._handle_coordinator_update() if self._lts_sensor_type == "opentherm_flow_temp": - self._state = ( - self._data.wiserhub.system.opentherm.operational_data.ch_flow_temperature - ) + self._state = self._data.wiserhub.system.opentherm.operational_data.ch_flow_temperature elif self._lts_sensor_type == "opentherm_return_temp": - self._state = ( - self._data.wiserhub.system.opentherm.operational_data.ch_return_temperature - ) + self._state = self._data.wiserhub.system.opentherm.operational_data.ch_return_temperature self.async_write_ha_state() @property @@ -1138,11 +1153,7 @@ def __init__(self, data, device_id, sensor_type="", name="") -> None: device_name = data.wiserhub.rooms.get_by_id(self._device.room_id).name if name: - super().__init__( - data, - device_id, - f"{name.title()} " - ) + super().__init__(data, device_id, f"{name.title()} ") else: if sensor_type == "Power": super().__init__( @@ -1167,17 +1178,13 @@ def _handle_coordinator_update(self) -> None: ).instantaneous_power elif self._lts_sensor_type == "Energy": self._state = round( - self._data.wiserhub.devices.get_by_id( - self._device_id - ).delivered_power + self._data.wiserhub.devices.get_by_id(self._device_id).delivered_power / 1000, 2, ) elif self._lts_sensor_type == "EnergyReceived": self._state = round( - self._data.wiserhub.devices.get_by_id( - self._device_id - ).received_power + self._data.wiserhub.devices.get_by_id(self._device_id).received_power / 1000, 2, ) @@ -1220,7 +1227,6 @@ def icon(self): if self._lts_sensor_type == "Power": return ( "mdi:home-lightning-bolt" - # if self._data.wiserhub.devices.get_by_id( # self._device_id # ).instantaneous_power diff --git a/custom_components/wiser/switch.py b/custom_components/wiser/switch.py index 367fce7..ec60670 100755 --- a/custom_components/wiser/switch.py +++ b/custom_components/wiser/switch.py @@ -20,6 +20,7 @@ get_identifier, get_room_name, get_unique_id, + hub_error_handler, ) from custom_components.wiser.schedules import WiserScheduleEntity @@ -72,12 +73,30 @@ "icon": "mdi:clock-time-one", "type": "system", }, + { + "name": "Summer Comfort Enabled", + "key": "summer_comfort_enabled", + "icon": "mdi:sofa", + "type": "system", + }, + { + "name": "Summer Discomfort Prevention", + "key": "summer_discomfort_prevention", + "icon": "mdi:beach", + "type": "system", + }, { "name": "Window Detection", "key": "window_detection_active", "icon": "mdi:window-closed", "type": "room", }, + { + "name": "Include In Summer Comfort", + "key": "include_in_summer_comfort", + "icon": "mdi:sofa", + "type": "room", + }, { "name": "Device Lock", "key": "device_lock_enabled", @@ -104,12 +123,16 @@ async def async_setup_entry(hass: HomeAssistant, config_entry, async_add_entitie for room in [ room for room in data.wiserhub.rooms.all if len(room.devices) > 0 ]: - wiser_switches.append( - WiserRoomSwitch( - data, switch["name"], switch["key"], switch["icon"], room.id + if getattr(room, switch["key"]) is not None: + wiser_switches.append( + WiserRoomSwitch( + data, switch["name"], switch["key"], switch["icon"], room.id + ) ) - ) - elif switch["type"] == "system": + elif ( + switch["type"] == "system" + and getattr(data.wiserhub.system, switch["key"]) is not None + ): wiser_switches.append( WiserSystemSwitch(data, switch["name"], switch["key"], switch["icon"]) ) @@ -137,6 +160,12 @@ async def async_setup_entry(hass: HomeAssistant, config_entry, async_add_entitie wiser_switches.extend( [WiserShutterAwayActionSwitch(data, shutter.id, f"Wiser {shutter.name}")] ) + if data.hub_version == 2: + wiser_switches.append( + WiserShutterSummerComfortSwitch( + data, shutter.id, f"Wiser {shutter.name}" + ) + ) # Add SmartPlugs (if any) for plug in data.wiserhub.devices.smartplugs.all: @@ -207,10 +236,12 @@ def is_on(self): """Return true if device is on.""" return self._is_on + @hub_error_handler async def async_turn_on(self, **kwargs): """Turn the device on.""" raise NotImplementedError + @hub_error_handler async def async_turn_off(self, **kwargs): """Turn the device off.""" raise NotImplementedError @@ -236,6 +267,7 @@ def _handle_coordinator_update(self) -> None: ) self.async_write_ha_state() + @hub_error_handler async def async_turn_on(self, **kwargs): """Turn the device on.""" @@ -244,6 +276,7 @@ async def async_turn_on(self, **kwargs): await self.async_force_update() return True + @hub_error_handler async def async_turn_off(self, **kwargs): """Turn the device off.""" fn = getattr(self._data.wiserhub.system, "set_" + self._key) @@ -297,6 +330,7 @@ def name(self): """Return the name of the Device.""" return f"{get_room_name(self._data, self._room_id)} {self._name}" + @hub_error_handler async def async_turn_on(self, **kwargs): """Turn the device on.""" fn = getattr(self._room, "set_" + self._key) @@ -304,6 +338,7 @@ async def async_turn_on(self, **kwargs): await self.async_force_update() return True + @hub_error_handler async def async_turn_off(self, **kwargs): """Turn the device off.""" fn = getattr(self._room, "set_" + self._key) @@ -354,6 +389,7 @@ def name(self): """Return the name of the Device.""" return f"{get_device_name(self._data, self._device_id)} {self._name}" + @hub_error_handler async def async_turn_on(self, **kwargs): """Turn the device on.""" fn = getattr(self._device, "set_" + self._key) @@ -361,6 +397,7 @@ async def async_turn_on(self, **kwargs): await self.async_force_update() return True + @hub_error_handler async def async_turn_off(self, **kwargs): """Turn the device off.""" fn = getattr(self._device, "set_" + self._key) @@ -466,12 +503,14 @@ def extra_state_attributes(self): attrs["next_schedule_state"] = self._device.schedule.next.setting return attrs + @hub_error_handler async def async_turn_on(self, **kwargs): """Turn the device on.""" await self._device.turn_on() await self.async_force_update(2) return True + @hub_error_handler async def async_turn_off(self, **kwargs): """Turn the device off.""" await self._device.turn_off() @@ -522,12 +561,14 @@ def device_info(self): "via_device": (DOMAIN, self._data.wiserhub.system.name), } + @hub_error_handler async def async_turn_on(self, **kwargs): """Turn the device on.""" await self._smartplug.set_away_mode_action("Off") await self.async_force_update() return True + @hub_error_handler async def async_turn_off(self, **kwargs): """Turn the device off.""" await self._smartplug.set_away_mode_action("NoChange") @@ -578,12 +619,14 @@ def device_info(self): "via_device": (DOMAIN, self._data.wiserhub.system.name), } + @hub_error_handler async def async_turn_on(self, **kwargs): """Turn the device on.""" await self._light.set_away_mode_action("Off") await self.async_force_update() return True + @hub_error_handler async def async_turn_off(self, **kwargs): """Turn the device off.""" await self._light.set_away_mode_action("NoChange") @@ -634,12 +677,14 @@ def device_info(self): "via_device": (DOMAIN, self._data.wiserhub.system.name), } + @hub_error_handler async def async_turn_on(self, **kwargs): """Turn the device on.""" await self._shutter.set_away_mode_action("Close") await self.async_force_update() return True + @hub_error_handler async def async_turn_off(self, **kwargs): """Turn the device off.""" await self._shutter.set_away_mode_action("NoChange") @@ -698,12 +743,14 @@ def extra_state_attributes(self): attrs = {} return attrs + @hub_error_handler async def async_turn_on(self, **kwargs): """Turn the device on.""" await self._data.wiserhub.rooms.get_by_id(self._room_id).set_passive_mode(True) await self.async_force_update() return True + @hub_error_handler async def async_turn_off(self, **kwargs): """Turn the device off.""" room = self._data.wiserhub.rooms.get_by_id(self._room_id) @@ -711,3 +758,70 @@ async def async_turn_off(self, **kwargs): await room.cancel_overrides() await self.async_force_update() return True + + +class WiserShutterSummerComfortSwitch(WiserSwitch): + """Shutter Respect Summer Comfort Class.""" + + def __init__(self, data, ShutterId, name) -> None: + """Initialize the sensor.""" + self._name = name + self._shutter_id = ShutterId + super().__init__(data, name, "", "shutter", "mdi:sofa") + self._shutter = self._data.wiserhub.devices.get_by_id(self._shutter_id) + self._is_on = True if self._shutter.respect_summer_comfort == False else False + + @callback + def _handle_coordinator_update(self) -> None: + """Async Update to HA.""" + super()._handle_coordinator_update() + self._shutter = self._data.wiserhub.devices.get_by_id(self._shutter_id) + self._is_on = True if self._shutter.respect_summer_comfort == True else False + self.async_write_ha_state() + + @property + def name(self): + """Return the name of the Device.""" + return f"{get_device_name(self._data, self._shutter_id)} Respect Summer Comfort" + + @property + def unique_id(self): + """Return unique Id.""" + return get_unique_id( + self._data, self._shutter.product_type, self.name, self._shutter_id + ) + + @property + def device_info(self): + """Return device specific attributes.""" + return { + "name": get_device_name(self._data, self._shutter_id), + "identifiers": {(DOMAIN, get_identifier(self._data, self._shutter_id))}, + "manufacturer": MANUFACTURER, + "model": self._shutter.product_type, + "sw_version": self._shutter.firmware_version, + "via_device": (DOMAIN, self._data.wiserhub.system.name), + } + + @property + def extra_state_attributes(self): + """Return the device state attributes for the attribute card.""" + attrs = {} + + attrs["summer_comfort_lift"] = self._shutter.summer_comfort_lift + attrs["summer_comfort_tilt"] = self._shutter.summer_comfort_tilt + return attrs + + @hub_error_handler + async def async_turn_on(self, **kwargs): + """Turn the respect summer comfort on.""" + await self._shutter.set_respect_summer_comfort("true") + await self.async_force_update() + return True + + @hub_error_handler + async def async_turn_off(self, **kwargs): + """Turn the respect summer comfort off.""" + await self._shutter.set_respect_summer_comfort("false") + await self.async_force_update() + return True diff --git a/hacs.json b/hacs.json index c622fdf..0f77b62 100644 --- a/hacs.json +++ b/hacs.json @@ -1,6 +1,6 @@ { "name": "Drayton Wiser Integration for Home Assistant", - "homeassistant": "2023.10", + "homeassistant": "2023.12", "render_readme": true, "zip_release": true, "filename": "wiser.zip"