From 9abaa91921bc035dcccc681a51898d2897c89d31 Mon Sep 17 00:00:00 2001 From: Mark Parker Date: Tue, 26 Dec 2023 12:56:27 +0000 Subject: [PATCH 01/12] add v2 hub additional switches --- custom_components/wiser/switch.py | 103 ++++++++++++++++++++++++++++-- 1 file changed, 98 insertions(+), 5 deletions(-) diff --git a/custom_components/wiser/switch.py b/custom_components/wiser/switch.py index 367fce7..55e6acb 100755 --- a/custom_components/wiser/switch.py +++ b/custom_components/wiser/switch.py @@ -72,12 +72,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 +122,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 +159,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: @@ -711,3 +739,68 @@ 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 + + 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 + + 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 From 4fc8f0a0976fd50331577d79af4ce5294e66d408 Mon Sep 17 00:00:00 2001 From: Mark Parker Date: Tue, 26 Dec 2023 16:25:15 +0000 Subject: [PATCH 02/12] add error handler decorator --- custom_components/wiser/button.py | 8 ++++- custom_components/wiser/climate.py | 51 ++++++++++++++++++++++++++ custom_components/wiser/cover.py | 58 ++++++++++++++++++++---------- custom_components/wiser/helpers.py | 22 ++++++++++++ custom_components/wiser/light.py | 4 ++- custom_components/wiser/number.py | 4 ++- custom_components/wiser/select.py | 3 +- custom_components/wiser/switch.py | 21 +++++++++++ 8 files changed, 148 insertions(+), 23 deletions(-) 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/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/helpers.py b/custom_components/wiser/helpers.py index 69bbfe3..0215cba 100644 --- a/custom_components/wiser/helpers.py +++ b/custom_components/wiser/helpers.py @@ -1,5 +1,27 @@ +from aioWiserHeatAPI.wiserhub import ( + WiserHubConnectionError, + WiserHubAuthenticationError, + WiserHubRESTError, +) from homeassistant.core import HomeAssistant from .const import DOMAIN, ENTITY_PREFIX +import logging + +_LOGGER = logging.getLogger(__name__) + + +def hub_error_handler(func): + """Decorator to handle hub errors""" + async def wrapper(*args, **kwargs): + try: + await func(*args, **kwargs) + except ( + WiserHubConnectionError, + WiserHubAuthenticationError, + WiserHubRESTError, + ) as ex: + _LOGGER.warning(ex) + return wrapper def get_device_name(data, device_id, device_type="device"): diff --git a/custom_components/wiser/light.py b/custom_components/wiser/light.py index fca14db..cf5c67c 100644 --- a/custom_components/wiser/light.py +++ b/custom_components/wiser/light.py @@ -11,7 +11,7 @@ from homeassistant.helpers.update_coordinator import CoordinatorEntity from .const import DATA, DOMAIN, MANUFACTURER_SCHNEIDER -from .helpers import get_device_name, get_identifier, get_unique_id +from .helpers import get_device_name, get_identifier, get_unique_id, hub_error_handler from .schedules import WiserScheduleEntity MANUFACTURER = MANUFACTURER_SCHNEIDER @@ -141,6 +141,7 @@ def extra_state_attributes(self): return attrs + @hub_error_handler async def async_turn_on(self, **kwargs): """Turn light on.""" if ATTR_BRIGHTNESS in kwargs: @@ -155,6 +156,7 @@ async def async_turn_on(self, **kwargs): await self.async_force_update(2) return True + @hub_error_handler async def async_turn_off(self, **kwargs): """Turn light off.""" _LOGGER.debug(f"Turning off {self.name}") diff --git a/custom_components/wiser/number.py b/custom_components/wiser/number.py index 5db2e4c..46c5441 100644 --- a/custom_components/wiser/number.py +++ b/custom_components/wiser/number.py @@ -2,7 +2,7 @@ import logging from .const import DATA, DOMAIN, MANUFACTURER -from .helpers import get_device_name, get_identifier, get_unique_id +from .helpers import get_device_name, get_identifier, get_unique_id, hub_error_handler from aioWiserHeatAPI.wiserhub import TEMP_MINIMUM, TEMP_MAXIMUM @@ -127,6 +127,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") @@ -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/switch.py b/custom_components/wiser/switch.py index 55e6acb..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 @@ -235,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 @@ -264,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.""" @@ -272,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) @@ -325,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) @@ -332,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) @@ -382,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) @@ -389,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) @@ -494,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() @@ -550,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") @@ -606,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") @@ -662,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") @@ -726,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) @@ -793,12 +812,14 @@ def extra_state_attributes(self): 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") From b4026939cee6d454dd875a692d96638d90db8475 Mon Sep 17 00:00:00 2001 From: Mark Parker Date: Tue, 26 Dec 2023 16:26:01 +0000 Subject: [PATCH 03/12] black formatting --- custom_components/wiser/coordinator.py | 36 +++++++---------- custom_components/wiser/sensor.py | 54 ++++++++++++++------------ 2 files changed, 44 insertions(+), 46 deletions(-) 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/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 From dc967e86b83caff99a78fc209c4b458ca56b279d Mon Sep 17 00:00:00 2001 From: Mark Parker Date: Tue, 26 Dec 2023 16:33:53 +0000 Subject: [PATCH 04/12] add version to card resource registration --- custom_components/wiser/__init__.py | 1 - custom_components/wiser/const.py | 16 +++- custom_components/wiser/frontend/__init__.py | 81 ++++++++++++++++---- 3 files changed, 78 insertions(+), 20 deletions(-) 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/const.py b/custom_components/wiser/const.py index f3c5c05..ef13359 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.2", + }, + { + "name": "Wiser Zigbee Card", + "filename": "wiser-zigbee-card.js", + "version": "2.1.1", + }, +] + WISER_PLATFORMS = [ "climate", "sensor", 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 From 77405642ba731a26eea5764dfb7a51eea6377da3 Mon Sep 17 00:00:00 2001 From: Mark Parker Date: Tue, 26 Dec 2023 16:34:06 +0000 Subject: [PATCH 05/12] bump api to v1.5.5 --- custom_components/wiser/manifest.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/custom_components/wiser/manifest.json b/custom_components/wiser/manifest.json index 6799283..886b552 100755 --- a/custom_components/wiser/manifest.json +++ b/custom_components/wiser/manifest.json @@ -16,7 +16,7 @@ "iot_class": "local_polling", "issue_tracker": "https://github.com/asantaga/wiserHomeAssistantPlatform/issues", "requirements": [ - "aioWiserHeatAPI==1.5.1" + "aioWiserHeatAPI==1.5.5" ], "version": "3.4.1", "zeroconf": [ From bd500c908201810babea87e5594d6d32a5e5be02 Mon Sep 17 00:00:00 2001 From: Mark Parker Date: Tue, 26 Dec 2023 17:01:01 +0000 Subject: [PATCH 06/12] update schedule card to v1.3.3 --- custom_components/wiser/const.py | 2 +- .../wiser/frontend/wiser-schedule-card.js | 15 +++++++++++---- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/custom_components/wiser/const.py b/custom_components/wiser/const.py index ef13359..9722143 100755 --- a/custom_components/wiser/const.py +++ b/custom_components/wiser/const.py @@ -14,7 +14,7 @@ { "name": "Wiser Schedule Card", "filename": "wiser-schedule-card.js", - "version": "1.3.2", + "version": "1.3.3", }, { "name": "Wiser Zigbee Card", 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

