diff --git a/homeassistant/components/aladdin_connect/cover.py b/homeassistant/components/aladdin_connect/cover.py index f032fcecbe0..ee0955cbb3d 100644 --- a/homeassistant/components/aladdin_connect/cover.py +++ b/homeassistant/components/aladdin_connect/cover.py @@ -90,6 +90,7 @@ class AladdinDevice(CoverEntity): self._number = device["door_number"] self._name = device["name"] self._serial = device["serial"] + self._model = device["model"] self._attr_unique_id = f"{self._device_id}-{self._number}" self._attr_has_entity_name = True @@ -97,9 +98,10 @@ class AladdinDevice(CoverEntity): def device_info(self) -> DeviceInfo | None: """Device information for Aladdin Connect cover.""" return DeviceInfo( - identifiers={(DOMAIN, self._device_id)}, + identifiers={(DOMAIN, f"{self._device_id}-{self._number}")}, name=self._name, manufacturer="Overhead Door", + model=self._model, ) async def async_added_to_hass(self) -> None: diff --git a/homeassistant/components/aladdin_connect/model.py b/homeassistant/components/aladdin_connect/model.py index 63624b223a9..9b250459d3b 100644 --- a/homeassistant/components/aladdin_connect/model.py +++ b/homeassistant/components/aladdin_connect/model.py @@ -12,3 +12,4 @@ class DoorDevice(TypedDict): name: str status: str serial: str + model: str diff --git a/homeassistant/components/aladdin_connect/sensor.py b/homeassistant/components/aladdin_connect/sensor.py index 68631c57fc8..5fcc75fa27c 100644 --- a/homeassistant/components/aladdin_connect/sensor.py +++ b/homeassistant/components/aladdin_connect/sensor.py @@ -56,6 +56,15 @@ SENSORS: tuple[AccSensorEntityDescription, ...] = ( state_class=SensorStateClass.MEASUREMENT, value_fn=AladdinConnectClient.get_rssi_status, ), + AccSensorEntityDescription( + key="ble_strength", + name="BLE Strength", + device_class=SensorDeviceClass.SIGNAL_STRENGTH, + entity_registry_enabled_default=False, + native_unit_of_measurement=SIGNAL_STRENGTH_DECIBELS, + state_class=SensorStateClass.MEASUREMENT, + value_fn=AladdinConnectClient.get_ble_strength, + ), ) @@ -89,22 +98,26 @@ class AladdinConnectSensor(SensorEntity): device: DoorDevice, description: AccSensorEntityDescription, ) -> None: - """Initialize a sensor for an Abode device.""" + """Initialize a sensor for an Aladdin Connect device.""" self._device_id = device["device_id"] self._number = device["door_number"] self._name = device["name"] + self._model = device["model"] self._acc = acc self.entity_description = description self._attr_unique_id = f"{self._device_id}-{self._number}-{description.key}" self._attr_has_entity_name = True + if self._model == "01" and description.key in ("battery_level", "ble_strength"): + self._attr_entity_registry_enabled_default = True @property def device_info(self) -> DeviceInfo | None: """Device information for Aladdin Connect sensors.""" return DeviceInfo( - identifiers={(DOMAIN, self._device_id)}, + identifiers={(DOMAIN, f"{self._device_id}-{self._number}")}, name=self._name, manufacturer="Overhead Door", + model=self._model, ) @property diff --git a/homeassistant/components/deconz/alarm_control_panel.py b/homeassistant/components/deconz/alarm_control_panel.py index bf0f39b75d0..f2758d8da75 100644 --- a/homeassistant/components/deconz/alarm_control_panel.py +++ b/homeassistant/components/deconz/alarm_control_panel.py @@ -48,7 +48,7 @@ def get_alarm_system_id_for_unique_id( gateway: DeconzGateway, unique_id: str ) -> str | None: """Retrieve alarm system ID the unique ID is registered to.""" - for alarm_system in gateway.api.alarmsystems.values(): + for alarm_system in gateway.api.alarm_systems.values(): if unique_id in alarm_system.devices: return alarm_system.resource_id return None @@ -123,27 +123,27 @@ class DeconzAlarmControlPanel(DeconzDevice, AlarmControlPanelEntity): async def async_alarm_arm_away(self, code: str | None = None) -> None: """Send arm away command.""" if code: - await self.gateway.api.alarmsystems.arm( + await self.gateway.api.alarm_systems.arm( self.alarm_system_id, AlarmSystemArmAction.AWAY, code ) async def async_alarm_arm_home(self, code: str | None = None) -> None: """Send arm home command.""" if code: - await self.gateway.api.alarmsystems.arm( + await self.gateway.api.alarm_systems.arm( self.alarm_system_id, AlarmSystemArmAction.STAY, code ) async def async_alarm_arm_night(self, code: str | None = None) -> None: """Send arm night command.""" if code: - await self.gateway.api.alarmsystems.arm( + await self.gateway.api.alarm_systems.arm( self.alarm_system_id, AlarmSystemArmAction.NIGHT, code ) async def async_alarm_disarm(self, code: str | None = None) -> None: """Send disarm command.""" if code: - await self.gateway.api.alarmsystems.arm( + await self.gateway.api.alarm_systems.arm( self.alarm_system_id, AlarmSystemArmAction.DISARM, code ) diff --git a/homeassistant/components/deconz/binary_sensor.py b/homeassistant/components/deconz/binary_sensor.py index 814dec443e0..7e7f98ed634 100644 --- a/homeassistant/components/deconz/binary_sensor.py +++ b/homeassistant/components/deconz/binary_sensor.py @@ -159,7 +159,7 @@ ENTITY_DESCRIPTIONS = { ], } -BINARY_SENSOR_DESCRIPTIONS = [ +COMMON_BINARY_SENSOR_DESCRIPTIONS = [ DeconzBinarySensorDescription( key="tampered", value_fn=lambda device: device.tampered, @@ -215,7 +215,8 @@ async def async_setup_entry( sensor = gateway.api.sensors[sensor_id] for description in ( - ENTITY_DESCRIPTIONS.get(type(sensor), []) + BINARY_SENSOR_DESCRIPTIONS + ENTITY_DESCRIPTIONS.get(type(sensor), []) + + COMMON_BINARY_SENSOR_DESCRIPTIONS ): if ( not hasattr(sensor, description.key) @@ -284,8 +285,8 @@ class DeconzBinarySensor(DeconzDevice, BinarySensorEntity): if self._device.on is not None: attr[ATTR_ON] = self._device.on - if self._device.secondary_temperature is not None: - attr[ATTR_TEMPERATURE] = self._device.secondary_temperature + if self._device.internal_temperature is not None: + attr[ATTR_TEMPERATURE] = self._device.internal_temperature if isinstance(self._device, Presence): diff --git a/homeassistant/components/deconz/cover.py b/homeassistant/components/deconz/cover.py index 3e56882f15a..423978ea21b 100644 --- a/homeassistant/components/deconz/cover.py +++ b/homeassistant/components/deconz/cover.py @@ -4,6 +4,7 @@ from __future__ import annotations from typing import Any, cast from pydeconz.interfaces.lights import CoverAction +from pydeconz.models import ResourceType from pydeconz.models.event import EventType from pydeconz.models.light.cover import Cover @@ -23,9 +24,9 @@ from .deconz_device import DeconzDevice from .gateway import DeconzGateway, get_gateway_from_config_entry DECONZ_TYPE_TO_DEVICE_CLASS = { - "Level controllable output": CoverDeviceClass.DAMPER, - "Window covering controller": CoverDeviceClass.SHADE, - "Window covering device": CoverDeviceClass.SHADE, + ResourceType.LEVEL_CONTROLLABLE_OUTPUT.value: CoverDeviceClass.DAMPER, + ResourceType.WINDOW_COVERING_CONTROLLER.value: CoverDeviceClass.SHADE, + ResourceType.WINDOW_COVERING_DEVICE.value: CoverDeviceClass.SHADE, } @@ -72,6 +73,8 @@ class DeconzCover(DeconzDevice, CoverEntity): self._attr_device_class = DECONZ_TYPE_TO_DEVICE_CLASS.get(cover.type) + self.legacy_mode = cover.type == ResourceType.LEVEL_CONTROLLABLE_OUTPUT.value + @property def current_cover_position(self) -> int: """Return the current position of the cover.""" @@ -88,6 +91,7 @@ class DeconzCover(DeconzDevice, CoverEntity): await self.gateway.api.lights.covers.set_state( id=self._device.resource_id, lift=position, + legacy_mode=self.legacy_mode, ) async def async_open_cover(self, **kwargs: Any) -> None: @@ -95,6 +99,7 @@ class DeconzCover(DeconzDevice, CoverEntity): await self.gateway.api.lights.covers.set_state( id=self._device.resource_id, action=CoverAction.OPEN, + legacy_mode=self.legacy_mode, ) async def async_close_cover(self, **kwargs: Any) -> None: @@ -102,6 +107,7 @@ class DeconzCover(DeconzDevice, CoverEntity): await self.gateway.api.lights.covers.set_state( id=self._device.resource_id, action=CoverAction.CLOSE, + legacy_mode=self.legacy_mode, ) async def async_stop_cover(self, **kwargs: Any) -> None: @@ -109,6 +115,7 @@ class DeconzCover(DeconzDevice, CoverEntity): await self.gateway.api.lights.covers.set_state( id=self._device.resource_id, action=CoverAction.STOP, + legacy_mode=self.legacy_mode, ) @property @@ -124,6 +131,7 @@ class DeconzCover(DeconzDevice, CoverEntity): await self.gateway.api.lights.covers.set_state( id=self._device.resource_id, tilt=position, + legacy_mode=self.legacy_mode, ) async def async_open_cover_tilt(self, **kwargs: Any) -> None: @@ -131,6 +139,7 @@ class DeconzCover(DeconzDevice, CoverEntity): await self.gateway.api.lights.covers.set_state( id=self._device.resource_id, tilt=0, + legacy_mode=self.legacy_mode, ) async def async_close_cover_tilt(self, **kwargs: Any) -> None: @@ -138,6 +147,7 @@ class DeconzCover(DeconzDevice, CoverEntity): await self.gateway.api.lights.covers.set_state( id=self._device.resource_id, tilt=100, + legacy_mode=self.legacy_mode, ) async def async_stop_cover_tilt(self, **kwargs: Any) -> None: @@ -145,4 +155,5 @@ class DeconzCover(DeconzDevice, CoverEntity): await self.gateway.api.lights.covers.set_state( id=self._device.resource_id, action=CoverAction.STOP, + legacy_mode=self.legacy_mode, ) diff --git a/homeassistant/components/deconz/diagnostics.py b/homeassistant/components/deconz/diagnostics.py index 11854421512..5b7986fc4c9 100644 --- a/homeassistant/components/deconz/diagnostics.py +++ b/homeassistant/components/deconz/diagnostics.py @@ -26,7 +26,7 @@ async def async_get_config_entry_diagnostics( gateway.api.config.raw, REDACT_DECONZ_CONFIG ) diag["websocket_state"] = ( - gateway.api.websocket.state if gateway.api.websocket else "Unknown" + gateway.api.websocket.state.value if gateway.api.websocket else "Unknown" ) diag["deconz_ids"] = gateway.deconz_ids diag["entities"] = gateway.entities @@ -37,7 +37,7 @@ async def async_get_config_entry_diagnostics( } for event in gateway.events } - diag["alarm_systems"] = {k: v.raw for k, v in gateway.api.alarmsystems.items()} + diag["alarm_systems"] = {k: v.raw for k, v in gateway.api.alarm_systems.items()} diag["groups"] = {k: v.raw for k, v in gateway.api.groups.items()} diag["lights"] = {k: v.raw for k, v in gateway.api.lights.items()} diag["scenes"] = {k: v.raw for k, v in gateway.api.scenes.items()} diff --git a/homeassistant/components/deconz/gateway.py b/homeassistant/components/deconz/gateway.py index f8e4548cf91..6f29cef5190 100644 --- a/homeassistant/components/deconz/gateway.py +++ b/homeassistant/components/deconz/gateway.py @@ -169,7 +169,7 @@ class DeconzGateway: ) ) - for device_id in deconz_device_interface: + for device_id in sorted(deconz_device_interface, key=int): async_add_device(EventType.ADDED, device_id) initializing = False diff --git a/homeassistant/components/deconz/manifest.json b/homeassistant/components/deconz/manifest.json index e4e412056e6..38c78d849da 100644 --- a/homeassistant/components/deconz/manifest.json +++ b/homeassistant/components/deconz/manifest.json @@ -3,7 +3,7 @@ "name": "deCONZ", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/deconz", - "requirements": ["pydeconz==102"], + "requirements": ["pydeconz==104"], "ssdp": [ { "manufacturer": "Royal Philips Electronics", diff --git a/homeassistant/components/deconz/number.py b/homeassistant/components/deconz/number.py index 4c0959f950d..5e528dc2bb2 100644 --- a/homeassistant/components/deconz/number.py +++ b/homeassistant/components/deconz/number.py @@ -6,7 +6,7 @@ from collections.abc import Callable from dataclasses import dataclass from pydeconz.models.event import EventType -from pydeconz.models.sensor.presence import PRESENCE_DELAY, Presence +from pydeconz.models.sensor.presence import Presence from homeassistant.components.number import ( DOMAIN, @@ -42,7 +42,7 @@ ENTITY_DESCRIPTIONS = { key="delay", value_fn=lambda device: device.delay, suffix="Delay", - update_key=PRESENCE_DELAY, + update_key="delay", native_max_value=65535, native_min_value=0, native_step=1, diff --git a/homeassistant/components/deconz/sensor.py b/homeassistant/components/deconz/sensor.py index 15af1b3dd8f..b8bb0e8ffea 100644 --- a/homeassistant/components/deconz/sensor.py +++ b/homeassistant/components/deconz/sensor.py @@ -209,7 +209,7 @@ ENTITY_DESCRIPTIONS = { } -SENSOR_DESCRIPTIONS = [ +COMMON_SENSOR_DESCRIPTIONS = [ DeconzSensorDescription( key="battery", value_fn=lambda device: device.battery, @@ -221,8 +221,8 @@ SENSOR_DESCRIPTIONS = [ entity_category=EntityCategory.DIAGNOSTIC, ), DeconzSensorDescription( - key="secondary_temperature", - value_fn=lambda device: device.secondary_temperature, + key="internal_temperature", + value_fn=lambda device: device.internal_temperature, suffix="Temperature", update_key="temperature", device_class=SensorDeviceClass.TEMPERATURE, @@ -253,7 +253,7 @@ async def async_setup_entry( known_entities = set(gateway.entities[DOMAIN]) for description in ( - ENTITY_DESCRIPTIONS.get(type(sensor), []) + SENSOR_DESCRIPTIONS + ENTITY_DESCRIPTIONS.get(type(sensor), []) + COMMON_SENSOR_DESCRIPTIONS ): if ( not hasattr(sensor, description.key) @@ -342,8 +342,8 @@ class DeconzSensor(DeconzDevice, SensorEntity): if self._device.on is not None: attr[ATTR_ON] = self._device.on - if self._device.secondary_temperature is not None: - attr[ATTR_TEMPERATURE] = self._device.secondary_temperature + if self._device.internal_temperature is not None: + attr[ATTR_TEMPERATURE] = self._device.internal_temperature if isinstance(self._device, Consumption): attr[ATTR_POWER] = self._device.power @@ -384,14 +384,16 @@ class DeconzBatteryTracker: self.sensor = gateway.api.sensors[sensor_id] self.gateway = gateway self.async_add_entities = async_add_entities - self.unsub = self.sensor.subscribe(self.async_update_callback) + self.unsubscribe = self.sensor.subscribe(self.async_update_callback) @callback def async_update_callback(self) -> None: """Update the device's state.""" if "battery" in self.sensor.changed_keys: - self.unsub() + self.unsubscribe() known_entities = set(self.gateway.entities[DOMAIN]) - entity = DeconzSensor(self.sensor, self.gateway, SENSOR_DESCRIPTIONS[0]) + entity = DeconzSensor( + self.sensor, self.gateway, COMMON_SENSOR_DESCRIPTIONS[0] + ) if entity.unique_id not in known_entities: self.async_add_entities([entity]) diff --git a/homeassistant/components/edl21/sensor.py b/homeassistant/components/edl21/sensor.py index 65603b0c8c4..730acabbc98 100644 --- a/homeassistant/components/edl21/sensor.py +++ b/homeassistant/components/edl21/sensor.py @@ -22,6 +22,7 @@ from homeassistant.const import ( ELECTRIC_POTENTIAL_VOLT, ENERGY_KILO_WATT_HOUR, ENERGY_WATT_HOUR, + FREQUENCY_HERTZ, POWER_WATT, ) from homeassistant.core import HomeAssistant, callback @@ -252,6 +253,7 @@ SENSOR_UNIT_MAPPING = { "A": ELECTRIC_CURRENT_AMPERE, "V": ELECTRIC_POTENTIAL_VOLT, "°": DEGREE, + "Hz": FREQUENCY_HERTZ, } diff --git a/homeassistant/components/goodwe/number.py b/homeassistant/components/goodwe/number.py index 00d8d9d0cae..f8d3979879f 100644 --- a/homeassistant/components/goodwe/number.py +++ b/homeassistant/components/goodwe/number.py @@ -25,6 +25,7 @@ class GoodweNumberEntityDescriptionBase: getter: Callable[[Inverter], Awaitable[int]] setter: Callable[[Inverter, int], Awaitable[None]] + filter: Callable[[Inverter], bool] @dataclass @@ -35,17 +36,33 @@ class GoodweNumberEntityDescription( NUMBERS = ( + # non DT inverters (limit in W) GoodweNumberEntityDescription( key="grid_export_limit", name="Grid export limit", icon="mdi:transmission-tower", entity_category=EntityCategory.CONFIG, native_unit_of_measurement=POWER_WATT, - getter=lambda inv: inv.get_grid_export_limit(), - setter=lambda inv, val: inv.set_grid_export_limit(val), native_step=100, native_min_value=0, native_max_value=10000, + getter=lambda inv: inv.get_grid_export_limit(), + setter=lambda inv, val: inv.set_grid_export_limit(val), + filter=lambda inv: type(inv).__name__ != "DT", + ), + # DT inverters (limit is in %) + GoodweNumberEntityDescription( + key="grid_export_limit", + name="Grid export limit", + icon="mdi:transmission-tower", + entity_category=EntityCategory.CONFIG, + native_unit_of_measurement=PERCENTAGE, + native_step=1, + native_min_value=0, + native_max_value=100, + getter=lambda inv: inv.get_grid_export_limit(), + setter=lambda inv, val: inv.set_grid_export_limit(val), + filter=lambda inv: type(inv).__name__ == "DT", ), GoodweNumberEntityDescription( key="battery_discharge_depth", @@ -53,11 +70,12 @@ NUMBERS = ( icon="mdi:battery-arrow-down", entity_category=EntityCategory.CONFIG, native_unit_of_measurement=PERCENTAGE, - getter=lambda inv: inv.get_ongrid_battery_dod(), - setter=lambda inv, val: inv.set_ongrid_battery_dod(val), native_step=1, native_min_value=0, native_max_value=99, + getter=lambda inv: inv.get_ongrid_battery_dod(), + setter=lambda inv, val: inv.set_ongrid_battery_dod(val), + filter=lambda inv: True, ), ) @@ -73,7 +91,7 @@ async def async_setup_entry( entities = [] - for description in NUMBERS: + for description in filter(lambda dsc: dsc.filter(inverter), NUMBERS): try: current_value = await description.getter(inverter) except (InverterError, ValueError): @@ -82,7 +100,7 @@ async def async_setup_entry( continue entities.append( - InverterNumberEntity(device_info, description, inverter, current_value), + InverterNumberEntity(device_info, description, inverter, current_value) ) async_add_entities(entities) diff --git a/homeassistant/components/growatt_server/const.py b/homeassistant/components/growatt_server/const.py index 4fcc4887843..4e548ef2c2a 100644 --- a/homeassistant/components/growatt_server/const.py +++ b/homeassistant/components/growatt_server/const.py @@ -8,11 +8,15 @@ DEFAULT_PLANT_ID = "0" DEFAULT_NAME = "Growatt" SERVER_URLS = [ - "https://server.growatt.com/", + "https://server-api.growatt.com/", "https://server-us.growatt.com/", "http://server.smten.com/", ] +DEPRECATED_URLS = [ + "https://server.growatt.com/", +] + DEFAULT_URL = SERVER_URLS[0] DOMAIN = "growatt_server" diff --git a/homeassistant/components/growatt_server/sensor.py b/homeassistant/components/growatt_server/sensor.py index db045242987..c90bfa6f3fb 100644 --- a/homeassistant/components/growatt_server/sensor.py +++ b/homeassistant/components/growatt_server/sensor.py @@ -19,6 +19,7 @@ from .const import ( CONF_PLANT_ID, DEFAULT_PLANT_ID, DEFAULT_URL, + DEPRECATED_URLS, DOMAIN, LOGIN_INVALID_AUTH_CODE, ) @@ -62,12 +63,23 @@ async def async_setup_entry( async_add_entities: AddEntitiesCallback, ) -> None: """Set up the Growatt sensor.""" - config = config_entry.data + config = {**config_entry.data} username = config[CONF_USERNAME] password = config[CONF_PASSWORD] url = config.get(CONF_URL, DEFAULT_URL) name = config[CONF_NAME] + # If the URL has been deprecated then change to the default instead + if url in DEPRECATED_URLS: + _LOGGER.info( + "URL: %s has been deprecated, migrating to the latest default: %s", + url, + DEFAULT_URL, + ) + url = DEFAULT_URL + config[CONF_URL] = url + hass.config_entries.async_update_entry(config_entry, data=config) + api = growattServer.GrowattApi() api.server_url = url diff --git a/homeassistant/components/knx/manifest.json b/homeassistant/components/knx/manifest.json index 0f9b6b4b95a..bb5599939db 100644 --- a/homeassistant/components/knx/manifest.json +++ b/homeassistant/components/knx/manifest.json @@ -3,7 +3,7 @@ "name": "KNX", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/knx", - "requirements": ["xknx==1.0.0"], + "requirements": ["xknx==1.0.1"], "codeowners": ["@Julius2342", "@farmio", "@marvin-w"], "quality_scale": "platinum", "iot_class": "local_push", diff --git a/homeassistant/components/met_eireann/weather.py b/homeassistant/components/met_eireann/weather.py index f20f0e1254a..b872e9a8df6 100644 --- a/homeassistant/components/met_eireann/weather.py +++ b/homeassistant/components/met_eireann/weather.py @@ -13,7 +13,7 @@ from homeassistant.const import ( CONF_NAME, LENGTH_MILLIMETERS, PRESSURE_HPA, - SPEED_METERS_PER_SECOND, + SPEED_KILOMETERS_PER_HOUR, TEMP_CELSIUS, ) from homeassistant.core import HomeAssistant @@ -58,7 +58,7 @@ class MetEireannWeather(CoordinatorEntity, WeatherEntity): _attr_native_precipitation_unit = LENGTH_MILLIMETERS _attr_native_pressure_unit = PRESSURE_HPA _attr_native_temperature_unit = TEMP_CELSIUS - _attr_native_wind_speed_unit = SPEED_METERS_PER_SECOND + _attr_native_wind_speed_unit = SPEED_KILOMETERS_PER_HOUR def __init__(self, coordinator, config, hourly): """Initialise the platform with a data instance and site.""" diff --git a/homeassistant/components/shelly/climate.py b/homeassistant/components/shelly/climate.py index 453d67f39a7..a3f42c4a928 100644 --- a/homeassistant/components/shelly/climate.py +++ b/homeassistant/components/shelly/climate.py @@ -188,7 +188,10 @@ class BlockSleepingClimate( def hvac_mode(self) -> HVACMode: """HVAC current mode.""" if self.device_block is None: - return HVACMode(self.last_state.state) if self.last_state else HVACMode.OFF + if self.last_state and self.last_state.state in list(HVACMode): + return HVACMode(self.last_state.state) + return HVACMode.OFF + if self.device_block.mode is None or self._check_is_off(): return HVACMode.OFF diff --git a/homeassistant/components/switchbot/manifest.json b/homeassistant/components/switchbot/manifest.json index e70f467ae74..5631134cdf6 100644 --- a/homeassistant/components/switchbot/manifest.json +++ b/homeassistant/components/switchbot/manifest.json @@ -2,7 +2,7 @@ "domain": "switchbot", "name": "SwitchBot", "documentation": "https://www.home-assistant.io/integrations/switchbot", - "requirements": ["PySwitchbot==0.18.10"], + "requirements": ["PySwitchbot==0.18.14"], "config_flow": true, "dependencies": ["bluetooth"], "codeowners": [ diff --git a/homeassistant/components/zha/manifest.json b/homeassistant/components/zha/manifest.json index 1eb2536fed6..0648cbd86f7 100644 --- a/homeassistant/components/zha/manifest.json +++ b/homeassistant/components/zha/manifest.json @@ -4,15 +4,15 @@ "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/zha", "requirements": [ - "bellows==0.32.0", + "bellows==0.33.1", "pyserial==3.5", "pyserial-asyncio==0.6", "zha-quirks==0.0.78", "zigpy-deconz==0.18.0", - "zigpy==0.49.1", + "zigpy==0.50.2", "zigpy-xbee==0.15.0", - "zigpy-zigate==0.9.1", - "zigpy-znp==0.8.1" + "zigpy-zigate==0.9.2", + "zigpy-znp==0.8.2" ], "usb": [ { diff --git a/homeassistant/const.py b/homeassistant/const.py index f75c1802375..18da32c5e05 100644 --- a/homeassistant/const.py +++ b/homeassistant/const.py @@ -7,7 +7,7 @@ from .backports.enum import StrEnum MAJOR_VERSION: Final = 2022 MINOR_VERSION: Final = 8 -PATCH_VERSION: Final = "6" +PATCH_VERSION: Final = "7" __short_version__: Final = f"{MAJOR_VERSION}.{MINOR_VERSION}" __version__: Final = f"{__short_version__}.{PATCH_VERSION}" REQUIRED_PYTHON_VER: Final[tuple[int, int, int]] = (3, 9, 0) diff --git a/pyproject.toml b/pyproject.toml index e69bdd747d2..b9e6608e9b5 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "homeassistant" -version = "2022.8.6" +version = "2022.8.7" license = {text = "Apache-2.0"} description = "Open-source home automation platform running on Python 3." readme = "README.rst" diff --git a/requirements_all.txt b/requirements_all.txt index 01d1c95d878..df5bef18de6 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -37,7 +37,7 @@ PyRMVtransport==0.3.3 PySocks==1.7.1 # homeassistant.components.switchbot -PySwitchbot==0.18.10 +PySwitchbot==0.18.14 # homeassistant.components.transport_nsw PyTransportNSW==0.1.1 @@ -396,7 +396,7 @@ beautifulsoup4==4.11.1 # beewi_smartclim==0.0.10 # homeassistant.components.zha -bellows==0.32.0 +bellows==0.33.1 # homeassistant.components.bmw_connected_drive bimmer_connected==0.10.2 @@ -1455,7 +1455,7 @@ pydaikin==2.7.0 pydanfossair==0.1.0 # homeassistant.components.deconz -pydeconz==102 +pydeconz==104 # homeassistant.components.delijn pydelijn==1.0.0 @@ -2473,7 +2473,7 @@ xboxapi==2.0.1 xiaomi-ble==0.6.4 # homeassistant.components.knx -xknx==1.0.0 +xknx==1.0.1 # homeassistant.components.bluesound # homeassistant.components.fritz @@ -2529,13 +2529,13 @@ zigpy-deconz==0.18.0 zigpy-xbee==0.15.0 # homeassistant.components.zha -zigpy-zigate==0.9.1 +zigpy-zigate==0.9.2 # homeassistant.components.zha -zigpy-znp==0.8.1 +zigpy-znp==0.8.2 # homeassistant.components.zha -zigpy==0.49.1 +zigpy==0.50.2 # homeassistant.components.zoneminder zm-py==0.5.2 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 1b5068e81fb..e6637b90fac 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -33,7 +33,7 @@ PyRMVtransport==0.3.3 PySocks==1.7.1 # homeassistant.components.switchbot -PySwitchbot==0.18.10 +PySwitchbot==0.18.14 # homeassistant.components.transport_nsw PyTransportNSW==0.1.1 @@ -320,7 +320,7 @@ base36==0.1.1 beautifulsoup4==4.11.1 # homeassistant.components.zha -bellows==0.32.0 +bellows==0.33.1 # homeassistant.components.bmw_connected_drive bimmer_connected==0.10.2 @@ -1001,7 +1001,7 @@ pycoolmasternet-async==0.1.2 pydaikin==2.7.0 # homeassistant.components.deconz -pydeconz==102 +pydeconz==104 # homeassistant.components.dexcom pydexcom==0.2.3 @@ -1665,7 +1665,7 @@ xbox-webapi==2.0.11 xiaomi-ble==0.6.4 # homeassistant.components.knx -xknx==1.0.0 +xknx==1.0.1 # homeassistant.components.bluesound # homeassistant.components.fritz @@ -1703,13 +1703,13 @@ zigpy-deconz==0.18.0 zigpy-xbee==0.15.0 # homeassistant.components.zha -zigpy-zigate==0.9.1 +zigpy-zigate==0.9.2 # homeassistant.components.zha -zigpy-znp==0.8.1 +zigpy-znp==0.8.2 # homeassistant.components.zha -zigpy==0.49.1 +zigpy==0.50.2 # homeassistant.components.zwave_js zwave-js-server-python==0.39.0 diff --git a/tests/components/aladdin_connect/conftest.py b/tests/components/aladdin_connect/conftest.py index c8f7d240ba5..ee9afed9823 100644 --- a/tests/components/aladdin_connect/conftest.py +++ b/tests/components/aladdin_connect/conftest.py @@ -11,6 +11,7 @@ DEVICE_CONFIG_OPEN = { "status": "open", "link_status": "Connected", "serial": "12345", + "model": "02", } @@ -31,6 +32,8 @@ def fixture_mock_aladdinconnect_api(): mock_opener.get_battery_status.return_value = "99" mock_opener.async_get_rssi_status = AsyncMock(return_value="-55") mock_opener.get_rssi_status.return_value = "-55" + mock_opener.async_get_ble_strength = AsyncMock(return_value="-45") + mock_opener.get_ble_strength.return_value = "-45" mock_opener.get_doors = AsyncMock(return_value=[DEVICE_CONFIG_OPEN]) mock_opener.register_callback = mock.Mock(return_value=True) diff --git a/tests/components/aladdin_connect/test_sensor.py b/tests/components/aladdin_connect/test_sensor.py index 3702bcd9efa..282f6d3e04c 100644 --- a/tests/components/aladdin_connect/test_sensor.py +++ b/tests/components/aladdin_connect/test_sensor.py @@ -1,6 +1,6 @@ """Test the Aladdin Connect Sensors.""" from datetime import timedelta -from unittest.mock import MagicMock, patch +from unittest.mock import AsyncMock, MagicMock, patch from homeassistant.components.aladdin_connect.const import DOMAIN from homeassistant.components.aladdin_connect.cover import SCAN_INTERVAL @@ -10,6 +10,17 @@ from homeassistant.util.dt import utcnow from tests.common import MockConfigEntry, async_fire_time_changed +DEVICE_CONFIG_MODEL_01 = { + "device_id": 533255, + "door_number": 1, + "name": "home", + "status": "closed", + "link_status": "Connected", + "serial": "12345", + "model": "01", +} + + CONFIG = {"username": "test-user", "password": "test-password"} RELOAD_AFTER_UPDATE_DELAY = timedelta(seconds=31) @@ -83,3 +94,71 @@ async def test_sensors( state = hass.states.get("sensor.home_wi_fi_rssi") assert state + + +async def test_sensors_model_01( + hass: HomeAssistant, + mock_aladdinconnect_api: MagicMock, +) -> None: + """Test Sensors for AladdinConnect.""" + config_entry = MockConfigEntry( + domain=DOMAIN, + data=CONFIG, + unique_id="test-id", + ) + config_entry.add_to_hass(hass) + + await hass.async_block_till_done() + + with patch( + "homeassistant.components.aladdin_connect.AladdinConnectClient", + return_value=mock_aladdinconnect_api, + ): + mock_aladdinconnect_api.get_doors = AsyncMock( + return_value=[DEVICE_CONFIG_MODEL_01] + ) + await hass.config_entries.async_setup(config_entry.entry_id) + await hass.async_block_till_done() + + registry = entity_registry.async_get(hass) + entry = registry.async_get("sensor.home_battery_level") + assert entry + assert entry.disabled is False + assert entry.disabled_by is None + state = hass.states.get("sensor.home_battery_level") + assert state + + entry = registry.async_get("sensor.home_wi_fi_rssi") + await hass.async_block_till_done() + assert entry + assert entry.disabled + assert entry.disabled_by is entity_registry.RegistryEntryDisabler.INTEGRATION + update_entry = registry.async_update_entity( + entry.entity_id, **{"disabled_by": None} + ) + await hass.async_block_till_done() + assert update_entry != entry + assert update_entry.disabled is False + state = hass.states.get("sensor.home_wi_fi_rssi") + assert state is None + + update_entry = registry.async_update_entity( + entry.entity_id, **{"disabled_by": None} + ) + await hass.async_block_till_done() + async_fire_time_changed( + hass, + utcnow() + SCAN_INTERVAL, + ) + await hass.async_block_till_done() + + state = hass.states.get("sensor.home_wi_fi_rssi") + assert state + + entry = registry.async_get("sensor.home_ble_strength") + await hass.async_block_till_done() + assert entry + assert entry.disabled is False + assert entry.disabled_by is None + state = hass.states.get("sensor.home_ble_strength") + assert state diff --git a/tests/components/deconz/conftest.py b/tests/components/deconz/conftest.py index 8b92e94416a..44411ca40cf 100644 --- a/tests/components/deconz/conftest.py +++ b/tests/components/deconz/conftest.py @@ -3,7 +3,7 @@ from __future__ import annotations from unittest.mock import patch -from pydeconz.websocket import SIGNAL_CONNECTION_STATE, SIGNAL_DATA +from pydeconz.websocket import Signal import pytest from tests.components.light.conftest import mock_light_profiles # noqa: F401 @@ -20,10 +20,10 @@ def mock_deconz_websocket(): if data: mock.return_value.data = data - await pydeconz_gateway_session_handler(signal=SIGNAL_DATA) + await pydeconz_gateway_session_handler(signal=Signal.DATA) elif state: mock.return_value.state = state - await pydeconz_gateway_session_handler(signal=SIGNAL_CONNECTION_STATE) + await pydeconz_gateway_session_handler(signal=Signal.CONNECTION_STATE) else: raise NotImplementedError diff --git a/tests/components/deconz/test_cover.py b/tests/components/deconz/test_cover.py index 0c37edc221d..f2f4c7d7a2d 100644 --- a/tests/components/deconz/test_cover.py +++ b/tests/components/deconz/test_cover.py @@ -213,3 +213,111 @@ async def test_tilt_cover(hass, aioclient_mock): blocking=True, ) assert aioclient_mock.mock_calls[4][2] == {"stop": True} + + +async def test_level_controllable_output_cover(hass, aioclient_mock): + """Test that tilting a cover works.""" + data = { + "lights": { + "0": { + "etag": "4cefc909134c8e99086b55273c2bde67", + "hascolor": False, + "lastannounced": "2022-08-08T12:06:18Z", + "lastseen": "2022-08-14T14:22Z", + "manufacturername": "Keen Home Inc", + "modelid": "SV01-410-MP-1.0", + "name": "Vent", + "state": { + "alert": "none", + "bri": 242, + "on": False, + "reachable": True, + "sat": 10, + }, + "swversion": "0x00000012", + "type": "Level controllable output", + "uniqueid": "00:22:a3:00:00:00:00:00-01", + } + } + } + with patch.dict(DECONZ_WEB_REQUEST, data): + config_entry = await setup_deconz_integration(hass, aioclient_mock) + + assert len(hass.states.async_all()) == 1 + covering_device = hass.states.get("cover.vent") + assert covering_device.state == STATE_OPEN + assert covering_device.attributes[ATTR_CURRENT_TILT_POSITION] == 97 + + # Verify service calls for tilting cover + + mock_deconz_put_request(aioclient_mock, config_entry.data, "/lights/0/state") + + # Service open cover + + await hass.services.async_call( + COVER_DOMAIN, + SERVICE_OPEN_COVER, + {ATTR_ENTITY_ID: "cover.vent"}, + blocking=True, + ) + assert aioclient_mock.mock_calls[1][2] == {"on": False} + + # Service close cover + + await hass.services.async_call( + COVER_DOMAIN, + SERVICE_CLOSE_COVER, + {ATTR_ENTITY_ID: "cover.vent"}, + blocking=True, + ) + assert aioclient_mock.mock_calls[2][2] == {"on": True} + + # Service set cover position + + await hass.services.async_call( + COVER_DOMAIN, + SERVICE_SET_COVER_POSITION, + {ATTR_ENTITY_ID: "cover.vent", ATTR_POSITION: 40}, + blocking=True, + ) + assert aioclient_mock.mock_calls[3][2] == {"bri": 152} + + # Service set tilt cover + + await hass.services.async_call( + COVER_DOMAIN, + SERVICE_SET_COVER_TILT_POSITION, + {ATTR_ENTITY_ID: "cover.vent", ATTR_TILT_POSITION: 40}, + blocking=True, + ) + assert aioclient_mock.mock_calls[4][2] == {"sat": 152} + + # Service open tilt cover + + await hass.services.async_call( + COVER_DOMAIN, + SERVICE_OPEN_COVER_TILT, + {ATTR_ENTITY_ID: "cover.vent"}, + blocking=True, + ) + assert aioclient_mock.mock_calls[5][2] == {"sat": 0} + + # Service close tilt cover + + await hass.services.async_call( + COVER_DOMAIN, + SERVICE_CLOSE_COVER_TILT, + {ATTR_ENTITY_ID: "cover.vent"}, + blocking=True, + ) + assert aioclient_mock.mock_calls[6][2] == {"sat": 254} + + # Service stop cover movement + + await hass.services.async_call( + COVER_DOMAIN, + SERVICE_STOP_COVER_TILT, + {ATTR_ENTITY_ID: "cover.vent"}, + blocking=True, + ) + assert aioclient_mock.mock_calls[7][2] == {"bri_inc": 0} diff --git a/tests/components/deconz/test_diagnostics.py b/tests/components/deconz/test_diagnostics.py index d0905f5ba5f..459e0e910ab 100644 --- a/tests/components/deconz/test_diagnostics.py +++ b/tests/components/deconz/test_diagnostics.py @@ -1,6 +1,6 @@ """Test deCONZ diagnostics.""" -from pydeconz.websocket import STATE_RUNNING +from pydeconz.websocket import State from homeassistant.components.deconz.const import CONF_MASTER_GATEWAY from homeassistant.components.diagnostics import REDACTED @@ -17,7 +17,7 @@ async def test_entry_diagnostics( """Test config entry diagnostics.""" config_entry = await setup_deconz_integration(hass, aioclient_mock) - await mock_deconz_websocket(state=STATE_RUNNING) + await mock_deconz_websocket(state=State.RUNNING) await hass.async_block_till_done() assert await get_diagnostics_for_config_entry(hass, hass_client, config_entry) == { @@ -44,7 +44,7 @@ async def test_entry_diagnostics( "uuid": "1234", "websocketport": 1234, }, - "websocket_state": STATE_RUNNING, + "websocket_state": State.RUNNING.value, "deconz_ids": {}, "entities": { str(Platform.ALARM_CONTROL_PANEL): [], diff --git a/tests/components/deconz/test_gateway.py b/tests/components/deconz/test_gateway.py index e6ded3981c4..9471752eb8d 100644 --- a/tests/components/deconz/test_gateway.py +++ b/tests/components/deconz/test_gateway.py @@ -5,7 +5,7 @@ from copy import deepcopy from unittest.mock import patch import pydeconz -from pydeconz.websocket import STATE_RETRYING, STATE_RUNNING +from pydeconz.websocket import State import pytest from homeassistant.components import ssdp @@ -223,12 +223,12 @@ async def test_connection_status_signalling( assert hass.states.get("binary_sensor.presence").state == STATE_OFF - await mock_deconz_websocket(state=STATE_RETRYING) + await mock_deconz_websocket(state=State.RETRYING) await hass.async_block_till_done() assert hass.states.get("binary_sensor.presence").state == STATE_UNAVAILABLE - await mock_deconz_websocket(state=STATE_RUNNING) + await mock_deconz_websocket(state=State.RUNNING) await hass.async_block_till_done() assert hass.states.get("binary_sensor.presence").state == STATE_OFF