Date: Tue, 26 Dec 2023 17:01:13 +0000 Subject: [PATCH 07/12] set min HA version to 2023.12 --- hacs.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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" From de8b20f938618c7b73309b05a9040282ba4dc35b Mon Sep 17 00:00:00 2001 From: Mark Parker Date: Tue, 26 Dec 2023 17:05:49 +0000 Subject: [PATCH 08/12] update readme --- README.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/README.md b/README.md index 8ba9df2..20021e7 100755 --- a/README.md +++ b/README.md @@ -22,6 +22,13 @@ 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 + - 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)) + - 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)) From a4f19470974f197c02af55f27e72be50f40084bd Mon Sep 17 00:00:00 2001 From: Mark Parker Date: Tue, 26 Dec 2023 17:06:38 +0000 Subject: [PATCH 09/12] update readme --- README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 20021e7..d8a1eee 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 From 2f0e6b06a73d666fe0346d922b964ddbaa3dc6b1 Mon Sep 17 00:00:00 2001 From: Mark Parker Date: Tue, 26 Dec 2023 17:07:30 +0000 Subject: [PATCH 10/12] update readme --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index d8a1eee..44a4d54 100755 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@ 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. +**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) From 65364d5c361a1e30d77e845dfd74ffe59ac96c18 Mon Sep 17 00:00:00 2001 From: Mark Parker Date: Tue, 26 Dec 2023 17:08:21 +0000 Subject: [PATCH 11/12] update readme --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 44a4d54..4c0eb00 100755 --- a/README.md +++ b/README.md @@ -26,6 +26,7 @@ For more information checkout the AMAZING community thread available on - 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)) - Added more v2 hub features and attributes From 2382a0cff55be90519c146c45dc451285ecc801d Mon Sep 17 00:00:00 2001 From: Mark Parker Date: Wed, 27 Dec 2023 11:18:52 +0000 Subject: [PATCH 12/12] update readme --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 4c0eb00..5297c1f 100755 --- a/README.md +++ b/README.md @@ -29,6 +29,7 @@ For more information checkout the AMAZING community thread available on - 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