From d5ff05bdf54a0acc78edfca2f1bd0c11163b64bc Mon Sep 17 00:00:00 2001 From: jan iversen Date: Tue, 12 Sep 2023 16:05:59 +0200 Subject: [PATCH 01/34] Remove modbus pragma no cover and solve nan (#99221) * Remove pragma no cover. * Ruff ! * Review comments. * update test. * Review. * review. * Add slave test. --- .../components/modbus/base_platform.py | 25 ++-- tests/components/modbus/test_sensor.py | 119 +++++++++++++++++- 2 files changed, 131 insertions(+), 13 deletions(-) diff --git a/homeassistant/components/modbus/base_platform.py b/homeassistant/components/modbus/base_platform.py index 65cfa1b49ba..cb8cd1cdc75 100644 --- a/homeassistant/components/modbus/base_platform.py +++ b/homeassistant/components/modbus/base_platform.py @@ -188,10 +188,14 @@ class BaseStructPlatform(BasePlatform, RestoreEntity): registers.reverse() return registers - def __process_raw_value(self, entry: float | int | str) -> float | int | str | None: + def __process_raw_value( + self, entry: float | int | str | bytes + ) -> float | int | str | bytes | None: """Process value from sensor with NaN handling, scaling, offset, min/max etc.""" if self._nan_value and entry in (self._nan_value, -self._nan_value): return None + if isinstance(entry, bytes): + return entry val: float | int = self._scale * entry + self._offset if self._min_value is not None and val < self._min_value: return self._min_value @@ -232,14 +236,20 @@ class BaseStructPlatform(BasePlatform, RestoreEntity): if isinstance(v_temp, int) and self._precision == 0: v_result.append(str(v_temp)) elif v_temp is None: - v_result.append("") # pragma: no cover + v_result.append("0") elif v_temp != v_temp: # noqa: PLR0124 # NaN float detection replace with None - v_result.append("nan") # pragma: no cover + v_result.append("0") else: v_result.append(f"{float(v_temp):.{self._precision}f}") return ",".join(map(str, v_result)) + # NaN float detection replace with None + if val[0] != val[0]: # noqa: PLR0124 + return None + if byte_string == b"nan\x00": + return None + # Apply scale, precision, limits to floats and ints val_result = self.__process_raw_value(val[0]) @@ -249,15 +259,10 @@ class BaseStructPlatform(BasePlatform, RestoreEntity): if val_result is None: return None - # NaN float detection replace with None - if val_result != val_result: # noqa: PLR0124 - return None # pragma: no cover if isinstance(val_result, int) and self._precision == 0: return str(val_result) - if isinstance(val_result, str): - if val_result == "nan": - val_result = None # pragma: no cover - return val_result + if isinstance(val_result, bytes): + return val_result.decode() return f"{float(val_result):.{self._precision}f}" diff --git a/tests/components/modbus/test_sensor.py b/tests/components/modbus/test_sensor.py index f72371ed42e..9cc55e5ab51 100644 --- a/tests/components/modbus/test_sensor.py +++ b/tests/components/modbus/test_sensor.py @@ -1,4 +1,6 @@ """The tests for the Modbus sensor component.""" +import struct + from freezegun.api import FrozenDateTimeFactory import pytest @@ -625,6 +627,21 @@ async def test_all_sensor(hass: HomeAssistant, mock_do_cycle, expected) -> None: @pytest.mark.parametrize( ("config_addon", "register_words", "do_exception", "expected"), [ + ( + { + CONF_SLAVE_COUNT: 1, + CONF_UNIQUE_ID: SLAVE_UNIQUE_ID, + CONF_DATA_TYPE: DataType.FLOAT32, + }, + [ + 0x5102, + 0x0304, + int.from_bytes(struct.pack(">f", float("nan"))[0:2]), + int.from_bytes(struct.pack(">f", float("nan"))[2:4]), + ], + False, + ["34899771392", "0"], + ), ( { CONF_SLAVE_COUNT: 0, @@ -902,6 +919,65 @@ async def test_wrong_unpack(hass: HomeAssistant, mock_do_cycle) -> None: assert hass.states.get(ENTITY_ID).state == STATE_UNAVAILABLE +@pytest.mark.parametrize( + "do_config", + [ + { + CONF_SENSORS: [ + { + CONF_NAME: TEST_ENTITY_NAME, + CONF_ADDRESS: 51, + CONF_SCAN_INTERVAL: 1, + }, + ], + }, + ], +) +@pytest.mark.parametrize( + ("config_addon", "register_words", "expected"), + [ + ( + { + CONF_DATA_TYPE: DataType.FLOAT32, + }, + [ + int.from_bytes(struct.pack(">f", float("nan"))[0:2]), + int.from_bytes(struct.pack(">f", float("nan"))[2:4]), + ], + STATE_UNAVAILABLE, + ), + ( + { + CONF_DATA_TYPE: DataType.FLOAT32, + }, + [0x6E61, 0x6E00], + STATE_UNAVAILABLE, + ), + ( + { + CONF_DATA_TYPE: DataType.CUSTOM, + CONF_COUNT: 2, + CONF_STRUCTURE: "4s", + }, + [0x6E61, 0x6E00], + STATE_UNAVAILABLE, + ), + ( + { + CONF_DATA_TYPE: DataType.CUSTOM, + CONF_COUNT: 2, + CONF_STRUCTURE: "4s", + }, + [0x6161, 0x6100], + "aaa\x00", + ), + ], +) +async def test_unpack_ok(hass: HomeAssistant, mock_do_cycle, expected) -> None: + """Run test for sensor.""" + assert hass.states.get(ENTITY_ID).state == expected + + @pytest.mark.parametrize( "do_config", [ @@ -965,10 +1041,35 @@ async def test_lazy_error_sensor( CONF_DATA_TYPE: DataType.CUSTOM, CONF_STRUCTURE: ">4f", }, - # floats: 7.931250095367432, 10.600000381469727, + # floats: nan, 10.600000381469727, # 1.000879611487865e-28, 10.566553115844727 - [0x40FD, 0xCCCD, 0x4129, 0x999A, 0x10FD, 0xC0CD, 0x4129, 0x109A], - "7.93,10.60,0.00,10.57", + [ + int.from_bytes(struct.pack(">f", float("nan"))[0:2]), + int.from_bytes(struct.pack(">f", float("nan"))[2:4]), + 0x4129, + 0x999A, + 0x10FD, + 0xC0CD, + 0x4129, + 0x109A, + ], + "0,10.60,0.00,10.57", + ), + ( + { + CONF_COUNT: 4, + CONF_DATA_TYPE: DataType.CUSTOM, + CONF_STRUCTURE: ">2i", + CONF_NAN_VALUE: 0x0000000F, + }, + # int: nan, 10, + [ + 0x0000, + 0x000F, + 0x0000, + 0x000A, + ], + "0,10", ), ( { @@ -988,6 +1089,18 @@ async def test_lazy_error_sensor( [0x0101], "257", ), + ( + { + CONF_COUNT: 8, + CONF_PRECISION: 2, + CONF_DATA_TYPE: DataType.CUSTOM, + CONF_STRUCTURE: ">4f", + }, + # floats: 7.931250095367432, 10.600000381469727, + # 1.000879611487865e-28, 10.566553115844727 + [0x40FD, 0xCCCD, 0x4129, 0x999A, 0x10FD, 0xC0CD, 0x4129, 0x109A], + "7.93,10.60,0.00,10.57", + ), ], ) async def test_struct_sensor(hass: HomeAssistant, mock_do_cycle, expected) -> None: From 367d893fc890f2701133c96193f384b0207b4590 Mon Sep 17 00:00:00 2001 From: Michael Arthur Date: Mon, 4 Sep 2023 01:53:23 +1200 Subject: [PATCH 02/34] Bugfix: Electric Kiwi reduce interval so oauth doesn't expire (#99489) decrease interval time as EK have broken/changed their oauth again --- homeassistant/components/electric_kiwi/coordinator.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/electric_kiwi/coordinator.py b/homeassistant/components/electric_kiwi/coordinator.py index 49611f9febd..b084f4656d5 100644 --- a/homeassistant/components/electric_kiwi/coordinator.py +++ b/homeassistant/components/electric_kiwi/coordinator.py @@ -14,7 +14,7 @@ from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, Upda _LOGGER = logging.getLogger(__name__) -HOP_SCAN_INTERVAL = timedelta(hours=2) +HOP_SCAN_INTERVAL = timedelta(minutes=20) class ElectricKiwiHOPDataCoordinator(DataUpdateCoordinator[Hop]): From d399ebb8e143de9ecb7e8a59652cf0fbec47d255 Mon Sep 17 00:00:00 2001 From: jan iversen Date: Sun, 3 Sep 2023 17:13:49 +0200 Subject: [PATCH 03/34] Read modbus data before scan_interval (#99243) Read before scan_interval. --- homeassistant/components/modbus/base_platform.py | 4 +--- tests/components/modbus/conftest.py | 2 +- tests/components/modbus/test_sensor.py | 5 +---- 3 files changed, 3 insertions(+), 8 deletions(-) diff --git a/homeassistant/components/modbus/base_platform.py b/homeassistant/components/modbus/base_platform.py index cb8cd1cdc75..0f5a10d3679 100644 --- a/homeassistant/components/modbus/base_platform.py +++ b/homeassistant/components/modbus/base_platform.py @@ -31,7 +31,6 @@ from homeassistant.helpers.event import async_call_later, async_track_time_inter from homeassistant.helpers.restore_state import RestoreEntity from .const import ( - ACTIVE_SCAN_INTERVAL, CALL_TYPE_COIL, CALL_TYPE_DISCRETE, CALL_TYPE_REGISTER_HOLDING, @@ -116,8 +115,7 @@ class BasePlatform(Entity): def async_run(self) -> None: """Remote start entity.""" self.async_hold(update=False) - if self._scan_interval == 0 or self._scan_interval > ACTIVE_SCAN_INTERVAL: - self._cancel_call = async_call_later(self.hass, 1, self.async_update) + self._cancel_call = async_call_later(self.hass, 1, self.async_update) if self._scan_interval > 0: self._cancel_timer = async_track_time_interval( self.hass, self.async_update, timedelta(seconds=self._scan_interval) diff --git a/tests/components/modbus/conftest.py b/tests/components/modbus/conftest.py index 23d3ee522bb..d4c7dfa5e10 100644 --- a/tests/components/modbus/conftest.py +++ b/tests/components/modbus/conftest.py @@ -149,7 +149,7 @@ async def mock_do_cycle_fixture( mock_pymodbus_return, ) -> FrozenDateTimeFactory: """Trigger update call with time_changed event.""" - freezer.tick(timedelta(seconds=90)) + freezer.tick(timedelta(seconds=1)) async_fire_time_changed(hass) await hass.async_block_till_done() return freezer diff --git a/tests/components/modbus/test_sensor.py b/tests/components/modbus/test_sensor.py index 9cc55e5ab51..c9507298ab2 100644 --- a/tests/components/modbus/test_sensor.py +++ b/tests/components/modbus/test_sensor.py @@ -269,7 +269,6 @@ async def test_config_wrong_struct_sensor( { CONF_NAME: TEST_ENTITY_NAME, CONF_ADDRESS: 51, - CONF_SCAN_INTERVAL: 1, }, ], }, @@ -727,7 +726,6 @@ async def test_slave_sensor(hass: HomeAssistant, mock_do_cycle, expected) -> Non CONF_NAME: TEST_ENTITY_NAME, CONF_ADDRESS: 51, CONF_INPUT_TYPE: CALL_TYPE_REGISTER_HOLDING, - CONF_SCAN_INTERVAL: 1, }, ], }, @@ -1011,7 +1009,7 @@ async def test_lazy_error_sensor( hass.states.async_set(ENTITY_ID, 17) await hass.async_block_till_done() assert hass.states.get(ENTITY_ID).state == start_expect - await do_next_cycle(hass, mock_do_cycle, 11) + await do_next_cycle(hass, mock_do_cycle, 5) assert hass.states.get(ENTITY_ID).state == start_expect await do_next_cycle(hass, mock_do_cycle, 11) assert hass.states.get(ENTITY_ID).state == end_expect @@ -1116,7 +1114,6 @@ async def test_struct_sensor(hass: HomeAssistant, mock_do_cycle, expected) -> No { CONF_NAME: TEST_ENTITY_NAME, CONF_ADDRESS: 201, - CONF_SCAN_INTERVAL: 1, }, ], }, From 7235de1a0ccbd1f92639b46445501de428b09adb Mon Sep 17 00:00:00 2001 From: jan iversen Date: Tue, 12 Sep 2023 16:01:15 +0200 Subject: [PATCH 04/34] Make modbus retry fast on read errors (#99576) * Fast retry on read errors. * Review comments. --- homeassistant/components/modbus/base_platform.py | 4 +++- homeassistant/components/modbus/sensor.py | 7 ++++++- tests/components/modbus/test_sensor.py | 12 ++++-------- 3 files changed, 13 insertions(+), 10 deletions(-) diff --git a/homeassistant/components/modbus/base_platform.py b/homeassistant/components/modbus/base_platform.py index 0f5a10d3679..f1a48728814 100644 --- a/homeassistant/components/modbus/base_platform.py +++ b/homeassistant/components/modbus/base_platform.py @@ -115,7 +115,9 @@ class BasePlatform(Entity): def async_run(self) -> None: """Remote start entity.""" self.async_hold(update=False) - self._cancel_call = async_call_later(self.hass, 1, self.async_update) + self._cancel_call = async_call_later( + self.hass, timedelta(milliseconds=100), self.async_update + ) if self._scan_interval > 0: self._cancel_timer = async_track_time_interval( self.hass, self.async_update, timedelta(seconds=self._scan_interval) diff --git a/homeassistant/components/modbus/sensor.py b/homeassistant/components/modbus/sensor.py index fe2d4bc415d..f2ed504b41b 100644 --- a/homeassistant/components/modbus/sensor.py +++ b/homeassistant/components/modbus/sensor.py @@ -1,7 +1,7 @@ """Support for Modbus Register sensors.""" from __future__ import annotations -from datetime import datetime +from datetime import datetime, timedelta import logging from typing import Any @@ -19,6 +19,7 @@ from homeassistant.const import ( ) from homeassistant.core import HomeAssistant, callback from homeassistant.helpers.entity_platform import AddEntitiesCallback +from homeassistant.helpers.event import async_call_later from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType from homeassistant.helpers.update_coordinator import ( CoordinatorEntity, @@ -106,12 +107,16 @@ class ModbusRegisterSensor(BaseStructPlatform, RestoreSensor, SensorEntity): """Update the state of the sensor.""" # remark "now" is a dummy parameter to avoid problems with # async_track_time_interval + self._cancel_call = None raw_result = await self._hub.async_pb_call( self._slave, self._address, self._count, self._input_type ) if raw_result is None: if self._lazy_errors: self._lazy_errors -= 1 + self._cancel_call = async_call_later( + self.hass, timedelta(seconds=1), self.async_update + ) return self._lazy_errors = self._lazy_error_count self._attr_available = False diff --git a/tests/components/modbus/test_sensor.py b/tests/components/modbus/test_sensor.py index c9507298ab2..de390d126fe 100644 --- a/tests/components/modbus/test_sensor.py +++ b/tests/components/modbus/test_sensor.py @@ -992,27 +992,23 @@ async def test_unpack_ok(hass: HomeAssistant, mock_do_cycle, expected) -> None: ], ) @pytest.mark.parametrize( - ("register_words", "do_exception", "start_expect", "end_expect"), + ("register_words", "do_exception"), [ ( [0x8000], True, - "17", - STATE_UNAVAILABLE, ), ], ) async def test_lazy_error_sensor( - hass: HomeAssistant, mock_do_cycle: FrozenDateTimeFactory, start_expect, end_expect + hass: HomeAssistant, mock_do_cycle: FrozenDateTimeFactory ) -> None: """Run test for sensor.""" hass.states.async_set(ENTITY_ID, 17) await hass.async_block_till_done() - assert hass.states.get(ENTITY_ID).state == start_expect + assert hass.states.get(ENTITY_ID).state == "17" await do_next_cycle(hass, mock_do_cycle, 5) - assert hass.states.get(ENTITY_ID).state == start_expect - await do_next_cycle(hass, mock_do_cycle, 11) - assert hass.states.get(ENTITY_ID).state == end_expect + assert hass.states.get(ENTITY_ID).state == STATE_UNAVAILABLE @pytest.mark.parametrize( From 4c125fda9d1e3684029896557a37eb7e67499e42 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tiit=20R=C3=A4tsep?= Date: Mon, 11 Sep 2023 12:15:46 +0300 Subject: [PATCH 05/34] Fix Soma cover tilt (#99717) --- homeassistant/components/soma/cover.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/soma/cover.py b/homeassistant/components/soma/cover.py index 26487756a44..4aa2559b140 100644 --- a/homeassistant/components/soma/cover.py +++ b/homeassistant/components/soma/cover.py @@ -51,6 +51,8 @@ class SomaTilt(SomaEntity, CoverEntity): | CoverEntityFeature.STOP_TILT | CoverEntityFeature.SET_TILT_POSITION ) + CLOSED_UP_THRESHOLD = 80 + CLOSED_DOWN_THRESHOLD = 20 @property def current_cover_tilt_position(self) -> int: @@ -60,7 +62,12 @@ class SomaTilt(SomaEntity, CoverEntity): @property def is_closed(self) -> bool: """Return if the cover tilt is closed.""" - return self.current_position == 0 + if ( + self.current_position < self.CLOSED_DOWN_THRESHOLD + or self.current_position > self.CLOSED_UP_THRESHOLD + ): + return True + return False def close_cover_tilt(self, **kwargs: Any) -> None: """Close the cover tilt.""" From 8e6ec01bfb3e2baffe952a8e8840f0cb36eb5d99 Mon Sep 17 00:00:00 2001 From: puddly <32534428+puddly@users.noreply.github.com> Date: Mon, 11 Sep 2023 14:36:01 +0200 Subject: [PATCH 06/34] Cache device trigger info during ZHA startup (#99764) * Do not connect to the radio hardware within `_connect_zigpy_app` * Make `connect_zigpy_app` public * Create radio manager instances from config entries * Cache device triggers on startup * reorg zha init * don't reuse gateway * don't nuke yaml configuration * review comments * Fix existing unit tests * Ensure `app.shutdown` is called, not just `app.disconnect` * Revert creating group entities and device registry entries early * Add unit tests --------- Co-authored-by: David F. Mulcahey --- homeassistant/components/zha/__init__.py | 47 ++++++- homeassistant/components/zha/core/const.py | 1 + homeassistant/components/zha/core/device.py | 21 +-- homeassistant/components/zha/core/gateway.py | 48 +++---- .../components/zha/device_trigger.py | 91 ++++++------ homeassistant/components/zha/radio_manager.py | 33 +++-- .../homeassistant_hardware/conftest.py | 2 +- .../homeassistant_sky_connect/conftest.py | 2 +- .../homeassistant_sky_connect/test_init.py | 2 +- .../homeassistant_yellow/conftest.py | 2 +- tests/components/zha/conftest.py | 25 +++- tests/components/zha/test_config_flow.py | 7 - tests/components/zha/test_device_trigger.py | 130 ++++++++++++++++-- tests/components/zha/test_init.py | 16 +-- tests/components/zha/test_radio_manager.py | 6 +- 15 files changed, 299 insertions(+), 134 deletions(-) diff --git a/homeassistant/components/zha/__init__.py b/homeassistant/components/zha/__init__.py index f9113ebaa90..662ddd080e0 100644 --- a/homeassistant/components/zha/__init__.py +++ b/homeassistant/components/zha/__init__.py @@ -1,5 +1,6 @@ """Support for Zigbee Home Automation devices.""" import asyncio +import contextlib import copy import logging import os @@ -33,13 +34,16 @@ from .core.const import ( CONF_ZIGPY, DATA_ZHA, DATA_ZHA_CONFIG, + DATA_ZHA_DEVICE_TRIGGER_CACHE, DATA_ZHA_GATEWAY, DOMAIN, PLATFORMS, SIGNAL_ADD_ENTITIES, RadioType, ) +from .core.device import get_device_automation_triggers from .core.discovery import GROUP_PROBE +from .radio_manager import ZhaRadioManager DEVICE_CONFIG_SCHEMA_ENTRY = vol.Schema({vol.Optional(CONF_TYPE): cv.string}) ZHA_CONFIG_SCHEMA = { @@ -134,9 +138,43 @@ async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> b else: _LOGGER.debug("ZHA storage file does not exist or was already removed") - # Re-use the gateway object between ZHA reloads - if (zha_gateway := zha_data.get(DATA_ZHA_GATEWAY)) is None: - zha_gateway = ZHAGateway(hass, config, config_entry) + # Load and cache device trigger information early + zha_data.setdefault(DATA_ZHA_DEVICE_TRIGGER_CACHE, {}) + + device_registry = dr.async_get(hass) + radio_mgr = ZhaRadioManager.from_config_entry(hass, config_entry) + + async with radio_mgr.connect_zigpy_app() as app: + for dev in app.devices.values(): + dev_entry = device_registry.async_get_device( + identifiers={(DOMAIN, str(dev.ieee))}, + connections={(dr.CONNECTION_ZIGBEE, str(dev.ieee))}, + ) + + if dev_entry is None: + continue + + zha_data[DATA_ZHA_DEVICE_TRIGGER_CACHE][dev_entry.id] = ( + str(dev.ieee), + get_device_automation_triggers(dev), + ) + + _LOGGER.debug("Trigger cache: %s", zha_data[DATA_ZHA_DEVICE_TRIGGER_CACHE]) + + zha_gateway = ZHAGateway(hass, config, config_entry) + + async def async_zha_shutdown(): + """Handle shutdown tasks.""" + await zha_gateway.shutdown() + # clean up any remaining entity metadata + # (entities that have been discovered but not yet added to HA) + # suppress KeyError because we don't know what state we may + # be in when we get here in failure cases + with contextlib.suppress(KeyError): + for platform in PLATFORMS: + del hass.data[DATA_ZHA][platform] + + config_entry.async_on_unload(async_zha_shutdown) try: await zha_gateway.async_initialize() @@ -155,9 +193,6 @@ async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> b repairs.async_delete_blocking_issues(hass) - config_entry.async_on_unload(zha_gateway.shutdown) - - device_registry = dr.async_get(hass) device_registry.async_get_or_create( config_entry_id=config_entry.entry_id, connections={(dr.CONNECTION_ZIGBEE, str(zha_gateway.coordinator_ieee))}, diff --git a/homeassistant/components/zha/core/const.py b/homeassistant/components/zha/core/const.py index 63b59e9d8d4..9569fc49659 100644 --- a/homeassistant/components/zha/core/const.py +++ b/homeassistant/components/zha/core/const.py @@ -186,6 +186,7 @@ DATA_ZHA = "zha" DATA_ZHA_CONFIG = "config" DATA_ZHA_BRIDGE_ID = "zha_bridge_id" DATA_ZHA_CORE_EVENTS = "zha_core_events" +DATA_ZHA_DEVICE_TRIGGER_CACHE = "zha_device_trigger_cache" DATA_ZHA_GATEWAY = "zha_gateway" DEBUG_COMP_BELLOWS = "bellows" diff --git a/homeassistant/components/zha/core/device.py b/homeassistant/components/zha/core/device.py index 1455173b27c..60bf78e516c 100644 --- a/homeassistant/components/zha/core/device.py +++ b/homeassistant/components/zha/core/device.py @@ -93,6 +93,16 @@ _UPDATE_ALIVE_INTERVAL = (60, 90) _CHECKIN_GRACE_PERIODS = 2 +def get_device_automation_triggers( + device: zigpy.device.Device, +) -> dict[tuple[str, str], dict[str, str]]: + """Get the supported device automation triggers for a zigpy device.""" + return { + ("device_offline", "device_offline"): {"device_event_type": "device_offline"}, + **getattr(device, "device_automation_triggers", {}), + } + + class DeviceStatus(Enum): """Status of a device.""" @@ -311,16 +321,7 @@ class ZHADevice(LogMixin): @cached_property def device_automation_triggers(self) -> dict[tuple[str, str], dict[str, str]]: """Return the device automation triggers for this device.""" - triggers = { - ("device_offline", "device_offline"): { - "device_event_type": "device_offline" - } - } - - if hasattr(self._zigpy_device, "device_automation_triggers"): - triggers.update(self._zigpy_device.device_automation_triggers) - - return triggers + return get_device_automation_triggers(self._zigpy_device) @property def available_signal(self) -> str: diff --git a/homeassistant/components/zha/core/gateway.py b/homeassistant/components/zha/core/gateway.py index 353bc6904d7..5cc2cd9a4b9 100644 --- a/homeassistant/components/zha/core/gateway.py +++ b/homeassistant/components/zha/core/gateway.py @@ -149,12 +149,6 @@ class ZHAGateway: self.config_entry = config_entry self._unsubs: list[Callable[[], None]] = [] - discovery.PROBE.initialize(self._hass) - discovery.GROUP_PROBE.initialize(self._hass) - - self.ha_device_registry = dr.async_get(self._hass) - self.ha_entity_registry = er.async_get(self._hass) - def get_application_controller_data(self) -> tuple[ControllerApplication, dict]: """Get an uninitialized instance of a zigpy `ControllerApplication`.""" radio_type = self.config_entry.data[CONF_RADIO_TYPE] @@ -197,6 +191,12 @@ class ZHAGateway: async def async_initialize(self) -> None: """Initialize controller and connect radio.""" + discovery.PROBE.initialize(self._hass) + discovery.GROUP_PROBE.initialize(self._hass) + + self.ha_device_registry = dr.async_get(self._hass) + self.ha_entity_registry = er.async_get(self._hass) + app_controller_cls, app_config = self.get_application_controller_data() self.application_controller = await app_controller_cls.new( config=app_config, @@ -204,23 +204,6 @@ class ZHAGateway: start_radio=False, ) - self._hass.data[DATA_ZHA][DATA_ZHA_GATEWAY] = self - - self.async_load_devices() - - # Groups are attached to the coordinator device so we need to load it early - coordinator = self._find_coordinator_device() - loaded_groups = False - - # We can only load groups early if the coordinator's model info has been stored - # in the zigpy database - if coordinator.model is not None: - self.coordinator_zha_device = self._async_get_or_create_device( - coordinator, restored=True - ) - self.async_load_groups() - loaded_groups = True - for attempt in range(STARTUP_RETRIES): try: await self.application_controller.startup(auto_form=True) @@ -242,14 +225,15 @@ class ZHAGateway: else: break + self._hass.data[DATA_ZHA][DATA_ZHA_GATEWAY] = self + self._hass.data[DATA_ZHA][DATA_ZHA_BRIDGE_ID] = str(self.coordinator_ieee) + self.coordinator_zha_device = self._async_get_or_create_device( self._find_coordinator_device(), restored=True ) - self._hass.data[DATA_ZHA][DATA_ZHA_BRIDGE_ID] = str(self.coordinator_ieee) - # If ZHA groups could not load early, we can safely load them now - if not loaded_groups: - self.async_load_groups() + self.async_load_devices() + self.async_load_groups() self.application_controller.add_listener(self) self.application_controller.groups.add_listener(self) @@ -766,7 +750,15 @@ class ZHAGateway: unsubscribe() for device in self.devices.values(): device.async_cleanup_handles() - await self.application_controller.shutdown() + # shutdown is called when the config entry unloads are processed + # there are cases where unloads are processed because of a failure of + # some sort and the application controller may not have been + # created yet + if ( + hasattr(self, "application_controller") + and self.application_controller is not None + ): + await self.application_controller.shutdown() def handle_message( self, diff --git a/homeassistant/components/zha/device_trigger.py b/homeassistant/components/zha/device_trigger.py index 9e33e3fa615..7a479443377 100644 --- a/homeassistant/components/zha/device_trigger.py +++ b/homeassistant/components/zha/device_trigger.py @@ -9,12 +9,12 @@ from homeassistant.components.device_automation.exceptions import ( from homeassistant.components.homeassistant.triggers import event as event_trigger from homeassistant.const import CONF_DEVICE_ID, CONF_DOMAIN, CONF_PLATFORM, CONF_TYPE from homeassistant.core import CALLBACK_TYPE, HomeAssistant -from homeassistant.exceptions import HomeAssistantError, IntegrationError +from homeassistant.exceptions import HomeAssistantError from homeassistant.helpers.trigger import TriggerActionType, TriggerInfo from homeassistant.helpers.typing import ConfigType from . import DOMAIN as ZHA_DOMAIN -from .core.const import ZHA_EVENT +from .core.const import DATA_ZHA, DATA_ZHA_DEVICE_TRIGGER_CACHE, ZHA_EVENT from .core.helpers import async_get_zha_device CONF_SUBTYPE = "subtype" @@ -26,21 +26,32 @@ TRIGGER_SCHEMA = DEVICE_TRIGGER_BASE_SCHEMA.extend( ) +def _get_device_trigger_data(hass: HomeAssistant, device_id: str) -> tuple[str, dict]: + """Get device trigger data for a device, falling back to the cache if possible.""" + + # First, try checking to see if the device itself is accessible + try: + zha_device = async_get_zha_device(hass, device_id) + except KeyError: + pass + else: + return str(zha_device.ieee), zha_device.device_automation_triggers + + # If not, check the trigger cache but allow any `KeyError`s to propagate + return hass.data[DATA_ZHA][DATA_ZHA_DEVICE_TRIGGER_CACHE][device_id] + + async def async_validate_trigger_config( hass: HomeAssistant, config: ConfigType ) -> ConfigType: """Validate config.""" config = TRIGGER_SCHEMA(config) + # Trigger validation will not occur if the config entry is not loaded + _, triggers = _get_device_trigger_data(hass, config[CONF_DEVICE_ID]) + trigger = (config[CONF_TYPE], config[CONF_SUBTYPE]) - try: - zha_device = async_get_zha_device(hass, config[CONF_DEVICE_ID]) - except (KeyError, AttributeError, IntegrationError) as err: - raise InvalidDeviceAutomationConfig from err - if ( - zha_device.device_automation_triggers is None - or trigger not in zha_device.device_automation_triggers - ): + if trigger not in triggers: raise InvalidDeviceAutomationConfig(f"device does not have trigger {trigger}") return config @@ -53,26 +64,26 @@ async def async_attach_trigger( trigger_info: TriggerInfo, ) -> CALLBACK_TYPE: """Listen for state changes based on configuration.""" - trigger_key: tuple[str, str] = (config[CONF_TYPE], config[CONF_SUBTYPE]) + try: - zha_device = async_get_zha_device(hass, config[CONF_DEVICE_ID]) - except (KeyError, AttributeError) as err: + ieee, triggers = _get_device_trigger_data(hass, config[CONF_DEVICE_ID]) + except KeyError as err: raise HomeAssistantError( f"Unable to get zha device {config[CONF_DEVICE_ID]}" ) from err - if trigger_key not in zha_device.device_automation_triggers: + trigger_key: tuple[str, str] = (config[CONF_TYPE], config[CONF_SUBTYPE]) + + if trigger_key not in triggers: raise HomeAssistantError(f"Unable to find trigger {trigger_key}") - trigger = zha_device.device_automation_triggers[trigger_key] - - event_config = { - event_trigger.CONF_PLATFORM: "event", - event_trigger.CONF_EVENT_TYPE: ZHA_EVENT, - event_trigger.CONF_EVENT_DATA: {DEVICE_IEEE: str(zha_device.ieee), **trigger}, - } - - event_config = event_trigger.TRIGGER_SCHEMA(event_config) + event_config = event_trigger.TRIGGER_SCHEMA( + { + event_trigger.CONF_PLATFORM: "event", + event_trigger.CONF_EVENT_TYPE: ZHA_EVENT, + event_trigger.CONF_EVENT_DATA: {DEVICE_IEEE: ieee, **triggers[trigger_key]}, + } + ) return await event_trigger.async_attach_trigger( hass, event_config, action, trigger_info, platform_type="device" ) @@ -83,24 +94,20 @@ async def async_get_triggers( ) -> list[dict[str, str]]: """List device triggers. - Make sure the device supports device automations and - if it does return the trigger list. + Make sure the device supports device automations and return the trigger list. """ - zha_device = async_get_zha_device(hass, device_id) + try: + _, triggers = _get_device_trigger_data(hass, device_id) + except KeyError as err: + raise InvalidDeviceAutomationConfig from err - if not zha_device.device_automation_triggers: - return [] - - triggers = [] - for trigger, subtype in zha_device.device_automation_triggers: - triggers.append( - { - CONF_DEVICE_ID: device_id, - CONF_DOMAIN: ZHA_DOMAIN, - CONF_PLATFORM: DEVICE, - CONF_TYPE: trigger, - CONF_SUBTYPE: subtype, - } - ) - - return triggers + return [ + { + CONF_DEVICE_ID: device_id, + CONF_DOMAIN: ZHA_DOMAIN, + CONF_PLATFORM: DEVICE, + CONF_TYPE: trigger, + CONF_SUBTYPE: subtype, + } + for trigger, subtype in triggers + ] diff --git a/homeassistant/components/zha/radio_manager.py b/homeassistant/components/zha/radio_manager.py index 751fea99847..df30a85cd7b 100644 --- a/homeassistant/components/zha/radio_manager.py +++ b/homeassistant/components/zha/radio_manager.py @@ -8,7 +8,7 @@ import copy import enum import logging import os -from typing import Any +from typing import Any, Self from bellows.config import CONF_USE_THREAD import voluptuous as vol @@ -127,8 +127,21 @@ class ZhaRadioManager: self.backups: list[zigpy.backups.NetworkBackup] = [] self.chosen_backup: zigpy.backups.NetworkBackup | None = None + @classmethod + def from_config_entry( + cls, hass: HomeAssistant, config_entry: config_entries.ConfigEntry + ) -> Self: + """Create an instance from a config entry.""" + mgr = cls() + mgr.hass = hass + mgr.device_path = config_entry.data[CONF_DEVICE][CONF_DEVICE_PATH] + mgr.device_settings = config_entry.data[CONF_DEVICE] + mgr.radio_type = RadioType[config_entry.data[CONF_RADIO_TYPE]] + + return mgr + @contextlib.asynccontextmanager - async def _connect_zigpy_app(self) -> ControllerApplication: + async def connect_zigpy_app(self) -> ControllerApplication: """Connect to the radio with the current config and then clean up.""" assert self.radio_type is not None @@ -155,10 +168,9 @@ class ZhaRadioManager: ) try: - await app.connect() yield app finally: - await app.disconnect() + await app.shutdown() await asyncio.sleep(CONNECT_DELAY_S) async def restore_backup( @@ -170,7 +182,8 @@ class ZhaRadioManager: ): return - async with self._connect_zigpy_app() as app: + async with self.connect_zigpy_app() as app: + await app.connect() await app.backups.restore_backup(backup, **kwargs) @staticmethod @@ -218,7 +231,9 @@ class ZhaRadioManager: """Connect to the radio and load its current network settings.""" backup = None - async with self._connect_zigpy_app() as app: + async with self.connect_zigpy_app() as app: + await app.connect() + # Check if the stick has any settings and load them try: await app.load_network_info() @@ -241,12 +256,14 @@ class ZhaRadioManager: async def async_form_network(self) -> None: """Form a brand-new network.""" - async with self._connect_zigpy_app() as app: + async with self.connect_zigpy_app() as app: + await app.connect() await app.form_network() async def async_reset_adapter(self) -> None: """Reset the current adapter.""" - async with self._connect_zigpy_app() as app: + async with self.connect_zigpy_app() as app: + await app.connect() await app.reset_network_info() async def async_restore_backup_step_1(self) -> bool: diff --git a/tests/components/homeassistant_hardware/conftest.py b/tests/components/homeassistant_hardware/conftest.py index 60083c2de94..02b468e558e 100644 --- a/tests/components/homeassistant_hardware/conftest.py +++ b/tests/components/homeassistant_hardware/conftest.py @@ -23,7 +23,7 @@ def mock_zha_config_flow_setup() -> Generator[None, None, None]: with patch( "bellows.zigbee.application.ControllerApplication.probe", side_effect=mock_probe ), patch( - "homeassistant.components.zha.radio_manager.ZhaRadioManager._connect_zigpy_app", + "homeassistant.components.zha.radio_manager.ZhaRadioManager.connect_zigpy_app", return_value=mock_connect_app, ), patch( "homeassistant.components.zha.async_setup_entry", diff --git a/tests/components/homeassistant_sky_connect/conftest.py b/tests/components/homeassistant_sky_connect/conftest.py index 85017866db9..90dbe5af384 100644 --- a/tests/components/homeassistant_sky_connect/conftest.py +++ b/tests/components/homeassistant_sky_connect/conftest.py @@ -25,7 +25,7 @@ def mock_zha(): ) with patch( - "homeassistant.components.zha.radio_manager.ZhaRadioManager._connect_zigpy_app", + "homeassistant.components.zha.radio_manager.ZhaRadioManager.connect_zigpy_app", return_value=mock_connect_app, ), patch( "homeassistant.components.zha.async_setup_entry", diff --git a/tests/components/homeassistant_sky_connect/test_init.py b/tests/components/homeassistant_sky_connect/test_init.py index cbf1cfa7d36..3afc8c24774 100644 --- a/tests/components/homeassistant_sky_connect/test_init.py +++ b/tests/components/homeassistant_sky_connect/test_init.py @@ -45,7 +45,7 @@ def mock_zha_config_flow_setup() -> Generator[None, None, None]: with patch( "bellows.zigbee.application.ControllerApplication.probe", side_effect=mock_probe ), patch( - "homeassistant.components.zha.radio_manager.ZhaRadioManager._connect_zigpy_app", + "homeassistant.components.zha.radio_manager.ZhaRadioManager.connect_zigpy_app", return_value=mock_connect_app, ): yield diff --git a/tests/components/homeassistant_yellow/conftest.py b/tests/components/homeassistant_yellow/conftest.py index e4a666f9f04..a7d66d659f0 100644 --- a/tests/components/homeassistant_yellow/conftest.py +++ b/tests/components/homeassistant_yellow/conftest.py @@ -23,7 +23,7 @@ def mock_zha_config_flow_setup() -> Generator[None, None, None]: with patch( "bellows.zigbee.application.ControllerApplication.probe", side_effect=mock_probe ), patch( - "homeassistant.components.zha.radio_manager.ZhaRadioManager._connect_zigpy_app", + "homeassistant.components.zha.radio_manager.ZhaRadioManager.connect_zigpy_app", return_value=mock_connect_app, ), patch( "homeassistant.components.zha.async_setup_entry", diff --git a/tests/components/zha/conftest.py b/tests/components/zha/conftest.py index 4778f3216da..7d391872a77 100644 --- a/tests/components/zha/conftest.py +++ b/tests/components/zha/conftest.py @@ -293,14 +293,20 @@ def zigpy_device_mock(zigpy_app_controller): return _mock_dev +@patch("homeassistant.components.zha.setup_quirks", MagicMock(return_value=True)) @pytest.fixture def zha_device_joined(hass, setup_zha): """Return a newly joined ZHA device.""" + setup_zha_fixture = setup_zha - async def _zha_device(zigpy_dev): + async def _zha_device(zigpy_dev, *, setup_zha: bool = True): zigpy_dev.last_seen = time.time() - await setup_zha() + + if setup_zha: + await setup_zha_fixture() + zha_gateway = common.get_zha_gateway(hass) + zha_gateway.application_controller.devices[zigpy_dev.ieee] = zigpy_dev await zha_gateway.async_device_initialized(zigpy_dev) await hass.async_block_till_done() return zha_gateway.get_device(zigpy_dev.ieee) @@ -308,17 +314,21 @@ def zha_device_joined(hass, setup_zha): return _zha_device +@patch("homeassistant.components.zha.setup_quirks", MagicMock(return_value=True)) @pytest.fixture def zha_device_restored(hass, zigpy_app_controller, setup_zha): """Return a restored ZHA device.""" + setup_zha_fixture = setup_zha - async def _zha_device(zigpy_dev, last_seen=None): + async def _zha_device(zigpy_dev, *, last_seen=None, setup_zha: bool = True): zigpy_app_controller.devices[zigpy_dev.ieee] = zigpy_dev if last_seen is not None: zigpy_dev.last_seen = last_seen - await setup_zha() + if setup_zha: + await setup_zha_fixture() + zha_gateway = hass.data[zha_const.DATA_ZHA][zha_const.DATA_ZHA_GATEWAY] return zha_gateway.get_device(zigpy_dev.ieee) @@ -376,3 +386,10 @@ def hass_disable_services(hass): hass, "services", MagicMock(has_service=MagicMock(return_value=True)) ): yield hass + + +@pytest.fixture(autouse=True) +def speed_up_radio_mgr(): + """Speed up the radio manager connection time by removing delays.""" + with patch("homeassistant.components.zha.radio_manager.CONNECT_DELAY_S", 0.00001): + yield diff --git a/tests/components/zha/test_config_flow.py b/tests/components/zha/test_config_flow.py index 77d8a615c72..d50d43da675 100644 --- a/tests/components/zha/test_config_flow.py +++ b/tests/components/zha/test_config_flow.py @@ -62,13 +62,6 @@ def mock_multipan_platform(): yield -@pytest.fixture(autouse=True) -def reduce_reconnect_timeout(): - """Reduces reconnect timeout to speed up tests.""" - with patch("homeassistant.components.zha.radio_manager.CONNECT_DELAY_S", 0.01): - yield - - @pytest.fixture(autouse=True) def mock_app(): """Mock zigpy app interface.""" diff --git a/tests/components/zha/test_device_trigger.py b/tests/components/zha/test_device_trigger.py index 22f62cb977a..491e2d96d4f 100644 --- a/tests/components/zha/test_device_trigger.py +++ b/tests/components/zha/test_device_trigger.py @@ -9,6 +9,9 @@ import zigpy.zcl.clusters.general as general import homeassistant.components.automation as automation from homeassistant.components.device_automation import DeviceAutomationType +from homeassistant.components.device_automation.exceptions import ( + InvalidDeviceAutomationConfig, +) from homeassistant.components.zha.core.const import ATTR_ENDPOINT_ID from homeassistant.const import Platform from homeassistant.core import HomeAssistant @@ -20,6 +23,7 @@ from .common import async_enable_traffic from .conftest import SIG_EP_INPUT, SIG_EP_OUTPUT, SIG_EP_PROFILE, SIG_EP_TYPE from tests.common import ( + MockConfigEntry, async_fire_time_changed, async_get_device_automations, async_mock_service, @@ -45,6 +49,16 @@ LONG_PRESS = "remote_button_long_press" LONG_RELEASE = "remote_button_long_release" +SWITCH_SIGNATURE = { + 1: { + SIG_EP_INPUT: [general.Basic.cluster_id], + SIG_EP_OUTPUT: [general.OnOff.cluster_id], + SIG_EP_TYPE: zigpy.profiles.zha.DeviceType.ON_OFF_SWITCH, + SIG_EP_PROFILE: zigpy.profiles.zha.PROFILE_ID, + } +} + + @pytest.fixture(autouse=True) def sensor_platforms_only(): """Only set up the sensor platform and required base platforms to speed up tests.""" @@ -72,16 +86,7 @@ def calls(hass): async def mock_devices(hass, zigpy_device_mock, zha_device_joined_restored): """IAS device fixture.""" - zigpy_device = zigpy_device_mock( - { - 1: { - SIG_EP_INPUT: [general.Basic.cluster_id], - SIG_EP_OUTPUT: [general.OnOff.cluster_id], - SIG_EP_TYPE: zigpy.profiles.zha.DeviceType.ON_OFF_SWITCH, - SIG_EP_PROFILE: zigpy.profiles.zha.PROFILE_ID, - } - } - ) + zigpy_device = zigpy_device_mock(SWITCH_SIGNATURE) zha_device = await zha_device_joined_restored(zigpy_device) zha_device.update_available(True) @@ -397,3 +402,108 @@ async def test_exception_bad_trigger( "Unnamed automation failed to setup triggers and has been disabled: " "device does not have trigger ('junk', 'junk')" in caplog.text ) + + +async def test_validate_trigger_config_missing_info( + hass: HomeAssistant, + config_entry: MockConfigEntry, + zigpy_device_mock, + mock_zigpy_connect, + zha_device_joined, + caplog: pytest.LogCaptureFixture, +) -> None: + """Test device triggers referring to a missing device.""" + + # Join a device + switch = zigpy_device_mock(SWITCH_SIGNATURE) + await zha_device_joined(switch) + + # After we unload the config entry, trigger info was not cached on startup, nor can + # it be pulled from the current device, making it impossible to validate triggers + await hass.config_entries.async_unload(config_entry.entry_id) + + ha_device_registry = dr.async_get(hass) + reg_device = ha_device_registry.async_get_device( + identifiers={("zha", str(switch.ieee))} + ) + + assert await async_setup_component( + hass, + automation.DOMAIN, + { + automation.DOMAIN: [ + { + "trigger": { + "device_id": reg_device.id, + "domain": "zha", + "platform": "device", + "type": "junk", + "subtype": "junk", + }, + "action": { + "service": "test.automation", + "data": {"message": "service called"}, + }, + } + ] + }, + ) + + assert "Unable to get zha device" in caplog.text + + with pytest.raises(InvalidDeviceAutomationConfig): + await async_get_device_automations( + hass, DeviceAutomationType.TRIGGER, reg_device.id + ) + + +async def test_validate_trigger_config_unloaded_bad_info( + hass: HomeAssistant, + config_entry: MockConfigEntry, + zigpy_device_mock, + mock_zigpy_connect, + zha_device_joined, + caplog: pytest.LogCaptureFixture, +) -> None: + """Test device triggers referring to a missing device.""" + + # Join a device + switch = zigpy_device_mock(SWITCH_SIGNATURE) + await zha_device_joined(switch) + + # After we unload the config entry, trigger info was not cached on startup, nor can + # it be pulled from the current device, making it impossible to validate triggers + await hass.config_entries.async_unload(config_entry.entry_id) + + # Reload ZHA to persist the device info in the cache + await hass.config_entries.async_setup(config_entry.entry_id) + await hass.config_entries.async_unload(config_entry.entry_id) + + ha_device_registry = dr.async_get(hass) + reg_device = ha_device_registry.async_get_device( + identifiers={("zha", str(switch.ieee))} + ) + + assert await async_setup_component( + hass, + automation.DOMAIN, + { + automation.DOMAIN: [ + { + "trigger": { + "device_id": reg_device.id, + "domain": "zha", + "platform": "device", + "type": "junk", + "subtype": "junk", + }, + "action": { + "service": "test.automation", + "data": {"message": "service called"}, + }, + } + ] + }, + ) + + assert "Unable to find trigger" in caplog.text diff --git a/tests/components/zha/test_init.py b/tests/components/zha/test_init.py index 63ca10bbf91..6bac012d667 100644 --- a/tests/components/zha/test_init.py +++ b/tests/components/zha/test_init.py @@ -6,7 +6,6 @@ import pytest from zigpy.config import CONF_DEVICE, CONF_DEVICE_PATH from zigpy.exceptions import TransientConnectionError -from homeassistant.components.zha import async_setup_entry from homeassistant.components.zha.core.const import ( CONF_BAUDRATE, CONF_RADIO_TYPE, @@ -22,7 +21,7 @@ from .test_light import LIGHT_ON_OFF from tests.common import MockConfigEntry -DATA_RADIO_TYPE = "deconz" +DATA_RADIO_TYPE = "ezsp" DATA_PORT_PATH = "/dev/serial/by-id/FTDI_USB__-__Serial_Cable_12345678-if00-port0" @@ -137,7 +136,7 @@ async def test_config_depreciation(hass: HomeAssistant, zha_config) -> None: "homeassistant.components.zha.websocket_api.async_load_api", Mock(return_value=True) ) async def test_setup_with_v3_cleaning_uri( - hass: HomeAssistant, path: str, cleaned_path: str + hass: HomeAssistant, path: str, cleaned_path: str, mock_zigpy_connect ) -> None: """Test migration of config entry from v3, applying corrections to the port path.""" config_entry_v3 = MockConfigEntry( @@ -150,14 +149,9 @@ async def test_setup_with_v3_cleaning_uri( ) config_entry_v3.add_to_hass(hass) - with patch( - "homeassistant.components.zha.ZHAGateway", return_value=AsyncMock() - ) as mock_gateway: - mock_gateway.return_value.coordinator_ieee = "mock_ieee" - mock_gateway.return_value.radio_description = "mock_radio" - - assert await async_setup_entry(hass, config_entry_v3) - hass.data[DOMAIN]["zha_gateway"] = mock_gateway.return_value + await hass.config_entries.async_setup(config_entry_v3.entry_id) + await hass.async_block_till_done() + await hass.config_entries.async_unload(config_entry_v3.entry_id) assert config_entry_v3.data[CONF_RADIO_TYPE] == DATA_RADIO_TYPE assert config_entry_v3.data[CONF_DEVICE][CONF_DEVICE_PATH] == cleaned_path diff --git a/tests/components/zha/test_radio_manager.py b/tests/components/zha/test_radio_manager.py index 7acf9219d67..1467e2e2951 100644 --- a/tests/components/zha/test_radio_manager.py +++ b/tests/components/zha/test_radio_manager.py @@ -32,9 +32,7 @@ def disable_platform_only(): @pytest.fixture(autouse=True) def reduce_reconnect_timeout(): """Reduces reconnect timeout to speed up tests.""" - with patch( - "homeassistant.components.zha.radio_manager.CONNECT_DELAY_S", 0.0001 - ), patch("homeassistant.components.zha.radio_manager.RETRY_DELAY_S", 0.0001): + with patch("homeassistant.components.zha.radio_manager.RETRY_DELAY_S", 0.0001): yield @@ -99,7 +97,7 @@ def mock_connect_zigpy_app() -> Generator[MagicMock, None, None]: ) with patch( - "homeassistant.components.zha.radio_manager.ZhaRadioManager._connect_zigpy_app", + "homeassistant.components.zha.radio_manager.ZhaRadioManager.connect_zigpy_app", return_value=mock_connect_app, ): yield mock_connect_app From f0b6367444fd59feed50a58033d4774fdbfbbb5f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A5le=20Stor=C3=B8=20Hauknes?= Date: Tue, 12 Sep 2023 15:59:54 +0200 Subject: [PATCH 07/34] Airthings BLE unique id migration (#99832) * Fix sensor unique id * Add sensor identifiers * Migrate entities to new unique id * Fix linting issues * Fix crash when migrating entity fails * Change how entities are migrated * Remve debug logging * Remove unneeded async * Remove migration code from init file * Add migration code to sensor.py * Adjust for loops to improve speed * Bugfixes, improve documentation * Remove old comment * Remove unused function parameter * Address PR feedback * Add tests * Improve tests and test data * Refactor test * Update logger level Co-authored-by: J. Nick Koston * Adjust PR comments * Address more PR comments * Address PR comments and adjust tests * Fix PR comment --------- Co-authored-by: J. Nick Koston --- .../components/airthings_ble/sensor.py | 55 ++++- tests/components/airthings_ble/__init__.py | 108 ++++++++- tests/components/airthings_ble/test_sensor.py | 213 ++++++++++++++++++ 3 files changed, 365 insertions(+), 11 deletions(-) create mode 100644 tests/components/airthings_ble/test_sensor.py diff --git a/homeassistant/components/airthings_ble/sensor.py b/homeassistant/components/airthings_ble/sensor.py index 4783f3e3b35..b66d6b8f810 100644 --- a/homeassistant/components/airthings_ble/sensor.py +++ b/homeassistant/components/airthings_ble/sensor.py @@ -5,25 +5,35 @@ import logging from airthings_ble import AirthingsDevice -from homeassistant import config_entries from homeassistant.components.sensor import ( SensorDeviceClass, SensorEntity, SensorEntityDescription, SensorStateClass, ) +from homeassistant.config_entries import ConfigEntry from homeassistant.const import ( CONCENTRATION_PARTS_PER_BILLION, CONCENTRATION_PARTS_PER_MILLION, LIGHT_LUX, PERCENTAGE, EntityCategory, + Platform, UnitOfPressure, UnitOfTemperature, ) -from homeassistant.core import HomeAssistant -from homeassistant.helpers.device_registry import CONNECTION_BLUETOOTH, DeviceInfo +from homeassistant.core import HomeAssistant, callback +from homeassistant.helpers.device_registry import ( + CONNECTION_BLUETOOTH, + DeviceInfo, + async_get as device_async_get, +) from homeassistant.helpers.entity_platform import AddEntitiesCallback +from homeassistant.helpers.entity_registry import ( + RegistryEntry, + async_entries_for_device, + async_get as entity_async_get, +) from homeassistant.helpers.typing import StateType from homeassistant.helpers.update_coordinator import ( CoordinatorEntity, @@ -107,9 +117,43 @@ SENSORS_MAPPING_TEMPLATE: dict[str, SensorEntityDescription] = { } +@callback +def async_migrate(hass: HomeAssistant, address: str, sensor_name: str) -> None: + """Migrate entities to new unique ids (with BLE Address).""" + ent_reg = entity_async_get(hass) + unique_id_trailer = f"_{sensor_name}" + new_unique_id = f"{address}{unique_id_trailer}" + if ent_reg.async_get_entity_id(DOMAIN, Platform.SENSOR, new_unique_id): + # New unique id already exists + return + dev_reg = device_async_get(hass) + if not ( + device := dev_reg.async_get_device( + connections={(CONNECTION_BLUETOOTH, address)} + ) + ): + return + entities = async_entries_for_device( + ent_reg, + device_id=device.id, + include_disabled_entities=True, + ) + matching_reg_entry: RegistryEntry | None = None + for entry in entities: + if entry.unique_id.endswith(unique_id_trailer) and ( + not matching_reg_entry or "(" not in entry.unique_id + ): + matching_reg_entry = entry + if not matching_reg_entry: + return + entity_id = matching_reg_entry.entity_id + ent_reg.async_update_entity(entity_id=entity_id, new_unique_id=new_unique_id) + _LOGGER.debug("Migrated entity '%s' to unique id '%s'", entity_id, new_unique_id) + + async def async_setup_entry( hass: HomeAssistant, - entry: config_entries.ConfigEntry, + entry: ConfigEntry, async_add_entities: AddEntitiesCallback, ) -> None: """Set up the Airthings BLE sensors.""" @@ -137,6 +181,7 @@ async def async_setup_entry( sensor_value, ) continue + async_migrate(hass, coordinator.data.address, sensor_type) entities.append( AirthingsSensor(coordinator, coordinator.data, sensors_mapping[sensor_type]) ) @@ -165,7 +210,7 @@ class AirthingsSensor( if identifier := airthings_device.identifier: name += f" ({identifier})" - self._attr_unique_id = f"{name}_{entity_description.key}" + self._attr_unique_id = f"{airthings_device.address}_{entity_description.key}" self._attr_device_info = DeviceInfo( connections={ ( diff --git a/tests/components/airthings_ble/__init__.py b/tests/components/airthings_ble/__init__.py index 0dd78718a30..da0c312bf28 100644 --- a/tests/components/airthings_ble/__init__.py +++ b/tests/components/airthings_ble/__init__.py @@ -5,8 +5,11 @@ from unittest.mock import patch from airthings_ble import AirthingsBluetoothDeviceData, AirthingsDevice +from homeassistant.components.airthings_ble.const import DOMAIN from homeassistant.components.bluetooth.models import BluetoothServiceInfoBleak +from homeassistant.helpers.device_registry import CONNECTION_BLUETOOTH +from tests.common import MockConfigEntry, MockEntity from tests.components.bluetooth import generate_advertisement_data, generate_ble_device @@ -36,18 +39,52 @@ def patch_airthings_ble(return_value=AirthingsDevice, side_effect=None): ) +def patch_airthings_device_update(): + """Patch airthings-ble device.""" + return patch( + "homeassistant.components.airthings_ble.AirthingsBluetoothDeviceData.update_device", + return_value=WAVE_DEVICE_INFO, + ) + + WAVE_SERVICE_INFO = BluetoothServiceInfoBleak( name="cc-cc-cc-cc-cc-cc", address="cc:cc:cc:cc:cc:cc", + device=generate_ble_device( + address="cc:cc:cc:cc:cc:cc", + name="Airthings Wave+", + ), rssi=-61, manufacturer_data={820: b"\xe4/\xa5\xae\t\x00"}, - service_data={}, - service_uuids=["b42e1c08-ade7-11e4-89d3-123b93f75cba"], + service_data={ + # Sensor data + "b42e2a68-ade7-11e4-89d3-123b93f75cba": bytearray( + b"\x01\x02\x03\x04\x00\x05\x00\x06\x00\x07\x00\x08\x00\x09\x00\x0A" + ), + # Manufacturer + "00002a29-0000-1000-8000-00805f9b34fb": bytearray(b"Airthings AS"), + # Model + "00002a24-0000-1000-8000-00805f9b34fb": bytearray(b"2930"), + # Identifier + "00002a25-0000-1000-8000-00805f9b34fb": bytearray(b"123456"), + # SW Version + "00002a26-0000-1000-8000-00805f9b34fb": bytearray(b"G-BLE-1.5.3-master+0"), + # HW Version + "00002a27-0000-1000-8000-00805f9b34fb": bytearray(b"REV A"), + # Command + "b42e2d06-ade7-11e4-89d3-123b93f75cba": bytearray(b"\x00"), + }, + service_uuids=[ + "b42e1c08-ade7-11e4-89d3-123b93f75cba", + "b42e2a68-ade7-11e4-89d3-123b93f75cba", + "00002a29-0000-1000-8000-00805f9b34fb", + "00002a24-0000-1000-8000-00805f9b34fb", + "00002a25-0000-1000-8000-00805f9b34fb", + "00002a26-0000-1000-8000-00805f9b34fb", + "00002a27-0000-1000-8000-00805f9b34fb", + "b42e2d06-ade7-11e4-89d3-123b93f75cba", + ], source="local", - device=generate_ble_device( - "cc:cc:cc:cc:cc:cc", - "cc-cc-cc-cc-cc-cc", - ), advertisement=generate_advertisement_data( manufacturer_data={820: b"\xe4/\xa5\xae\t\x00"}, service_uuids=["b42e1c08-ade7-11e4-89d3-123b93f75cba"], @@ -99,3 +136,62 @@ WAVE_DEVICE_INFO = AirthingsDevice( }, address="cc:cc:cc:cc:cc:cc", ) + +TEMPERATURE_V1 = MockEntity( + unique_id="Airthings Wave Plus 123456_temperature", + name="Airthings Wave Plus 123456 Temperature", +) + +HUMIDITY_V2 = MockEntity( + unique_id="Airthings Wave Plus (123456)_humidity", + name="Airthings Wave Plus (123456) Humidity", +) + +CO2_V1 = MockEntity( + unique_id="Airthings Wave Plus 123456_co2", + name="Airthings Wave Plus 123456 CO2", +) + +CO2_V2 = MockEntity( + unique_id="Airthings Wave Plus (123456)_co2", + name="Airthings Wave Plus (123456) CO2", +) + +VOC_V1 = MockEntity( + unique_id="Airthings Wave Plus 123456_voc", + name="Airthings Wave Plus 123456 CO2", +) + +VOC_V2 = MockEntity( + unique_id="Airthings Wave Plus (123456)_voc", + name="Airthings Wave Plus (123456) VOC", +) + +VOC_V3 = MockEntity( + unique_id="cc:cc:cc:cc:cc:cc_voc", + name="Airthings Wave Plus (123456) VOC", +) + + +def create_entry(hass): + """Create a config entry.""" + entry = MockConfigEntry( + domain=DOMAIN, + unique_id=WAVE_SERVICE_INFO.address, + title="Airthings Wave Plus (123456)", + ) + entry.add_to_hass(hass) + return entry + + +def create_device(hass, entry): + """Create a device for the given entry.""" + device_registry = hass.helpers.device_registry.async_get(hass) + device = device_registry.async_get_or_create( + config_entry_id=entry.entry_id, + connections={(CONNECTION_BLUETOOTH, WAVE_SERVICE_INFO.address)}, + manufacturer="Airthings AS", + name="Airthings Wave Plus (123456)", + model="Wave Plus", + ) + return device diff --git a/tests/components/airthings_ble/test_sensor.py b/tests/components/airthings_ble/test_sensor.py new file mode 100644 index 00000000000..68efd4d25f6 --- /dev/null +++ b/tests/components/airthings_ble/test_sensor.py @@ -0,0 +1,213 @@ +"""Test the Airthings Wave sensor.""" +import logging + +from homeassistant.components.airthings_ble.const import DOMAIN +from homeassistant.core import HomeAssistant + +from tests.components.airthings_ble import ( + CO2_V1, + CO2_V2, + HUMIDITY_V2, + TEMPERATURE_V1, + VOC_V1, + VOC_V2, + VOC_V3, + WAVE_DEVICE_INFO, + WAVE_SERVICE_INFO, + create_device, + create_entry, + patch_airthings_device_update, +) +from tests.components.bluetooth import inject_bluetooth_service_info + +_LOGGER = logging.getLogger(__name__) + + +async def test_migration_from_v1_to_v3_unique_id(hass: HomeAssistant): + """Verify that we can migrate from v1 (pre 2023.9.0) to the latest unique id format.""" + entry = create_entry(hass) + device = create_device(hass, entry) + + assert entry is not None + assert device is not None + + entity_registry = hass.helpers.entity_registry.async_get(hass) + + sensor = entity_registry.async_get_or_create( + domain=DOMAIN, + platform="sensor", + unique_id=TEMPERATURE_V1.unique_id, + config_entry=entry, + device_id=device.id, + ) + + await hass.async_block_till_done() + assert len(hass.states.async_all()) == 0 + + inject_bluetooth_service_info( + hass, + WAVE_SERVICE_INFO, + ) + + await hass.async_block_till_done() + + with patch_airthings_device_update(): + await hass.config_entries.async_setup(entry.entry_id) + await hass.async_block_till_done() + + assert len(hass.states.async_all()) > 0 + + assert ( + entity_registry.async_get(sensor.entity_id).unique_id + == WAVE_DEVICE_INFO.address + "_temperature" + ) + + +async def test_migration_from_v2_to_v3_unique_id(hass: HomeAssistant): + """Verify that we can migrate from v2 (introduced in 2023.9.0) to the latest unique id format.""" + entry = create_entry(hass) + device = create_device(hass, entry) + + assert entry is not None + assert device is not None + + entity_registry = hass.helpers.entity_registry.async_get(hass) + + await hass.async_block_till_done() + + sensor = entity_registry.async_get_or_create( + domain=DOMAIN, + platform="sensor", + unique_id=HUMIDITY_V2.unique_id, + config_entry=entry, + device_id=device.id, + ) + + await hass.async_block_till_done() + assert len(hass.states.async_all()) == 0 + + inject_bluetooth_service_info( + hass, + WAVE_SERVICE_INFO, + ) + + await hass.async_block_till_done() + + with patch_airthings_device_update(): + await hass.config_entries.async_setup(entry.entry_id) + await hass.async_block_till_done() + + assert len(hass.states.async_all()) > 0 + + assert ( + entity_registry.async_get(sensor.entity_id).unique_id + == WAVE_DEVICE_INFO.address + "_humidity" + ) + + +async def test_migration_from_v1_and_v2_to_v3_unique_id(hass: HomeAssistant): + """Test if migration works when we have both v1 (pre 2023.9.0) and v2 (introduced in 2023.9.0) unique ids.""" + entry = create_entry(hass) + device = create_device(hass, entry) + + assert entry is not None + assert device is not None + + entity_registry = hass.helpers.entity_registry.async_get(hass) + + await hass.async_block_till_done() + + v2 = entity_registry.async_get_or_create( + domain=DOMAIN, + platform="sensor", + unique_id=CO2_V2.unique_id, + config_entry=entry, + device_id=device.id, + ) + + v1 = entity_registry.async_get_or_create( + domain=DOMAIN, + platform="sensor", + unique_id=CO2_V1.unique_id, + config_entry=entry, + device_id=device.id, + ) + + await hass.async_block_till_done() + assert len(hass.states.async_all()) == 0 + + inject_bluetooth_service_info( + hass, + WAVE_SERVICE_INFO, + ) + + await hass.async_block_till_done() + + with patch_airthings_device_update(): + await hass.config_entries.async_setup(entry.entry_id) + await hass.async_block_till_done() + + assert len(hass.states.async_all()) > 0 + + assert ( + entity_registry.async_get(v1.entity_id).unique_id + == WAVE_DEVICE_INFO.address + "_co2" + ) + assert entity_registry.async_get(v2.entity_id).unique_id == v2.unique_id + + +async def test_migration_with_all_unique_ids(hass: HomeAssistant): + """Test if migration works when we have all unique ids.""" + entry = create_entry(hass) + device = create_device(hass, entry) + + assert entry is not None + assert device is not None + + entity_registry = hass.helpers.entity_registry.async_get(hass) + + await hass.async_block_till_done() + + v1 = entity_registry.async_get_or_create( + domain=DOMAIN, + platform="sensor", + unique_id=VOC_V1.unique_id, + config_entry=entry, + device_id=device.id, + ) + + v2 = entity_registry.async_get_or_create( + domain=DOMAIN, + platform="sensor", + unique_id=VOC_V2.unique_id, + config_entry=entry, + device_id=device.id, + ) + + v3 = entity_registry.async_get_or_create( + domain=DOMAIN, + platform="sensor", + unique_id=VOC_V3.unique_id, + config_entry=entry, + device_id=device.id, + ) + + await hass.async_block_till_done() + assert len(hass.states.async_all()) == 0 + + inject_bluetooth_service_info( + hass, + WAVE_SERVICE_INFO, + ) + + await hass.async_block_till_done() + + with patch_airthings_device_update(): + await hass.config_entries.async_setup(entry.entry_id) + await hass.async_block_till_done() + + assert len(hass.states.async_all()) > 0 + + assert entity_registry.async_get(v1.entity_id).unique_id == v1.unique_id + assert entity_registry.async_get(v2.entity_id).unique_id == v2.unique_id + assert entity_registry.async_get(v3.entity_id).unique_id == v3.unique_id From e597a6b640104518af25e5849fd21f31becac301 Mon Sep 17 00:00:00 2001 From: Marc Mueller <30130371+cdce8p@users.noreply.github.com> Date: Sat, 9 Sep 2023 01:51:26 +0200 Subject: [PATCH 08/34] Update RestrictedPython to 6.2 (#99955) --- homeassistant/components/python_script/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/python_script/manifest.json b/homeassistant/components/python_script/manifest.json index ea153be11cf..80ed6164e74 100644 --- a/homeassistant/components/python_script/manifest.json +++ b/homeassistant/components/python_script/manifest.json @@ -5,5 +5,5 @@ "documentation": "https://www.home-assistant.io/integrations/python_script", "loggers": ["RestrictedPython"], "quality_scale": "internal", - "requirements": ["RestrictedPython==6.1"] + "requirements": ["RestrictedPython==6.2"] } diff --git a/requirements_all.txt b/requirements_all.txt index 9a704bd2b25..18728ce1802 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -121,7 +121,7 @@ PyXiaomiGateway==0.14.3 RachioPy==1.0.3 # homeassistant.components.python_script -RestrictedPython==6.1 +RestrictedPython==6.2 # homeassistant.components.remember_the_milk RtmAPI==0.7.2 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 7beceb953a8..09d17d43302 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -108,7 +108,7 @@ PyXiaomiGateway==0.14.3 RachioPy==1.0.3 # homeassistant.components.python_script -RestrictedPython==6.1 +RestrictedPython==6.2 # homeassistant.components.remember_the_milk RtmAPI==0.7.2 From 02831ad94f62e85954397ecbe75b28142857de8a Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Fri, 8 Sep 2023 19:39:30 -0500 Subject: [PATCH 09/34] Bump bleak to 0.21.1 (#99960) --- homeassistant/components/bluetooth/manifest.json | 2 +- homeassistant/package_constraints.txt | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/bluetooth/manifest.json b/homeassistant/components/bluetooth/manifest.json index a3c40f739aa..393326d2687 100644 --- a/homeassistant/components/bluetooth/manifest.json +++ b/homeassistant/components/bluetooth/manifest.json @@ -14,7 +14,7 @@ ], "quality_scale": "internal", "requirements": [ - "bleak==0.21.0", + "bleak==0.21.1", "bleak-retry-connector==3.1.3", "bluetooth-adapters==0.16.1", "bluetooth-auto-recovery==1.2.2", diff --git a/homeassistant/package_constraints.txt b/homeassistant/package_constraints.txt index d03dc72863d..423c7a1ca94 100644 --- a/homeassistant/package_constraints.txt +++ b/homeassistant/package_constraints.txt @@ -9,7 +9,7 @@ attrs==23.1.0 awesomeversion==22.9.0 bcrypt==4.0.1 bleak-retry-connector==3.1.3 -bleak==0.21.0 +bleak==0.21.1 bluetooth-adapters==0.16.1 bluetooth-auto-recovery==1.2.2 bluetooth-data-tools==1.11.0 diff --git a/requirements_all.txt b/requirements_all.txt index 18728ce1802..dee20a57f11 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -521,7 +521,7 @@ bizkaibus==0.1.1 bleak-retry-connector==3.1.3 # homeassistant.components.bluetooth -bleak==0.21.0 +bleak==0.21.1 # homeassistant.components.blebox blebox-uniapi==2.1.4 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 09d17d43302..27e3119c99f 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -439,7 +439,7 @@ bimmer-connected==0.14.0 bleak-retry-connector==3.1.3 # homeassistant.components.bluetooth -bleak==0.21.0 +bleak==0.21.1 # homeassistant.components.blebox blebox-uniapi==2.1.4 From 0f9d00e4aa4628dd811f420fabd7c1b9a9ef54a8 Mon Sep 17 00:00:00 2001 From: Luke Lashley Date: Sat, 9 Sep 2023 08:15:28 -0400 Subject: [PATCH 10/34] Bump python-roborock to 33.2 (#99962) bump to 33.2 --- homeassistant/components/roborock/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/roborock/manifest.json b/homeassistant/components/roborock/manifest.json index 01548a6334c..dfcac67d2b0 100644 --- a/homeassistant/components/roborock/manifest.json +++ b/homeassistant/components/roborock/manifest.json @@ -6,5 +6,5 @@ "documentation": "https://www.home-assistant.io/integrations/roborock", "iot_class": "local_polling", "loggers": ["roborock"], - "requirements": ["python-roborock==0.32.3"] + "requirements": ["python-roborock==0.33.2"] } diff --git a/requirements_all.txt b/requirements_all.txt index dee20a57f11..f64b36b18d6 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2159,7 +2159,7 @@ python-qbittorrent==0.4.3 python-ripple-api==0.0.3 # homeassistant.components.roborock -python-roborock==0.32.3 +python-roborock==0.33.2 # homeassistant.components.smarttub python-smarttub==0.0.33 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 27e3119c99f..d6a01686d1a 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1585,7 +1585,7 @@ python-picnic-api==1.1.0 python-qbittorrent==0.4.3 # homeassistant.components.roborock -python-roborock==0.32.3 +python-roborock==0.33.2 # homeassistant.components.smarttub python-smarttub==0.0.33 From e6c2833032fdc1d8029b563f5d8b6ec75178d886 Mon Sep 17 00:00:00 2001 From: Raman Gupta <7243222+raman325@users.noreply.github.com> Date: Mon, 11 Sep 2023 22:21:44 -0400 Subject: [PATCH 11/34] Handle disconnects in zwave_js repair flow (#99964) * Handle disconnects in zwave_js repair flow * Combine logic to reduce LoC * only check once --- homeassistant/components/zwave_js/repairs.py | 24 +++--- .../components/zwave_js/strings.json | 3 + tests/components/zwave_js/test_repairs.py | 74 ++++++++++++++++--- 3 files changed, 80 insertions(+), 21 deletions(-) diff --git a/homeassistant/components/zwave_js/repairs.py b/homeassistant/components/zwave_js/repairs.py index 89f51dddb88..83ee0523a3b 100644 --- a/homeassistant/components/zwave_js/repairs.py +++ b/homeassistant/components/zwave_js/repairs.py @@ -2,7 +2,6 @@ from __future__ import annotations import voluptuous as vol -from zwave_js_server.model.node import Node from homeassistant import data_entry_flow from homeassistant.components.repairs import ConfirmRepairFlow, RepairsFlow @@ -14,10 +13,10 @@ from .helpers import async_get_node_from_device_id class DeviceConfigFileChangedFlow(RepairsFlow): """Handler for an issue fixing flow.""" - def __init__(self, node: Node, device_name: str) -> None: + def __init__(self, data: dict[str, str]) -> None: """Initialize.""" - self.node = node - self.device_name = device_name + self.device_name: str = data["device_name"] + self.device_id: str = data["device_id"] async def async_step_init( self, user_input: dict[str, str] | None = None @@ -30,7 +29,14 @@ class DeviceConfigFileChangedFlow(RepairsFlow): ) -> data_entry_flow.FlowResult: """Handle the confirm step of a fix flow.""" if user_input is not None: - self.hass.async_create_task(self.node.async_refresh_info()) + try: + node = async_get_node_from_device_id(self.hass, self.device_id) + except ValueError: + return self.async_abort( + reason="cannot_connect", + description_placeholders={"device_name": self.device_name}, + ) + self.hass.async_create_task(node.async_refresh_info()) return self.async_create_entry(title="", data={}) return self.async_show_form( @@ -41,15 +47,11 @@ class DeviceConfigFileChangedFlow(RepairsFlow): async def async_create_fix_flow( - hass: HomeAssistant, - issue_id: str, - data: dict[str, str] | None, + hass: HomeAssistant, issue_id: str, data: dict[str, str] | None ) -> RepairsFlow: """Create flow.""" if issue_id.split(".")[0] == "device_config_file_changed": assert data - return DeviceConfigFileChangedFlow( - async_get_node_from_device_id(hass, data["device_id"]), data["device_name"] - ) + return DeviceConfigFileChangedFlow(data) return ConfirmRepairFlow() diff --git a/homeassistant/components/zwave_js/strings.json b/homeassistant/components/zwave_js/strings.json index 6435c6b7a54..6994ce15a0c 100644 --- a/homeassistant/components/zwave_js/strings.json +++ b/homeassistant/components/zwave_js/strings.json @@ -170,6 +170,9 @@ "title": "Z-Wave device configuration file changed: {device_name}", "description": "Z-Wave JS discovers a lot of device metadata by interviewing the device. However, some of the information has to be loaded from a configuration file. Some of this information is only evaluated once, during the device interview.\n\nWhen a device config file is updated, this information may be stale and and the device must be re-interviewed to pick up the changes.\n\n This is not a required operation and device functionality will be impacted during the re-interview process, but you may see improvements for your device once it is complete.\n\nIf you'd like to proceed, click on SUBMIT below. The re-interview will take place in the background." } + }, + "abort": { + "cannot_connect": "Cannot connect to {device_name}. Please try again later after confirming that your Z-Wave network is up and connected to Home Assistant." } } } diff --git a/tests/components/zwave_js/test_repairs.py b/tests/components/zwave_js/test_repairs.py index 07371a299ef..d18bcfa09aa 100644 --- a/tests/components/zwave_js/test_repairs.py +++ b/tests/components/zwave_js/test_repairs.py @@ -22,16 +22,10 @@ import homeassistant.helpers.issue_registry as ir from tests.typing import ClientSessionGenerator, WebSocketGenerator -async def test_device_config_file_changed( - hass: HomeAssistant, - hass_client: ClientSessionGenerator, - hass_ws_client: WebSocketGenerator, - client, - multisensor_6_state, - integration, -) -> None: - """Test the device_config_file_changed issue.""" - dev_reg = dr.async_get(hass) +async def _trigger_repair_issue( + hass: HomeAssistant, client, multisensor_6_state +) -> Node: + """Trigger repair issue.""" # Create a node node_state = deepcopy(multisensor_6_state) node = Node(client, node_state) @@ -53,6 +47,23 @@ async def test_device_config_file_changed( client.async_send_command_no_wait.reset_mock() + return node + + +async def test_device_config_file_changed( + hass: HomeAssistant, + hass_client: ClientSessionGenerator, + hass_ws_client: WebSocketGenerator, + client, + multisensor_6_state, + integration, +) -> None: + """Test the device_config_file_changed issue.""" + dev_reg = dr.async_get(hass) + node = await _trigger_repair_issue(hass, client, multisensor_6_state) + + client.async_send_command_no_wait.reset_mock() + device = dev_reg.async_get_device(identifiers={get_device_id(client.driver, node)}) assert device issue_id = f"device_config_file_changed.{device.id}" @@ -157,3 +168,46 @@ async def test_invalid_issue( msg = await ws_client.receive_json() assert msg["success"] assert len(msg["result"]["issues"]) == 0 + + +async def test_abort_confirm( + hass: HomeAssistant, + hass_client: ClientSessionGenerator, + hass_ws_client: WebSocketGenerator, + client, + multisensor_6_state, + integration, +) -> None: + """Test aborting device_config_file_changed issue in confirm step.""" + dev_reg = dr.async_get(hass) + node = await _trigger_repair_issue(hass, client, multisensor_6_state) + + device = dev_reg.async_get_device(identifiers={get_device_id(client.driver, node)}) + assert device + issue_id = f"device_config_file_changed.{device.id}" + + await async_process_repairs_platforms(hass) + await hass_ws_client(hass) + http_client = await hass_client() + + url = RepairsFlowIndexView.url + resp = await http_client.post(url, json={"handler": DOMAIN, "issue_id": issue_id}) + assert resp.status == HTTPStatus.OK + data = await resp.json() + + flow_id = data["flow_id"] + assert data["step_id"] == "confirm" + + # Unload config entry so we can't connect to the node + await hass.config_entries.async_unload(integration.entry_id) + + # Apply fix + url = RepairsFlowResourceView.url.format(flow_id=flow_id) + resp = await http_client.post(url) + + assert resp.status == HTTPStatus.OK + data = await resp.json() + + assert data["type"] == "abort" + assert data["reason"] == "cannot_connect" + assert data["description_placeholders"] == {"device_name": device.name} From 7cdb4ec8523a59ba25e6ad89bfbc07d20cb2fd00 Mon Sep 17 00:00:00 2001 From: Bouwe Westerdijk <11290930+bouwew@users.noreply.github.com> Date: Sat, 9 Sep 2023 23:50:26 +0200 Subject: [PATCH 12/34] Bump plugwise to v0.32.2 (#99973) * Bump plugwise to v0.32.2 * Adapt number.py to the backend updates * Update related test-cases * Update plugwise test-fixtures * Update test_diagnostics.py --- .../components/plugwise/manifest.json | 2 +- homeassistant/components/plugwise/number.py | 13 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- .../plugwise/fixtures/adam_jip/all_data.json | 48 ++ .../all_data.json | 54 ++ .../anna_heatpump_heating/all_data.json | 6 + .../fixtures/m_adam_cooling/all_data.json | 12 + .../fixtures/m_adam_heating/all_data.json | 12 + .../m_anna_heatpump_cooling/all_data.json | 6 + .../m_anna_heatpump_idle/all_data.json | 6 + .../fixtures/p1v4_442_triple/all_data.json | 8 +- .../p1v4_442_triple/notifications.json | 6 +- .../fixtures/stretch_v31/all_data.json | 9 - tests/components/plugwise/test_diagnostics.py | 594 ++++++++++-------- tests/components/plugwise/test_number.py | 4 +- 16 files changed, 493 insertions(+), 291 deletions(-) diff --git a/homeassistant/components/plugwise/manifest.json b/homeassistant/components/plugwise/manifest.json index ef0f01b38f7..e87e1f0c281 100644 --- a/homeassistant/components/plugwise/manifest.json +++ b/homeassistant/components/plugwise/manifest.json @@ -7,6 +7,6 @@ "integration_type": "hub", "iot_class": "local_polling", "loggers": ["crcmod", "plugwise"], - "requirements": ["plugwise==0.31.9"], + "requirements": ["plugwise==0.32.2"], "zeroconf": ["_plugwise._tcp.local."] } diff --git a/homeassistant/components/plugwise/number.py b/homeassistant/components/plugwise/number.py index 5979480d90f..6fd3f7f92da 100644 --- a/homeassistant/components/plugwise/number.py +++ b/homeassistant/components/plugwise/number.py @@ -27,7 +27,7 @@ from .entity import PlugwiseEntity class PlugwiseEntityDescriptionMixin: """Mixin values for Plugwise entities.""" - command: Callable[[Smile, str, float], Awaitable[None]] + command: Callable[[Smile, str, str, float], Awaitable[None]] @dataclass @@ -43,7 +43,9 @@ NUMBER_TYPES = ( PlugwiseNumberEntityDescription( key="maximum_boiler_temperature", translation_key="maximum_boiler_temperature", - command=lambda api, number, value: api.set_number_setpoint(number, value), + command=lambda api, number, dev_id, value: api.set_number_setpoint( + number, dev_id, value + ), device_class=NumberDeviceClass.TEMPERATURE, entity_category=EntityCategory.CONFIG, native_unit_of_measurement=UnitOfTemperature.CELSIUS, @@ -51,7 +53,9 @@ NUMBER_TYPES = ( PlugwiseNumberEntityDescription( key="max_dhw_temperature", translation_key="max_dhw_temperature", - command=lambda api, number, value: api.set_number_setpoint(number, value), + command=lambda api, number, dev_id, value: api.set_number_setpoint( + number, dev_id, value + ), device_class=NumberDeviceClass.TEMPERATURE, entity_category=EntityCategory.CONFIG, native_unit_of_measurement=UnitOfTemperature.CELSIUS, @@ -94,6 +98,7 @@ class PlugwiseNumberEntity(PlugwiseEntity, NumberEntity): ) -> None: """Initiate Plugwise Number.""" super().__init__(coordinator, device_id) + self.device_id = device_id self.entity_description = description self._attr_unique_id = f"{device_id}-{description.key}" self._attr_mode = NumberMode.BOX @@ -109,6 +114,6 @@ class PlugwiseNumberEntity(PlugwiseEntity, NumberEntity): async def async_set_native_value(self, value: float) -> None: """Change to the new setpoint value.""" await self.entity_description.command( - self.coordinator.api, self.entity_description.key, value + self.coordinator.api, self.entity_description.key, self.device_id, value ) await self.coordinator.async_request_refresh() diff --git a/requirements_all.txt b/requirements_all.txt index f64b36b18d6..8c3336b5899 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1438,7 +1438,7 @@ plexauth==0.0.6 plexwebsocket==0.0.13 # homeassistant.components.plugwise -plugwise==0.31.9 +plugwise==0.32.2 # homeassistant.components.plum_lightpad plumlightpad==0.0.11 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index d6a01686d1a..77214c21835 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1086,7 +1086,7 @@ plexauth==0.0.6 plexwebsocket==0.0.13 # homeassistant.components.plugwise -plugwise==0.31.9 +plugwise==0.32.2 # homeassistant.components.plum_lightpad plumlightpad==0.0.11 diff --git a/tests/components/plugwise/fixtures/adam_jip/all_data.json b/tests/components/plugwise/fixtures/adam_jip/all_data.json index 177478f0fff..4dda9af3b54 100644 --- a/tests/components/plugwise/fixtures/adam_jip/all_data.json +++ b/tests/components/plugwise/fixtures/adam_jip/all_data.json @@ -20,6 +20,12 @@ "setpoint": 13.0, "temperature": 24.2 }, + "temperature_offset": { + "lower_bound": -2.0, + "resolution": 0.1, + "setpoint": 0.0, + "upper_bound": 2.0 + }, "thermostat": { "lower_bound": 0.0, "resolution": 0.01, @@ -43,6 +49,12 @@ "temperature_difference": 2.0, "valve_position": 0.0 }, + "temperature_offset": { + "lower_bound": -2.0, + "resolution": 0.1, + "setpoint": 0.1, + "upper_bound": 2.0 + }, "vendor": "Plugwise", "zigbee_mac_address": "ABCD012345670A07" }, @@ -60,6 +72,12 @@ "temperature_difference": 1.7, "valve_position": 0.0 }, + "temperature_offset": { + "lower_bound": -2.0, + "resolution": 0.1, + "setpoint": 0.1, + "upper_bound": 2.0 + }, "vendor": "Plugwise", "zigbee_mac_address": "ABCD012345670A05" }, @@ -99,6 +117,12 @@ "setpoint": 13.0, "temperature": 30.0 }, + "temperature_offset": { + "lower_bound": -2.0, + "resolution": 0.1, + "setpoint": 0.0, + "upper_bound": 2.0 + }, "thermostat": { "lower_bound": 0.0, "resolution": 0.01, @@ -122,6 +146,12 @@ "temperature_difference": 1.8, "valve_position": 100 }, + "temperature_offset": { + "lower_bound": -2.0, + "resolution": 0.1, + "setpoint": 0.1, + "upper_bound": 2.0 + }, "vendor": "Plugwise", "zigbee_mac_address": "ABCD012345670A09" }, @@ -145,6 +175,12 @@ "setpoint": 13.0, "temperature": 30.0 }, + "temperature_offset": { + "lower_bound": -2.0, + "resolution": 0.1, + "setpoint": 0.0, + "upper_bound": 2.0 + }, "thermostat": { "lower_bound": 0.0, "resolution": 0.01, @@ -187,6 +223,12 @@ "temperature_difference": 1.9, "valve_position": 0.0 }, + "temperature_offset": { + "lower_bound": -2.0, + "resolution": 0.1, + "setpoint": 0.1, + "upper_bound": 2.0 + }, "vendor": "Plugwise", "zigbee_mac_address": "ABCD012345670A04" }, @@ -246,6 +288,12 @@ "setpoint": 9.0, "temperature": 27.4 }, + "temperature_offset": { + "lower_bound": -2.0, + "resolution": 0.1, + "setpoint": 0.0, + "upper_bound": 2.0 + }, "thermostat": { "lower_bound": 4.0, "resolution": 0.01, diff --git a/tests/components/plugwise/fixtures/adam_multiple_devices_per_zone/all_data.json b/tests/components/plugwise/fixtures/adam_multiple_devices_per_zone/all_data.json index 63f0012ea92..0cc28731ff4 100644 --- a/tests/components/plugwise/fixtures/adam_multiple_devices_per_zone/all_data.json +++ b/tests/components/plugwise/fixtures/adam_multiple_devices_per_zone/all_data.json @@ -95,6 +95,12 @@ "temperature_difference": -0.4, "valve_position": 0.0 }, + "temperature_offset": { + "lower_bound": -2.0, + "resolution": 0.1, + "setpoint": 0.0, + "upper_bound": 2.0 + }, "vendor": "Plugwise", "zigbee_mac_address": "ABCD012345670A17" }, @@ -123,6 +129,12 @@ "setpoint": 15.0, "temperature": 17.2 }, + "temperature_offset": { + "lower_bound": -2.0, + "resolution": 0.1, + "setpoint": 0.0, + "upper_bound": 2.0 + }, "thermostat": { "lower_bound": 0.0, "resolution": 0.01, @@ -200,6 +212,12 @@ "temperature_difference": -0.2, "valve_position": 0.0 }, + "temperature_offset": { + "lower_bound": -2.0, + "resolution": 0.1, + "setpoint": 0.0, + "upper_bound": 2.0 + }, "vendor": "Plugwise", "zigbee_mac_address": "ABCD012345670A09" }, @@ -217,6 +235,12 @@ "temperature_difference": 3.5, "valve_position": 100 }, + "temperature_offset": { + "lower_bound": -2.0, + "resolution": 0.1, + "setpoint": 0.0, + "upper_bound": 2.0 + }, "vendor": "Plugwise", "zigbee_mac_address": "ABCD012345670A02" }, @@ -245,6 +269,12 @@ "setpoint": 21.5, "temperature": 20.9 }, + "temperature_offset": { + "lower_bound": -2.0, + "resolution": 0.1, + "setpoint": 0.0, + "upper_bound": 2.0 + }, "thermostat": { "lower_bound": 0.0, "resolution": 0.01, @@ -289,6 +319,12 @@ "temperature_difference": 0.1, "valve_position": 0.0 }, + "temperature_offset": { + "lower_bound": -2.0, + "resolution": 0.1, + "setpoint": 0.0, + "upper_bound": 2.0 + }, "vendor": "Plugwise", "zigbee_mac_address": "ABCD012345670A10" }, @@ -317,6 +353,12 @@ "setpoint": 13.0, "temperature": 16.5 }, + "temperature_offset": { + "lower_bound": -2.0, + "resolution": 0.1, + "setpoint": 0.0, + "upper_bound": 2.0 + }, "thermostat": { "lower_bound": 0.0, "resolution": 0.01, @@ -353,6 +395,12 @@ "temperature_difference": 0.0, "valve_position": 0.0 }, + "temperature_offset": { + "lower_bound": -2.0, + "resolution": 0.1, + "setpoint": 0.0, + "upper_bound": 2.0 + }, "thermostat": { "lower_bound": 0.0, "resolution": 0.01, @@ -387,6 +435,12 @@ "setpoint": 14.0, "temperature": 18.9 }, + "temperature_offset": { + "lower_bound": -2.0, + "resolution": 0.1, + "setpoint": 0.0, + "upper_bound": 2.0 + }, "thermostat": { "lower_bound": 0.0, "resolution": 0.01, diff --git a/tests/components/plugwise/fixtures/anna_heatpump_heating/all_data.json b/tests/components/plugwise/fixtures/anna_heatpump_heating/all_data.json index 49b5221233f..cdddfdb3439 100644 --- a/tests/components/plugwise/fixtures/anna_heatpump_heating/all_data.json +++ b/tests/components/plugwise/fixtures/anna_heatpump_heating/all_data.json @@ -76,6 +76,12 @@ "setpoint": 20.5, "temperature": 19.3 }, + "temperature_offset": { + "lower_bound": -2.0, + "resolution": 0.1, + "setpoint": -0.5, + "upper_bound": 2.0 + }, "thermostat": { "lower_bound": 4.0, "resolution": 0.1, diff --git a/tests/components/plugwise/fixtures/m_adam_cooling/all_data.json b/tests/components/plugwise/fixtures/m_adam_cooling/all_data.json index 92618a90189..ac7e602821e 100644 --- a/tests/components/plugwise/fixtures/m_adam_cooling/all_data.json +++ b/tests/components/plugwise/fixtures/m_adam_cooling/all_data.json @@ -40,6 +40,12 @@ "temperature_difference": 2.3, "valve_position": 0.0 }, + "temperature_offset": { + "lower_bound": -2.0, + "resolution": 0.1, + "setpoint": 0.1, + "upper_bound": 2.0 + }, "vendor": "Plugwise", "zigbee_mac_address": "ABCD012345670A01" }, @@ -118,6 +124,12 @@ "setpoint_low": 20.0, "temperature": 239 }, + "temperature_offset": { + "lower_bound": -2.0, + "resolution": 0.1, + "setpoint": 0.0, + "upper_bound": 2.0 + }, "thermostat": { "lower_bound": 0.0, "resolution": 0.01, diff --git a/tests/components/plugwise/fixtures/m_adam_heating/all_data.json b/tests/components/plugwise/fixtures/m_adam_heating/all_data.json index 4345cf76a3a..a4923b1c549 100644 --- a/tests/components/plugwise/fixtures/m_adam_heating/all_data.json +++ b/tests/components/plugwise/fixtures/m_adam_heating/all_data.json @@ -45,6 +45,12 @@ "temperature_difference": 2.3, "valve_position": 0.0 }, + "temperature_offset": { + "lower_bound": -2.0, + "resolution": 0.1, + "setpoint": 0.1, + "upper_bound": 2.0 + }, "vendor": "Plugwise", "zigbee_mac_address": "ABCD012345670A01" }, @@ -114,6 +120,12 @@ "setpoint": 15.0, "temperature": 17.9 }, + "temperature_offset": { + "lower_bound": -2.0, + "resolution": 0.1, + "setpoint": 0.0, + "upper_bound": 2.0 + }, "thermostat": { "lower_bound": 0.0, "resolution": 0.01, diff --git a/tests/components/plugwise/fixtures/m_anna_heatpump_cooling/all_data.json b/tests/components/plugwise/fixtures/m_anna_heatpump_cooling/all_data.json index 20f2db213bd..f98f253e938 100644 --- a/tests/components/plugwise/fixtures/m_anna_heatpump_cooling/all_data.json +++ b/tests/components/plugwise/fixtures/m_anna_heatpump_cooling/all_data.json @@ -78,6 +78,12 @@ "setpoint_low": 20.5, "temperature": 26.3 }, + "temperature_offset": { + "lower_bound": -2.0, + "resolution": 0.1, + "setpoint": -0.5, + "upper_bound": 2.0 + }, "thermostat": { "lower_bound": 4.0, "resolution": 0.1, diff --git a/tests/components/plugwise/fixtures/m_anna_heatpump_idle/all_data.json b/tests/components/plugwise/fixtures/m_anna_heatpump_idle/all_data.json index 3a7bd2dae89..56d26f67acb 100644 --- a/tests/components/plugwise/fixtures/m_anna_heatpump_idle/all_data.json +++ b/tests/components/plugwise/fixtures/m_anna_heatpump_idle/all_data.json @@ -78,6 +78,12 @@ "setpoint_low": 20.5, "temperature": 23.0 }, + "temperature_offset": { + "lower_bound": -2.0, + "resolution": 0.1, + "setpoint": -0.5, + "upper_bound": 2.0 + }, "thermostat": { "lower_bound": 4.0, "resolution": 0.1, diff --git a/tests/components/plugwise/fixtures/p1v4_442_triple/all_data.json b/tests/components/plugwise/fixtures/p1v4_442_triple/all_data.json index e9a3b4c68b9..d503bd3a59d 100644 --- a/tests/components/plugwise/fixtures/p1v4_442_triple/all_data.json +++ b/tests/components/plugwise/fixtures/p1v4_442_triple/all_data.json @@ -2,7 +2,7 @@ "devices": { "03e65b16e4b247a29ae0d75a78cb492e": { "binary_sensors": { - "plugwise_notification": false + "plugwise_notification": true }, "dev_class": "gateway", "firmware": "4.4.2", @@ -51,7 +51,11 @@ }, "gateway": { "gateway_id": "03e65b16e4b247a29ae0d75a78cb492e", - "notifications": {}, + "notifications": { + "97a04c0c263049b29350a660b4cdd01e": { + "warning": "The Smile P1 is not connected to a smart meter." + } + }, "smile_name": "Smile P1" } } diff --git a/tests/components/plugwise/fixtures/p1v4_442_triple/notifications.json b/tests/components/plugwise/fixtures/p1v4_442_triple/notifications.json index 0967ef424bc..49db062035a 100644 --- a/tests/components/plugwise/fixtures/p1v4_442_triple/notifications.json +++ b/tests/components/plugwise/fixtures/p1v4_442_triple/notifications.json @@ -1 +1,5 @@ -{} +{ + "97a04c0c263049b29350a660b4cdd01e": { + "warning": "The Smile P1 is not connected to a smart meter." + } +} diff --git a/tests/components/plugwise/fixtures/stretch_v31/all_data.json b/tests/components/plugwise/fixtures/stretch_v31/all_data.json index c336a9cb9c2..8604aaae10e 100644 --- a/tests/components/plugwise/fixtures/stretch_v31/all_data.json +++ b/tests/components/plugwise/fixtures/stretch_v31/all_data.json @@ -48,15 +48,6 @@ "vendor": "Plugwise", "zigbee_mac_address": "ABCD012345670A07" }, - "71e1944f2a944b26ad73323e399efef0": { - "dev_class": "switching", - "members": ["5ca521ac179d468e91d772eeeb8a2117"], - "model": "Switchgroup", - "name": "Test", - "switches": { - "relay": true - } - }, "aac7b735042c4832ac9ff33aae4f453b": { "dev_class": "dishwasher", "firmware": "2011-06-27T10:52:18+02:00", diff --git a/tests/components/plugwise/test_diagnostics.py b/tests/components/plugwise/test_diagnostics.py index 5dde8a0e09e..69f180692e2 100644 --- a/tests/components/plugwise/test_diagnostics.py +++ b/tests/components/plugwise/test_diagnostics.py @@ -31,159 +31,141 @@ async def test_diagnostics( }, }, "devices": { - "df4a4a8169904cdb9c03d61a21f42140": { - "dev_class": "zone_thermostat", - "firmware": "2016-10-27T02:00:00+02:00", - "hardware": "255", - "location": "12493538af164a409c6a1c79e38afe1c", - "model": "Lisa", - "name": "Zone Lisa Bios", - "zigbee_mac_address": "ABCD012345670A06", - "vendor": "Plugwise", - "thermostat": { - "setpoint": 13.0, - "lower_bound": 0.0, - "upper_bound": 99.9, - "resolution": 0.01, - }, - "available": True, - "preset_modes": ["home", "asleep", "away", "vacation", "no_frost"], - "active_preset": "away", - "available_schedules": [ - "CV Roan", - "Bios Schema met Film Avond", - "GF7 Woonkamer", - "Badkamer Schema", - "CV Jessie", - ], - "select_schedule": "None", - "last_used": "Badkamer Schema", - "mode": "heat", - "sensors": {"temperature": 16.5, "setpoint": 13.0, "battery": 67}, - }, - "b310b72a0e354bfab43089919b9a88bf": { - "dev_class": "thermo_sensor", - "firmware": "2019-03-27T01:00:00+01:00", - "hardware": "1", - "location": "c50f167537524366a5af7aa3942feb1e", - "model": "Tom/Floor", - "name": "Floor kraan", - "zigbee_mac_address": "ABCD012345670A02", - "vendor": "Plugwise", + "02cf28bfec924855854c544690a609ef": { "available": True, + "dev_class": "vcr", + "firmware": "2019-06-21T02:00:00+02:00", + "location": "cd143c07248f491493cea0533bc3d669", + "model": "Plug", + "name": "NVR", "sensors": { - "temperature": 26.0, - "setpoint": 21.5, - "temperature_difference": 3.5, - "valve_position": 100, + "electricity_consumed": 34.0, + "electricity_consumed_interval": 9.15, + "electricity_produced": 0.0, + "electricity_produced_interval": 0.0, }, - }, - "a2c3583e0a6349358998b760cea82d2a": { - "dev_class": "thermo_sensor", - "firmware": "2019-03-27T01:00:00+01:00", - "hardware": "1", - "location": "12493538af164a409c6a1c79e38afe1c", - "model": "Tom/Floor", - "name": "Bios Cv Thermostatic Radiator ", - "zigbee_mac_address": "ABCD012345670A09", + "switches": {"lock": True, "relay": True}, "vendor": "Plugwise", - "available": True, - "sensors": { - "temperature": 17.2, - "setpoint": 13.0, - "battery": 62, - "temperature_difference": -0.2, - "valve_position": 0.0, - }, - }, - "b59bcebaf94b499ea7d46e4a66fb62d8": { - "dev_class": "zone_thermostat", - "firmware": "2016-08-02T02:00:00+02:00", - "hardware": "255", - "location": "c50f167537524366a5af7aa3942feb1e", - "model": "Lisa", - "name": "Zone Lisa WK", - "zigbee_mac_address": "ABCD012345670A07", - "vendor": "Plugwise", - "thermostat": { - "setpoint": 21.5, - "lower_bound": 0.0, - "upper_bound": 99.9, - "resolution": 0.01, - }, - "available": True, - "preset_modes": ["home", "asleep", "away", "vacation", "no_frost"], - "active_preset": "home", - "available_schedules": [ - "CV Roan", - "Bios Schema met Film Avond", - "GF7 Woonkamer", - "Badkamer Schema", - "CV Jessie", - ], - "select_schedule": "GF7 Woonkamer", - "last_used": "GF7 Woonkamer", - "mode": "auto", - "sensors": {"temperature": 20.9, "setpoint": 21.5, "battery": 34}, - }, - "fe799307f1624099878210aa0b9f1475": { - "dev_class": "gateway", - "firmware": "3.0.15", - "hardware": "AME Smile 2.0 board", - "location": "1f9dcf83fd4e4b66b72ff787957bfe5d", - "mac_address": "012345670001", - "model": "Gateway", - "name": "Adam", - "zigbee_mac_address": "ABCD012345670101", - "vendor": "Plugwise", - "select_regulation_mode": "heating", - "binary_sensors": {"plugwise_notification": True}, - "sensors": {"outdoor_temperature": 7.81}, - }, - "d3da73bde12a47d5a6b8f9dad971f2ec": { - "dev_class": "thermo_sensor", - "firmware": "2019-03-27T01:00:00+01:00", - "hardware": "1", - "location": "82fa13f017d240daa0d0ea1775420f24", - "model": "Tom/Floor", - "name": "Thermostatic Radiator Jessie", - "zigbee_mac_address": "ABCD012345670A10", - "vendor": "Plugwise", - "available": True, - "sensors": { - "temperature": 17.1, - "setpoint": 15.0, - "battery": 62, - "temperature_difference": 0.1, - "valve_position": 0.0, - }, + "zigbee_mac_address": "ABCD012345670A15", }, "21f2b542c49845e6bb416884c55778d6": { + "available": True, "dev_class": "game_console", "firmware": "2019-06-21T02:00:00+02:00", "location": "cd143c07248f491493cea0533bc3d669", "model": "Plug", "name": "Playstation Smart Plug", - "zigbee_mac_address": "ABCD012345670A12", - "vendor": "Plugwise", - "available": True, "sensors": { "electricity_consumed": 82.6, "electricity_consumed_interval": 8.6, "electricity_produced": 0.0, "electricity_produced_interval": 0.0, }, - "switches": {"relay": True, "lock": False}, + "switches": {"lock": False, "relay": True}, + "vendor": "Plugwise", + "zigbee_mac_address": "ABCD012345670A12", + }, + "4a810418d5394b3f82727340b91ba740": { + "available": True, + "dev_class": "router", + "firmware": "2019-06-21T02:00:00+02:00", + "location": "cd143c07248f491493cea0533bc3d669", + "model": "Plug", + "name": "USG Smart Plug", + "sensors": { + "electricity_consumed": 8.5, + "electricity_consumed_interval": 0.0, + "electricity_produced": 0.0, + "electricity_produced_interval": 0.0, + }, + "switches": {"lock": True, "relay": True}, + "vendor": "Plugwise", + "zigbee_mac_address": "ABCD012345670A16", + }, + "675416a629f343c495449970e2ca37b5": { + "available": True, + "dev_class": "router", + "firmware": "2019-06-21T02:00:00+02:00", + "location": "cd143c07248f491493cea0533bc3d669", + "model": "Plug", + "name": "Ziggo Modem", + "sensors": { + "electricity_consumed": 12.2, + "electricity_consumed_interval": 2.97, + "electricity_produced": 0.0, + "electricity_produced_interval": 0.0, + }, + "switches": {"lock": True, "relay": True}, + "vendor": "Plugwise", + "zigbee_mac_address": "ABCD012345670A01", + }, + "680423ff840043738f42cc7f1ff97a36": { + "available": True, + "dev_class": "thermo_sensor", + "firmware": "2019-03-27T01:00:00+01:00", + "hardware": "1", + "location": "08963fec7c53423ca5680aa4cb502c63", + "model": "Tom/Floor", + "name": "Thermostatic Radiator Badkamer", + "sensors": { + "battery": 51, + "setpoint": 14.0, + "temperature": 19.1, + "temperature_difference": -0.4, + "valve_position": 0.0, + }, + "temperature_offset": { + "lower_bound": -2.0, + "resolution": 0.1, + "setpoint": 0.0, + "upper_bound": 2.0, + }, + "vendor": "Plugwise", + "zigbee_mac_address": "ABCD012345670A17", + }, + "6a3bf693d05e48e0b460c815a4fdd09d": { + "active_preset": "asleep", + "available": True, + "available_schedules": [ + "CV Roan", + "Bios Schema met Film Avond", + "GF7 Woonkamer", + "Badkamer Schema", + "CV Jessie", + ], + "dev_class": "zone_thermostat", + "firmware": "2016-10-27T02:00:00+02:00", + "hardware": "255", + "last_used": "CV Jessie", + "location": "82fa13f017d240daa0d0ea1775420f24", + "mode": "auto", + "model": "Lisa", + "name": "Zone Thermostat Jessie", + "preset_modes": ["home", "asleep", "away", "vacation", "no_frost"], + "select_schedule": "CV Jessie", + "sensors": {"battery": 37, "setpoint": 15.0, "temperature": 17.2}, + "temperature_offset": { + "lower_bound": -2.0, + "resolution": 0.1, + "setpoint": 0.0, + "upper_bound": 2.0, + }, + "thermostat": { + "lower_bound": 0.0, + "resolution": 0.01, + "setpoint": 15.0, + "upper_bound": 99.9, + }, + "vendor": "Plugwise", + "zigbee_mac_address": "ABCD012345670A03", }, "78d1126fc4c743db81b61c20e88342a7": { + "available": True, "dev_class": "central_heating_pump", "firmware": "2019-06-21T02:00:00+02:00", "location": "c50f167537524366a5af7aa3942feb1e", "model": "Plug", "name": "CV Pomp", - "zigbee_mac_address": "ABCD012345670A05", - "vendor": "Plugwise", - "available": True, "sensors": { "electricity_consumed": 35.6, "electricity_consumed_interval": 7.37, @@ -191,153 +173,88 @@ async def test_diagnostics( "electricity_produced_interval": 0.0, }, "switches": {"relay": True}, + "vendor": "Plugwise", + "zigbee_mac_address": "ABCD012345670A05", }, "90986d591dcd426cae3ec3e8111ff730": { + "binary_sensors": {"heating_state": True}, "dev_class": "heater_central", "location": "1f9dcf83fd4e4b66b72ff787957bfe5d", "model": "Unknown", "name": "OnOff", - "binary_sensors": {"heating_state": True}, "sensors": { - "water_temperature": 70.0, "intended_boiler_temperature": 70.0, "modulation_level": 1, + "water_temperature": 70.0, }, }, - "cd0ddb54ef694e11ac18ed1cbce5dbbd": { - "dev_class": "vcr", - "firmware": "2019-06-21T02:00:00+02:00", - "location": "cd143c07248f491493cea0533bc3d669", - "model": "Plug", - "name": "NAS", - "zigbee_mac_address": "ABCD012345670A14", - "vendor": "Plugwise", - "available": True, - "sensors": { - "electricity_consumed": 16.5, - "electricity_consumed_interval": 0.5, - "electricity_produced": 0.0, - "electricity_produced_interval": 0.0, - }, - "switches": {"relay": True, "lock": True}, - }, - "4a810418d5394b3f82727340b91ba740": { - "dev_class": "router", - "firmware": "2019-06-21T02:00:00+02:00", - "location": "cd143c07248f491493cea0533bc3d669", - "model": "Plug", - "name": "USG Smart Plug", - "zigbee_mac_address": "ABCD012345670A16", - "vendor": "Plugwise", - "available": True, - "sensors": { - "electricity_consumed": 8.5, - "electricity_consumed_interval": 0.0, - "electricity_produced": 0.0, - "electricity_produced_interval": 0.0, - }, - "switches": {"relay": True, "lock": True}, - }, - "02cf28bfec924855854c544690a609ef": { - "dev_class": "vcr", - "firmware": "2019-06-21T02:00:00+02:00", - "location": "cd143c07248f491493cea0533bc3d669", - "model": "Plug", - "name": "NVR", - "zigbee_mac_address": "ABCD012345670A15", - "vendor": "Plugwise", - "available": True, - "sensors": { - "electricity_consumed": 34.0, - "electricity_consumed_interval": 9.15, - "electricity_produced": 0.0, - "electricity_produced_interval": 0.0, - }, - "switches": {"relay": True, "lock": True}, - }, "a28f588dc4a049a483fd03a30361ad3a": { + "available": True, "dev_class": "settop", "firmware": "2019-06-21T02:00:00+02:00", "location": "cd143c07248f491493cea0533bc3d669", "model": "Plug", "name": "Fibaro HC2", - "zigbee_mac_address": "ABCD012345670A13", - "vendor": "Plugwise", - "available": True, "sensors": { "electricity_consumed": 12.5, "electricity_consumed_interval": 3.8, "electricity_produced": 0.0, "electricity_produced_interval": 0.0, }, - "switches": {"relay": True, "lock": True}, - }, - "6a3bf693d05e48e0b460c815a4fdd09d": { - "dev_class": "zone_thermostat", - "firmware": "2016-10-27T02:00:00+02:00", - "hardware": "255", - "location": "82fa13f017d240daa0d0ea1775420f24", - "model": "Lisa", - "name": "Zone Thermostat Jessie", - "zigbee_mac_address": "ABCD012345670A03", + "switches": {"lock": True, "relay": True}, "vendor": "Plugwise", - "thermostat": { - "setpoint": 15.0, - "lower_bound": 0.0, - "upper_bound": 99.9, - "resolution": 0.01, - }, - "available": True, - "preset_modes": ["home", "asleep", "away", "vacation", "no_frost"], - "active_preset": "asleep", - "available_schedules": [ - "CV Roan", - "Bios Schema met Film Avond", - "GF7 Woonkamer", - "Badkamer Schema", - "CV Jessie", - ], - "select_schedule": "CV Jessie", - "last_used": "CV Jessie", - "mode": "auto", - "sensors": {"temperature": 17.2, "setpoint": 15.0, "battery": 37}, + "zigbee_mac_address": "ABCD012345670A13", }, - "680423ff840043738f42cc7f1ff97a36": { + "a2c3583e0a6349358998b760cea82d2a": { + "available": True, "dev_class": "thermo_sensor", "firmware": "2019-03-27T01:00:00+01:00", "hardware": "1", - "location": "08963fec7c53423ca5680aa4cb502c63", + "location": "12493538af164a409c6a1c79e38afe1c", "model": "Tom/Floor", - "name": "Thermostatic Radiator Badkamer", - "zigbee_mac_address": "ABCD012345670A17", - "vendor": "Plugwise", - "available": True, + "name": "Bios Cv Thermostatic Radiator ", "sensors": { - "temperature": 19.1, - "setpoint": 14.0, - "battery": 51, - "temperature_difference": -0.4, + "battery": 62, + "setpoint": 13.0, + "temperature": 17.2, + "temperature_difference": -0.2, "valve_position": 0.0, }, - }, - "f1fee6043d3642a9b0a65297455f008e": { - "dev_class": "zone_thermostat", - "firmware": "2016-10-27T02:00:00+02:00", - "hardware": "255", - "location": "08963fec7c53423ca5680aa4cb502c63", - "model": "Lisa", - "name": "Zone Thermostat Badkamer", - "zigbee_mac_address": "ABCD012345670A08", - "vendor": "Plugwise", - "thermostat": { - "setpoint": 14.0, - "lower_bound": 0.0, - "upper_bound": 99.9, - "resolution": 0.01, + "temperature_offset": { + "lower_bound": -2.0, + "resolution": 0.1, + "setpoint": 0.0, + "upper_bound": 2.0, }, + "vendor": "Plugwise", + "zigbee_mac_address": "ABCD012345670A09", + }, + "b310b72a0e354bfab43089919b9a88bf": { + "available": True, + "dev_class": "thermo_sensor", + "firmware": "2019-03-27T01:00:00+01:00", + "hardware": "1", + "location": "c50f167537524366a5af7aa3942feb1e", + "model": "Tom/Floor", + "name": "Floor kraan", + "sensors": { + "setpoint": 21.5, + "temperature": 26.0, + "temperature_difference": 3.5, + "valve_position": 100, + }, + "temperature_offset": { + "lower_bound": -2.0, + "resolution": 0.1, + "setpoint": 0.0, + "upper_bound": 2.0, + }, + "vendor": "Plugwise", + "zigbee_mac_address": "ABCD012345670A02", + }, + "b59bcebaf94b499ea7d46e4a66fb62d8": { + "active_preset": "home", "available": True, - "preset_modes": ["home", "asleep", "away", "vacation", "no_frost"], - "active_preset": "away", "available_schedules": [ "CV Roan", "Bios Schema met Film Avond", @@ -345,46 +262,76 @@ async def test_diagnostics( "Badkamer Schema", "CV Jessie", ], - "select_schedule": "Badkamer Schema", - "last_used": "Badkamer Schema", + "dev_class": "zone_thermostat", + "firmware": "2016-08-02T02:00:00+02:00", + "hardware": "255", + "last_used": "GF7 Woonkamer", + "location": "c50f167537524366a5af7aa3942feb1e", "mode": "auto", - "sensors": {"temperature": 18.9, "setpoint": 14.0, "battery": 92}, + "model": "Lisa", + "name": "Zone Lisa WK", + "preset_modes": ["home", "asleep", "away", "vacation", "no_frost"], + "select_schedule": "GF7 Woonkamer", + "sensors": {"battery": 34, "setpoint": 21.5, "temperature": 20.9}, + "temperature_offset": { + "lower_bound": -2.0, + "resolution": 0.1, + "setpoint": 0.0, + "upper_bound": 2.0, + }, + "thermostat": { + "lower_bound": 0.0, + "resolution": 0.01, + "setpoint": 21.5, + "upper_bound": 99.9, + }, + "vendor": "Plugwise", + "zigbee_mac_address": "ABCD012345670A07", }, - "675416a629f343c495449970e2ca37b5": { - "dev_class": "router", + "cd0ddb54ef694e11ac18ed1cbce5dbbd": { + "available": True, + "dev_class": "vcr", "firmware": "2019-06-21T02:00:00+02:00", "location": "cd143c07248f491493cea0533bc3d669", "model": "Plug", - "name": "Ziggo Modem", - "zigbee_mac_address": "ABCD012345670A01", - "vendor": "Plugwise", - "available": True, + "name": "NAS", "sensors": { - "electricity_consumed": 12.2, - "electricity_consumed_interval": 2.97, + "electricity_consumed": 16.5, + "electricity_consumed_interval": 0.5, "electricity_produced": 0.0, "electricity_produced_interval": 0.0, }, - "switches": {"relay": True, "lock": True}, + "switches": {"lock": True, "relay": True}, + "vendor": "Plugwise", + "zigbee_mac_address": "ABCD012345670A14", }, - "e7693eb9582644e5b865dba8d4447cf1": { - "dev_class": "thermostatic_radiator_valve", + "d3da73bde12a47d5a6b8f9dad971f2ec": { + "available": True, + "dev_class": "thermo_sensor", "firmware": "2019-03-27T01:00:00+01:00", "hardware": "1", - "location": "446ac08dd04d4eff8ac57489757b7314", + "location": "82fa13f017d240daa0d0ea1775420f24", "model": "Tom/Floor", - "name": "CV Kraan Garage", - "zigbee_mac_address": "ABCD012345670A11", - "vendor": "Plugwise", - "thermostat": { - "setpoint": 5.5, - "lower_bound": 0.0, - "upper_bound": 100.0, - "resolution": 0.01, + "name": "Thermostatic Radiator Jessie", + "sensors": { + "battery": 62, + "setpoint": 15.0, + "temperature": 17.1, + "temperature_difference": 0.1, + "valve_position": 0.0, }, + "temperature_offset": { + "lower_bound": -2.0, + "resolution": 0.1, + "setpoint": 0.0, + "upper_bound": 2.0, + }, + "vendor": "Plugwise", + "zigbee_mac_address": "ABCD012345670A10", + }, + "df4a4a8169904cdb9c03d61a21f42140": { + "active_preset": "away", "available": True, - "preset_modes": ["home", "asleep", "away", "vacation", "no_frost"], - "active_preset": "no_frost", "available_schedules": [ "CV Roan", "Bios Schema met Film Avond", @@ -392,16 +339,123 @@ async def test_diagnostics( "Badkamer Schema", "CV Jessie", ], - "select_schedule": "None", + "dev_class": "zone_thermostat", + "firmware": "2016-10-27T02:00:00+02:00", + "hardware": "255", "last_used": "Badkamer Schema", + "location": "12493538af164a409c6a1c79e38afe1c", "mode": "heat", + "model": "Lisa", + "name": "Zone Lisa Bios", + "preset_modes": ["home", "asleep", "away", "vacation", "no_frost"], + "select_schedule": "None", + "sensors": {"battery": 67, "setpoint": 13.0, "temperature": 16.5}, + "temperature_offset": { + "lower_bound": -2.0, + "resolution": 0.1, + "setpoint": 0.0, + "upper_bound": 2.0, + }, + "thermostat": { + "lower_bound": 0.0, + "resolution": 0.01, + "setpoint": 13.0, + "upper_bound": 99.9, + }, + "vendor": "Plugwise", + "zigbee_mac_address": "ABCD012345670A06", + }, + "e7693eb9582644e5b865dba8d4447cf1": { + "active_preset": "no_frost", + "available": True, + "available_schedules": [ + "CV Roan", + "Bios Schema met Film Avond", + "GF7 Woonkamer", + "Badkamer Schema", + "CV Jessie", + ], + "dev_class": "thermostatic_radiator_valve", + "firmware": "2019-03-27T01:00:00+01:00", + "hardware": "1", + "last_used": "Badkamer Schema", + "location": "446ac08dd04d4eff8ac57489757b7314", + "mode": "heat", + "model": "Tom/Floor", + "name": "CV Kraan Garage", + "preset_modes": ["home", "asleep", "away", "vacation", "no_frost"], + "select_schedule": "None", "sensors": { - "temperature": 15.6, - "setpoint": 5.5, "battery": 68, + "setpoint": 5.5, + "temperature": 15.6, "temperature_difference": 0.0, "valve_position": 0.0, }, + "temperature_offset": { + "lower_bound": -2.0, + "resolution": 0.1, + "setpoint": 0.0, + "upper_bound": 2.0, + }, + "thermostat": { + "lower_bound": 0.0, + "resolution": 0.01, + "setpoint": 5.5, + "upper_bound": 100.0, + }, + "vendor": "Plugwise", + "zigbee_mac_address": "ABCD012345670A11", + }, + "f1fee6043d3642a9b0a65297455f008e": { + "active_preset": "away", + "available": True, + "available_schedules": [ + "CV Roan", + "Bios Schema met Film Avond", + "GF7 Woonkamer", + "Badkamer Schema", + "CV Jessie", + ], + "dev_class": "zone_thermostat", + "firmware": "2016-10-27T02:00:00+02:00", + "hardware": "255", + "last_used": "Badkamer Schema", + "location": "08963fec7c53423ca5680aa4cb502c63", + "mode": "auto", + "model": "Lisa", + "name": "Zone Thermostat Badkamer", + "preset_modes": ["home", "asleep", "away", "vacation", "no_frost"], + "select_schedule": "Badkamer Schema", + "sensors": {"battery": 92, "setpoint": 14.0, "temperature": 18.9}, + "temperature_offset": { + "lower_bound": -2.0, + "resolution": 0.1, + "setpoint": 0.0, + "upper_bound": 2.0, + }, + "thermostat": { + "lower_bound": 0.0, + "resolution": 0.01, + "setpoint": 14.0, + "upper_bound": 99.9, + }, + "vendor": "Plugwise", + "zigbee_mac_address": "ABCD012345670A08", + }, + "fe799307f1624099878210aa0b9f1475": { + "binary_sensors": {"plugwise_notification": True}, + "dev_class": "gateway", + "firmware": "3.0.15", + "hardware": "AME Smile 2.0 board", + "location": "1f9dcf83fd4e4b66b72ff787957bfe5d", + "mac_address": "012345670001", + "model": "Gateway", + "name": "Adam", + "select_regulation_mode": "heating", + "sensors": {"outdoor_temperature": 7.81}, + "vendor": "Plugwise", + "zigbee_mac_address": "ABCD012345670101", }, }, } diff --git a/tests/components/plugwise/test_number.py b/tests/components/plugwise/test_number.py index 9ca64e104d3..bccf257a433 100644 --- a/tests/components/plugwise/test_number.py +++ b/tests/components/plugwise/test_number.py @@ -38,7 +38,7 @@ async def test_anna_max_boiler_temp_change( assert mock_smile_anna.set_number_setpoint.call_count == 1 mock_smile_anna.set_number_setpoint.assert_called_with( - "maximum_boiler_temperature", 65.0 + "maximum_boiler_temperature", "1cbf783bb11e4a7c8a6843dee3a86927", 65.0 ) @@ -67,5 +67,5 @@ async def test_adam_dhw_setpoint_change( assert mock_smile_adam_2.set_number_setpoint.call_count == 1 mock_smile_adam_2.set_number_setpoint.assert_called_with( - "max_dhw_temperature", 55.0 + "max_dhw_temperature", "056ee145a816487eaa69243c3280f8bf", 55.0 ) From f200ba7a864ec93995695d7e33f257289c8bdad5 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sat, 9 Sep 2023 12:23:15 -0500 Subject: [PATCH 13/34] Bump bluetooth-auto-recovery to 1.2.3 (#99979) fixes #99977 --- homeassistant/components/bluetooth/manifest.json | 2 +- homeassistant/package_constraints.txt | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/bluetooth/manifest.json b/homeassistant/components/bluetooth/manifest.json index 393326d2687..33ec71065db 100644 --- a/homeassistant/components/bluetooth/manifest.json +++ b/homeassistant/components/bluetooth/manifest.json @@ -17,7 +17,7 @@ "bleak==0.21.1", "bleak-retry-connector==3.1.3", "bluetooth-adapters==0.16.1", - "bluetooth-auto-recovery==1.2.2", + "bluetooth-auto-recovery==1.2.3", "bluetooth-data-tools==1.11.0", "dbus-fast==1.95.2" ] diff --git a/homeassistant/package_constraints.txt b/homeassistant/package_constraints.txt index 423c7a1ca94..7bd0c843e6a 100644 --- a/homeassistant/package_constraints.txt +++ b/homeassistant/package_constraints.txt @@ -11,7 +11,7 @@ bcrypt==4.0.1 bleak-retry-connector==3.1.3 bleak==0.21.1 bluetooth-adapters==0.16.1 -bluetooth-auto-recovery==1.2.2 +bluetooth-auto-recovery==1.2.3 bluetooth-data-tools==1.11.0 certifi>=2021.5.30 ciso8601==2.3.0 diff --git a/requirements_all.txt b/requirements_all.txt index 8c3336b5899..3086c5398a7 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -543,7 +543,7 @@ bluemaestro-ble==0.2.3 bluetooth-adapters==0.16.1 # homeassistant.components.bluetooth -bluetooth-auto-recovery==1.2.2 +bluetooth-auto-recovery==1.2.3 # homeassistant.components.bluetooth # homeassistant.components.esphome diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 77214c21835..5c770f5936a 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -454,7 +454,7 @@ bluemaestro-ble==0.2.3 bluetooth-adapters==0.16.1 # homeassistant.components.bluetooth -bluetooth-auto-recovery==1.2.2 +bluetooth-auto-recovery==1.2.3 # homeassistant.components.bluetooth # homeassistant.components.esphome From 3d09e859fcd39257367fab3b89bbac447a1aef00 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 11 Sep 2023 21:34:23 -0500 Subject: [PATCH 14/34] Avoid probing ipp printers for unique_id when it is available via mdns (#99982) * Avoid probing ipp printers for unique_id when it is available via mdns We would always probe the device in the ipp flow and than abort if it was already configured. We avoid the probe for most printers. * dry * coverage * fix test * add test for updating host --- homeassistant/components/ipp/config_flow.py | 36 ++++-- .../ipp/fixtures/printer_without_uuid.json | 35 ++++++ tests/components/ipp/test_config_flow.py | 103 +++++++++++++++++- 3 files changed, 160 insertions(+), 14 deletions(-) create mode 100644 tests/components/ipp/fixtures/printer_without_uuid.json diff --git a/homeassistant/components/ipp/config_flow.py b/homeassistant/components/ipp/config_flow.py index 8d1da6eca91..dfe6c0b2127 100644 --- a/homeassistant/components/ipp/config_flow.py +++ b/homeassistant/components/ipp/config_flow.py @@ -116,8 +116,7 @@ class IPPFlowHandler(ConfigFlow, domain=DOMAIN): name = discovery_info.name.replace(f".{zctype}", "") tls = zctype == "_ipps._tcp.local." base_path = discovery_info.properties.get("rp", "ipp/print") - - self.context.update({"title_placeholders": {"name": name}}) + unique_id = discovery_info.properties.get("UUID") self.discovery_info.update( { @@ -127,10 +126,18 @@ class IPPFlowHandler(ConfigFlow, domain=DOMAIN): CONF_VERIFY_SSL: False, CONF_BASE_PATH: f"/{base_path}", CONF_NAME: name, - CONF_UUID: discovery_info.properties.get("UUID"), + CONF_UUID: unique_id, } ) + if unique_id: + # If we already have the unique id, try to set it now + # so we can avoid probing the device if its already + # configured or ignored + await self._async_set_unique_id_and_abort_if_already_configured(unique_id) + + self.context.update({"title_placeholders": {"name": name}}) + try: info = await validate_input(self.hass, self.discovery_info) except IPPConnectionUpgradeRequired: @@ -147,7 +154,6 @@ class IPPFlowHandler(ConfigFlow, domain=DOMAIN): _LOGGER.debug("IPP Error", exc_info=True) return self.async_abort(reason="ipp_error") - unique_id = self.discovery_info[CONF_UUID] if not unique_id and info[CONF_UUID]: _LOGGER.debug( "Printer UUID is missing from discovery info. Falling back to IPP UUID" @@ -164,18 +170,24 @@ class IPPFlowHandler(ConfigFlow, domain=DOMAIN): "Unable to determine unique id from discovery info and IPP response" ) - if unique_id: - await self.async_set_unique_id(unique_id) - self._abort_if_unique_id_configured( - updates={ - CONF_HOST: self.discovery_info[CONF_HOST], - CONF_NAME: self.discovery_info[CONF_NAME], - }, - ) + if unique_id and self.unique_id != unique_id: + await self._async_set_unique_id_and_abort_if_already_configured(unique_id) await self._async_handle_discovery_without_unique_id() return await self.async_step_zeroconf_confirm() + async def _async_set_unique_id_and_abort_if_already_configured( + self, unique_id: str + ) -> None: + """Set the unique ID and abort if already configured.""" + await self.async_set_unique_id(unique_id) + self._abort_if_unique_id_configured( + updates={ + CONF_HOST: self.discovery_info[CONF_HOST], + CONF_NAME: self.discovery_info[CONF_NAME], + }, + ) + async def async_step_zeroconf_confirm( self, user_input: dict[str, Any] | None = None ) -> FlowResult: diff --git a/tests/components/ipp/fixtures/printer_without_uuid.json b/tests/components/ipp/fixtures/printer_without_uuid.json new file mode 100644 index 00000000000..21f1eb93a32 --- /dev/null +++ b/tests/components/ipp/fixtures/printer_without_uuid.json @@ -0,0 +1,35 @@ +{ + "printer-state": "idle", + "printer-name": "Test Printer", + "printer-location": null, + "printer-make-and-model": "Test HA-1000 Series", + "printer-device-id": "MFG:TEST;CMD:ESCPL2,BDC,D4,D4PX,ESCPR7,END4,GENEP,URF;MDL:HA-1000 Series;CLS:PRINTER;DES:TEST HA-1000 Series;CID:EpsonRGB;FID:FXN,DPA,WFA,ETN,AFN,DAN,WRA;RID:20;DDS:022500;ELG:1000;SN:555534593035345555;URF:CP1,PQ4-5,OB9,OFU0,RS360,SRGB24,W8,DM3,IS1-7-6,V1.4,MT1-3-7-8-10-11-12;", + "printer-uri-supported": [ + "ipps://192.168.1.31:631/ipp/print", + "ipp://192.168.1.31:631/ipp/print" + ], + "uri-authentication-supported": ["none", "none"], + "uri-security-supported": ["tls", "none"], + "printer-info": "Test HA-1000 Series", + "printer-up-time": 30, + "printer-firmware-string-version": "20.23.06HA", + "printer-more-info": "http://192.168.1.31:80/PRESENTATION/BONJOUR", + "marker-names": [ + "Black ink", + "Photo black ink", + "Cyan ink", + "Yellow ink", + "Magenta ink" + ], + "marker-types": [ + "ink-cartridge", + "ink-cartridge", + "ink-cartridge", + "ink-cartridge", + "ink-cartridge" + ], + "marker-colors": ["#000000", "#000000", "#00FFFF", "#FFFF00", "#FF00FF"], + "marker-levels": [58, 98, 91, 95, 73], + "marker-low-levels": [10, 10, 10, 10, 10], + "marker-high-levels": [100, 100, 100, 100, 100] +} diff --git a/tests/components/ipp/test_config_flow.py b/tests/components/ipp/test_config_flow.py index 69a2bb9287a..0daf8a0f7e0 100644 --- a/tests/components/ipp/test_config_flow.py +++ b/tests/components/ipp/test_config_flow.py @@ -1,6 +1,7 @@ """Tests for the IPP config flow.""" import dataclasses -from unittest.mock import MagicMock +import json +from unittest.mock import MagicMock, patch from pyipp import ( IPPConnectionError, @@ -8,6 +9,7 @@ from pyipp import ( IPPError, IPPParseError, IPPVersionNotSupportedError, + Printer, ) import pytest @@ -23,7 +25,7 @@ from . import ( MOCK_ZEROCONF_IPPS_SERVICE_INFO, ) -from tests.common import MockConfigEntry +from tests.common import MockConfigEntry, load_fixture pytestmark = pytest.mark.usefixtures("mock_setup_entry") @@ -316,6 +318,31 @@ async def test_zeroconf_with_uuid_device_exists_abort( assert result["reason"] == "already_configured" +async def test_zeroconf_with_uuid_device_exists_abort_new_host( + hass: HomeAssistant, + mock_config_entry: MockConfigEntry, + mock_ipp_config_flow: MagicMock, +) -> None: + """Test we abort zeroconf flow if printer already configured.""" + mock_config_entry.add_to_hass(hass) + + discovery_info = dataclasses.replace(MOCK_ZEROCONF_IPP_SERVICE_INFO, host="1.2.3.9") + discovery_info.properties = { + **MOCK_ZEROCONF_IPP_SERVICE_INFO.properties, + "UUID": "cfe92100-67c4-11d4-a45f-f8d027761251", + } + + result = await hass.config_entries.flow.async_init( + DOMAIN, + context={"source": SOURCE_ZEROCONF}, + data=discovery_info, + ) + + assert result["type"] == FlowResultType.ABORT + assert result["reason"] == "already_configured" + assert mock_config_entry.data[CONF_HOST] == "1.2.3.9" + + async def test_zeroconf_empty_unique_id( hass: HomeAssistant, mock_ipp_config_flow: MagicMock, @@ -337,6 +364,21 @@ async def test_zeroconf_empty_unique_id( assert result["type"] == FlowResultType.FORM + result = await hass.config_entries.flow.async_configure( + result["flow_id"], + user_input={CONF_HOST: "192.168.1.31", CONF_BASE_PATH: "/ipp/print"}, + ) + + assert result["type"] == FlowResultType.CREATE_ENTRY + assert result["title"] == "EPSON XP-6000 Series" + + assert result["data"] + assert result["data"][CONF_HOST] == "192.168.1.31" + assert result["data"][CONF_UUID] == "cfe92100-67c4-11d4-a45f-f8d027761251" + + assert result["result"] + assert result["result"].unique_id == "cfe92100-67c4-11d4-a45f-f8d027761251" + async def test_zeroconf_no_unique_id( hass: HomeAssistant, @@ -355,6 +397,21 @@ async def test_zeroconf_no_unique_id( assert result["type"] == FlowResultType.FORM + result = await hass.config_entries.flow.async_configure( + result["flow_id"], + user_input={CONF_HOST: "192.168.1.31", CONF_BASE_PATH: "/ipp/print"}, + ) + + assert result["type"] == FlowResultType.CREATE_ENTRY + assert result["title"] == "EPSON XP-6000 Series" + + assert result["data"] + assert result["data"][CONF_HOST] == "192.168.1.31" + assert result["data"][CONF_UUID] == "cfe92100-67c4-11d4-a45f-f8d027761251" + + assert result["result"] + assert result["result"].unique_id == "cfe92100-67c4-11d4-a45f-f8d027761251" + async def test_full_user_flow_implementation( hass: HomeAssistant, @@ -448,3 +505,45 @@ async def test_full_zeroconf_tls_flow_implementation( assert result["result"] assert result["result"].unique_id == "cfe92100-67c4-11d4-a45f-f8d027761251" + + +async def test_zeroconf_empty_unique_id_uses_serial(hass: HomeAssistant) -> None: + """Test zeroconf flow if printer lacks (empty) unique identification with serial fallback.""" + fixture = await hass.async_add_executor_job( + load_fixture, "ipp/printer_without_uuid.json" + ) + mock_printer_without_uuid = Printer.from_dict(json.loads(fixture)) + mock_printer_without_uuid.unique_id = None + + discovery_info = dataclasses.replace(MOCK_ZEROCONF_IPP_SERVICE_INFO) + discovery_info.properties = { + **MOCK_ZEROCONF_IPP_SERVICE_INFO.properties, + "UUID": "", + } + with patch( + "homeassistant.components.ipp.config_flow.IPP", autospec=True + ) as ipp_mock: + client = ipp_mock.return_value + client.printer.return_value = mock_printer_without_uuid + result = await hass.config_entries.flow.async_init( + DOMAIN, + context={"source": SOURCE_ZEROCONF}, + data=discovery_info, + ) + + assert result["type"] == FlowResultType.FORM + + result = await hass.config_entries.flow.async_configure( + result["flow_id"], + user_input={CONF_HOST: "192.168.1.31", CONF_BASE_PATH: "/ipp/print"}, + ) + + assert result["type"] == FlowResultType.CREATE_ENTRY + assert result["title"] == "EPSON XP-6000 Series" + + assert result["data"] + assert result["data"][CONF_HOST] == "192.168.1.31" + assert result["data"][CONF_UUID] == "" + + assert result["result"] + assert result["result"].unique_id == "555534593035345555" From 42f62485cfc762f3457874bbe7972078522fd21c Mon Sep 17 00:00:00 2001 From: jan iversen Date: Sat, 9 Sep 2023 17:45:19 +0200 Subject: [PATCH 15/34] Bump pymodbus to v3.5.2 (#99988) --- homeassistant/components/modbus/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/modbus/manifest.json b/homeassistant/components/modbus/manifest.json index bef85f1d20d..b70055e5fbe 100644 --- a/homeassistant/components/modbus/manifest.json +++ b/homeassistant/components/modbus/manifest.json @@ -6,5 +6,5 @@ "iot_class": "local_polling", "loggers": ["pymodbus"], "quality_scale": "gold", - "requirements": ["pymodbus==3.5.1"] + "requirements": ["pymodbus==3.5.2"] } diff --git a/requirements_all.txt b/requirements_all.txt index 3086c5398a7..ea0c146ae25 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1851,7 +1851,7 @@ pymitv==1.4.3 pymochad==0.2.0 # homeassistant.components.modbus -pymodbus==3.5.1 +pymodbus==3.5.2 # homeassistant.components.monoprice pymonoprice==0.4 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 5c770f5936a..7339d2a6ba4 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1370,7 +1370,7 @@ pymeteoclimatic==0.0.6 pymochad==0.2.0 # homeassistant.components.modbus -pymodbus==3.5.1 +pymodbus==3.5.2 # homeassistant.components.monoprice pymonoprice==0.4 From 82a5615d7ddadcae13b157ddd4f7cda10de92d63 Mon Sep 17 00:00:00 2001 From: Kevin Stillhammer Date: Sat, 9 Sep 2023 19:16:27 +0200 Subject: [PATCH 16/34] Bump pywaze to 0.4.0 (#99995) bump pywaze from 0.3.0 to 0.4.0 --- homeassistant/components/waze_travel_time/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/waze_travel_time/manifest.json b/homeassistant/components/waze_travel_time/manifest.json index 3f1f8c6d67b..c72d9b1dbad 100644 --- a/homeassistant/components/waze_travel_time/manifest.json +++ b/homeassistant/components/waze_travel_time/manifest.json @@ -6,5 +6,5 @@ "documentation": "https://www.home-assistant.io/integrations/waze_travel_time", "iot_class": "cloud_polling", "loggers": ["pywaze", "homeassistant.helpers.location"], - "requirements": ["pywaze==0.3.0"] + "requirements": ["pywaze==0.4.0"] } diff --git a/requirements_all.txt b/requirements_all.txt index ea0c146ae25..7f7424431cf 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2231,7 +2231,7 @@ pyvlx==0.2.20 pyvolumio==0.1.5 # homeassistant.components.waze_travel_time -pywaze==0.3.0 +pywaze==0.4.0 # homeassistant.components.html5 pywebpush==1.9.2 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 7339d2a6ba4..3f03459b0a1 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1639,7 +1639,7 @@ pyvizio==0.1.61 pyvolumio==0.1.5 # homeassistant.components.waze_travel_time -pywaze==0.3.0 +pywaze==0.4.0 # homeassistant.components.html5 pywebpush==1.9.2 From a19bc71300d703f052159361a59d35a7753edbcb Mon Sep 17 00:00:00 2001 From: Aidan Timson Date: Sun, 10 Sep 2023 16:02:42 +0100 Subject: [PATCH 17/34] Bump systembridgeconnector to 3.8.2 (#100051) Update systembridgeconnector to 3.8.2 --- homeassistant/components/system_bridge/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/system_bridge/manifest.json b/homeassistant/components/system_bridge/manifest.json index c0f89c16339..bcc6189c8ef 100644 --- a/homeassistant/components/system_bridge/manifest.json +++ b/homeassistant/components/system_bridge/manifest.json @@ -10,6 +10,6 @@ "iot_class": "local_push", "loggers": ["systembridgeconnector"], "quality_scale": "silver", - "requirements": ["systembridgeconnector==3.4.9"], + "requirements": ["systembridgeconnector==3.8.2"], "zeroconf": ["_system-bridge._tcp.local."] } diff --git a/requirements_all.txt b/requirements_all.txt index 7f7424431cf..727fa06629c 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2504,7 +2504,7 @@ swisshydrodata==0.1.0 synology-srm==0.2.0 # homeassistant.components.system_bridge -systembridgeconnector==3.4.9 +systembridgeconnector==3.8.2 # homeassistant.components.tailscale tailscale==0.2.0 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 3f03459b0a1..a6e5369dcba 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1837,7 +1837,7 @@ sunwatcher==0.2.1 surepy==0.8.0 # homeassistant.components.system_bridge -systembridgeconnector==3.4.9 +systembridgeconnector==3.8.2 # homeassistant.components.tailscale tailscale==0.2.0 From a651e9df1d3d59047240db840f6154afccde6ebc Mon Sep 17 00:00:00 2001 From: Simone Chemelli Date: Sun, 10 Sep 2023 19:09:21 +0200 Subject: [PATCH 18/34] Bump aiovodafone to 0.2.0 (#100062) bump aiovodafone to 0.2.0 --- homeassistant/components/vodafone_station/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/vodafone_station/manifest.json b/homeassistant/components/vodafone_station/manifest.json index 5470cdd684c..68e7665b5ac 100644 --- a/homeassistant/components/vodafone_station/manifest.json +++ b/homeassistant/components/vodafone_station/manifest.json @@ -6,5 +6,5 @@ "documentation": "https://www.home-assistant.io/integrations/vodafone_station", "iot_class": "local_polling", "loggers": ["aiovodafone"], - "requirements": ["aiovodafone==0.1.0"] + "requirements": ["aiovodafone==0.2.0"] } diff --git a/requirements_all.txt b/requirements_all.txt index 727fa06629c..ef2b818bc79 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -369,7 +369,7 @@ aiounifi==61 aiovlc==0.1.0 # homeassistant.components.vodafone_station -aiovodafone==0.1.0 +aiovodafone==0.2.0 # homeassistant.components.waqi aiowaqi==0.2.1 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index a6e5369dcba..a644b9cf97a 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -344,7 +344,7 @@ aiounifi==61 aiovlc==0.1.0 # homeassistant.components.vodafone_station -aiovodafone==0.1.0 +aiovodafone==0.2.0 # homeassistant.components.watttime aiowatttime==0.1.1 From 55a4346460126be6b0ff7d9260e3a3c49bbea2bc Mon Sep 17 00:00:00 2001 From: Simone Chemelli Date: Mon, 11 Sep 2023 08:49:10 +0200 Subject: [PATCH 19/34] Remove Comelit alarm data retrieval (#100067) fix: remove alarm data retrieval --- homeassistant/components/comelit/coordinator.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/homeassistant/components/comelit/coordinator.py b/homeassistant/components/comelit/coordinator.py index beb7266c403..1affd5046fe 100644 --- a/homeassistant/components/comelit/coordinator.py +++ b/homeassistant/components/comelit/coordinator.py @@ -44,7 +44,6 @@ class ComelitSerialBridge(DataUpdateCoordinator): raise ConfigEntryAuthFailed devices_data = await self.api.get_all_devices() - alarm_data = await self.api.get_alarm_config() await self.api.logout() - return devices_data | alarm_data + return devices_data From 2cb84274ec04c0ce2e5df48ca629dee433222c5f Mon Sep 17 00:00:00 2001 From: Mike Degatano Date: Tue, 12 Sep 2023 09:59:12 -0400 Subject: [PATCH 20/34] Fix addon slug validation (#100070) * Fix addon slug validation * Don't redefine compile --- homeassistant/components/hassio/__init__.py | 5 ++- tests/components/hassio/test_init.py | 35 +++++++++++++++++++++ 2 files changed, 39 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/hassio/__init__.py b/homeassistant/components/hassio/__init__.py index 72fb5ce5110..270309149ef 100644 --- a/homeassistant/components/hassio/__init__.py +++ b/homeassistant/components/hassio/__init__.py @@ -6,6 +6,7 @@ from contextlib import suppress from datetime import datetime, timedelta import logging import os +import re from typing import Any, NamedTuple import voluptuous as vol @@ -149,10 +150,12 @@ SERVICE_BACKUP_PARTIAL = "backup_partial" SERVICE_RESTORE_FULL = "restore_full" SERVICE_RESTORE_PARTIAL = "restore_partial" +VALID_ADDON_SLUG = vol.Match(re.compile(r"^[-_.A-Za-z0-9]+$")) + def valid_addon(value: Any) -> str: """Validate value is a valid addon slug.""" - value = cv.slug(value) + value = VALID_ADDON_SLUG(value) hass: HomeAssistant | None = None with suppress(HomeAssistantError): diff --git a/tests/components/hassio/test_init.py b/tests/components/hassio/test_init.py index 31ee73013da..48f52ee7c24 100644 --- a/tests/components/hassio/test_init.py +++ b/tests/components/hassio/test_init.py @@ -633,6 +633,41 @@ async def test_invalid_service_calls( ) +async def test_addon_service_call_with_complex_slug( + hass: HomeAssistant, + aioclient_mock: AiohttpClientMocker, +) -> None: + """Addon slugs can have ., - and _, confirm that passes validation.""" + supervisor_mock_data = { + "version_latest": "1.0.0", + "version": "1.0.0", + "auto_update": True, + "addons": [ + { + "name": "test.a_1-2", + "slug": "test.a_1-2", + "state": "stopped", + "update_available": False, + "version": "1.0.0", + "version_latest": "1.0.0", + "repository": "core", + "icon": False, + }, + ], + } + with patch.dict(os.environ, MOCK_ENVIRON), patch( + "homeassistant.components.hassio.HassIO.is_connected", + return_value=None, + ), patch( + "homeassistant.components.hassio.HassIO.get_supervisor_info", + return_value=supervisor_mock_data, + ): + assert await async_setup_component(hass, "hassio", {}) + await hass.async_block_till_done() + + await hass.services.async_call("hassio", "addon_start", {"addon": "test.a_1-2"}) + + async def test_service_calls_core( hassio_env, hass: HomeAssistant, aioclient_mock: AiohttpClientMocker ) -> None: From df718ca509d00dc6ffcb38cf7cf8a079e4236fa4 Mon Sep 17 00:00:00 2001 From: Greig Sheridan Date: Mon, 11 Sep 2023 19:16:21 +1200 Subject: [PATCH 21/34] Remove duplicated word in enphase description text (#100098) --- homeassistant/components/enphase_envoy/strings.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/enphase_envoy/strings.json b/homeassistant/components/enphase_envoy/strings.json index ae0ac31413c..92eca38ef20 100644 --- a/homeassistant/components/enphase_envoy/strings.json +++ b/homeassistant/components/enphase_envoy/strings.json @@ -3,7 +3,7 @@ "flow_title": "{serial} ({host})", "step": { "user": { - "description": "For firmware version 7.0 and later, enter the Enphase cloud credentials, for older models models, enter username `installer` without a password.", + "description": "For firmware version 7.0 and later, enter the Enphase cloud credentials, for older models, enter username `installer` without a password.", "data": { "host": "[%key:common::config_flow::data::host%]", "username": "[%key:common::config_flow::data::username%]", From 7a4a792b7bf23e42ec332cf17cebc140c8163c8c Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Mon, 11 Sep 2023 14:33:43 +0200 Subject: [PATCH 22/34] Fix TriggerEntity.async_added_to_hass (#100119) --- .../components/template/trigger_entity.py | 3 +- tests/components/template/test_sensor.py | 44 ++++++++++++++++++- 2 files changed, 44 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/template/trigger_entity.py b/homeassistant/components/template/trigger_entity.py index ca2f7240086..5f5fbe5b99a 100644 --- a/homeassistant/components/template/trigger_entity.py +++ b/homeassistant/components/template/trigger_entity.py @@ -23,8 +23,7 @@ class TriggerEntity(TriggerBaseEntity, CoordinatorEntity[TriggerUpdateCoordinato async def async_added_to_hass(self) -> None: """Handle being added to Home Assistant.""" - await TriggerBaseEntity.async_added_to_hass(self) - await CoordinatorEntity.async_added_to_hass(self) # type: ignore[arg-type] + await super().async_added_to_hass() if self.coordinator.data is not None: self._process_data() diff --git a/tests/components/template/test_sensor.py b/tests/components/template/test_sensor.py index 1bd1e797c05..4010bb34d2d 100644 --- a/tests/components/template/test_sensor.py +++ b/tests/components/template/test_sensor.py @@ -1,7 +1,7 @@ """The test for the Template sensor platform.""" from asyncio import Event from datetime import timedelta -from unittest.mock import patch +from unittest.mock import ANY, patch import pytest @@ -1140,6 +1140,48 @@ async def test_trigger_entity( assert state.context is context +@pytest.mark.parametrize(("count", "domain"), [(1, "template")]) +@pytest.mark.parametrize( + "config", + [ + { + "template": [ + { + "trigger": {"platform": "event", "event_type": "test_event"}, + "sensors": { + "hello": { + "friendly_name": "Hello Name", + "value_template": "{{ trigger.event.data.beer }}", + "entity_picture_template": "{{ '/local/dogs.png' }}", + "icon_template": "{{ 'mdi:pirate' }}", + "attribute_templates": { + "last": "{{now().strftime('%D %X')}}", + "history_1": "{{this.attributes.last|default('Not yet set')}}", + }, + }, + }, + }, + ], + }, + ], +) +async def test_trigger_entity_runs_once( + hass: HomeAssistant, start_ha, entity_registry: er.EntityRegistry +) -> None: + """Test trigger entity handles a trigger once.""" + state = hass.states.get("sensor.hello_name") + assert state is not None + assert state.state == STATE_UNKNOWN + + hass.bus.async_fire("test_event", {"beer": 2}) + await hass.async_block_till_done() + + state = hass.states.get("sensor.hello_name") + assert state.state == "2" + assert state.attributes.get("last") == ANY + assert state.attributes.get("history_1") == "Not yet set" + + @pytest.mark.parametrize(("count", "domain"), [(1, "template")]) @pytest.mark.parametrize( "config", From 8e2fa67cfd385788e6ce970f328f25dcf9abb647 Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Mon, 11 Sep 2023 18:35:48 +0200 Subject: [PATCH 23/34] Bump hatasmota to 0.7.2 (#100129) --- .../components/tasmota/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- tests/components/tasmota/test_light.py | 32 +++++++++++++++++++ tests/components/tasmota/test_switch.py | 32 +++++++++++++++++++ 5 files changed, 67 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/tasmota/manifest.json b/homeassistant/components/tasmota/manifest.json index 9843f64fc25..fa34665cd73 100644 --- a/homeassistant/components/tasmota/manifest.json +++ b/homeassistant/components/tasmota/manifest.json @@ -8,5 +8,5 @@ "iot_class": "local_push", "loggers": ["hatasmota"], "mqtt": ["tasmota/discovery/#"], - "requirements": ["HATasmota==0.7.1"] + "requirements": ["HATasmota==0.7.2"] } diff --git a/requirements_all.txt b/requirements_all.txt index ef2b818bc79..2ca4a3b85ae 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -29,7 +29,7 @@ DoorBirdPy==2.1.0 HAP-python==4.7.1 # homeassistant.components.tasmota -HATasmota==0.7.1 +HATasmota==0.7.2 # homeassistant.components.mastodon Mastodon.py==1.5.1 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index a644b9cf97a..7818564c784 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -28,7 +28,7 @@ DoorBirdPy==2.1.0 HAP-python==4.7.1 # homeassistant.components.tasmota -HATasmota==0.7.1 +HATasmota==0.7.2 # homeassistant.components.doods # homeassistant.components.generic diff --git a/tests/components/tasmota/test_light.py b/tests/components/tasmota/test_light.py index 5c8339a6f89..82fa89c5280 100644 --- a/tests/components/tasmota/test_light.py +++ b/tests/components/tasmota/test_light.py @@ -1835,3 +1835,35 @@ async def test_entity_id_update_discovery_update( await help_test_entity_id_update_discovery_update( hass, mqtt_mock, Platform.LIGHT, config ) + + +async def test_no_device_name( + hass: HomeAssistant, mqtt_mock: MqttMockHAClient, setup_tasmota +) -> None: + """Test name of lights when no device name is set. + + When the device name is not set, Tasmota uses friendly name 1 as device naem. + This test ensures that case is handled correctly. + """ + config = copy.deepcopy(DEFAULT_CONFIG) + config["dn"] = "Light 1" + config["fn"][0] = "Light 1" + config["fn"][1] = "Light 2" + config["rl"][0] = 2 + config["rl"][1] = 2 + mac = config["mac"] + + async_fire_mqtt_message( + hass, + f"{DEFAULT_PREFIX}/{mac}/config", + json.dumps(config), + ) + await hass.async_block_till_done() + + state = hass.states.get("light.light_1") + assert state is not None + assert state.attributes["friendly_name"] == "Light 1" + + state = hass.states.get("light.light_1_light_2") + assert state is not None + assert state.attributes["friendly_name"] == "Light 1 Light 2" diff --git a/tests/components/tasmota/test_switch.py b/tests/components/tasmota/test_switch.py index b8d0ed2d060..54d94b46fe8 100644 --- a/tests/components/tasmota/test_switch.py +++ b/tests/components/tasmota/test_switch.py @@ -283,3 +283,35 @@ async def test_entity_id_update_discovery_update( await help_test_entity_id_update_discovery_update( hass, mqtt_mock, Platform.SWITCH, config ) + + +async def test_no_device_name( + hass: HomeAssistant, mqtt_mock: MqttMockHAClient, setup_tasmota +) -> None: + """Test name of switches when no device name is set. + + When the device name is not set, Tasmota uses friendly name 1 as device naem. + This test ensures that case is handled correctly. + """ + config = copy.deepcopy(DEFAULT_CONFIG) + config["dn"] = "Relay 1" + config["fn"][0] = "Relay 1" + config["fn"][1] = "Relay 2" + config["rl"][0] = 1 + config["rl"][1] = 1 + mac = config["mac"] + + async_fire_mqtt_message( + hass, + f"{DEFAULT_PREFIX}/{mac}/config", + json.dumps(config), + ) + await hass.async_block_till_done() + + state = hass.states.get("switch.relay_1") + assert state is not None + assert state.attributes["friendly_name"] == "Relay 1" + + state = hass.states.get("switch.relay_1_relay_2") + assert state is not None + assert state.attributes["friendly_name"] == "Relay 1 Relay 2" From 04549925c296365bfed4966274b0efbeb0f49021 Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Mon, 11 Sep 2023 23:06:21 +0200 Subject: [PATCH 24/34] Update frontend to 20230911.0 (#100139) --- homeassistant/components/frontend/manifest.json | 2 +- homeassistant/package_constraints.txt | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/frontend/manifest.json b/homeassistant/components/frontend/manifest.json index 58de25fc03d..6291e3a237e 100644 --- a/homeassistant/components/frontend/manifest.json +++ b/homeassistant/components/frontend/manifest.json @@ -20,5 +20,5 @@ "documentation": "https://www.home-assistant.io/integrations/frontend", "integration_type": "system", "quality_scale": "internal", - "requirements": ["home-assistant-frontend==20230908.0"] + "requirements": ["home-assistant-frontend==20230911.0"] } diff --git a/homeassistant/package_constraints.txt b/homeassistant/package_constraints.txt index 7bd0c843e6a..bd47d696230 100644 --- a/homeassistant/package_constraints.txt +++ b/homeassistant/package_constraints.txt @@ -22,7 +22,7 @@ ha-av==10.1.1 hass-nabucasa==0.70.0 hassil==1.2.5 home-assistant-bluetooth==1.10.3 -home-assistant-frontend==20230908.0 +home-assistant-frontend==20230911.0 home-assistant-intents==2023.8.2 httpx==0.24.1 ifaddr==0.2.0 diff --git a/requirements_all.txt b/requirements_all.txt index 2ca4a3b85ae..30112fe9b59 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -994,7 +994,7 @@ hole==0.8.0 holidays==0.28 # homeassistant.components.frontend -home-assistant-frontend==20230908.0 +home-assistant-frontend==20230911.0 # homeassistant.components.conversation home-assistant-intents==2023.8.2 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 7818564c784..cef0f2493e9 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -777,7 +777,7 @@ hole==0.8.0 holidays==0.28 # homeassistant.components.frontend -home-assistant-frontend==20230908.0 +home-assistant-frontend==20230911.0 # homeassistant.components.conversation home-assistant-intents==2023.8.2 From 5068846fc996bad06808cfc80f11438b90ade067 Mon Sep 17 00:00:00 2001 From: Robert Svensson Date: Mon, 11 Sep 2023 21:50:29 +0200 Subject: [PATCH 25/34] Fix devices not always reporting IP - bump aiounifi to v62 (#100149) --- homeassistant/components/unifi/device_tracker.py | 4 ++-- homeassistant/components/unifi/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/homeassistant/components/unifi/device_tracker.py b/homeassistant/components/unifi/device_tracker.py index fcfe71a2858..71b0a9869a9 100644 --- a/homeassistant/components/unifi/device_tracker.py +++ b/homeassistant/components/unifi/device_tracker.py @@ -139,7 +139,7 @@ class UnifiEntityTrackerDescriptionMixin(Generic[HandlerT, ApiItemT]): """Device tracker local functions.""" heartbeat_timedelta_fn: Callable[[UniFiController, str], timedelta] - ip_address_fn: Callable[[aiounifi.Controller, str], str] + ip_address_fn: Callable[[aiounifi.Controller, str], str | None] is_connected_fn: Callable[[UniFiController, str], bool] hostname_fn: Callable[[aiounifi.Controller, str], str | None] @@ -249,7 +249,7 @@ class UnifiScannerEntity(UnifiEntity[HandlerT, ApiItemT], ScannerEntity): return self.entity_description.hostname_fn(self.controller.api, self._obj_id) @property - def ip_address(self) -> str: + def ip_address(self) -> str | None: """Return the primary ip address of the device.""" return self.entity_description.ip_address_fn(self.controller.api, self._obj_id) diff --git a/homeassistant/components/unifi/manifest.json b/homeassistant/components/unifi/manifest.json index f20e5f9e4ac..8734fd7dce5 100644 --- a/homeassistant/components/unifi/manifest.json +++ b/homeassistant/components/unifi/manifest.json @@ -8,7 +8,7 @@ "iot_class": "local_push", "loggers": ["aiounifi"], "quality_scale": "platinum", - "requirements": ["aiounifi==61"], + "requirements": ["aiounifi==62"], "ssdp": [ { "manufacturer": "Ubiquiti Networks", diff --git a/requirements_all.txt b/requirements_all.txt index 30112fe9b59..b9b43688779 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -363,7 +363,7 @@ aiosyncthing==0.5.1 aiotractive==0.5.6 # homeassistant.components.unifi -aiounifi==61 +aiounifi==62 # homeassistant.components.vlc_telnet aiovlc==0.1.0 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index cef0f2493e9..bcc92a956eb 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -338,7 +338,7 @@ aiosyncthing==0.5.1 aiotractive==0.5.6 # homeassistant.components.unifi -aiounifi==61 +aiounifi==62 # homeassistant.components.vlc_telnet aiovlc==0.1.0 From d1bc6df14f81a89725a3157b131530240991a22e Mon Sep 17 00:00:00 2001 From: Michael <35783820+mib1185@users.noreply.github.com> Date: Tue, 12 Sep 2023 04:30:50 +0200 Subject: [PATCH 26/34] Fix AVM Fritz!Tools update entity (#100151) * move update entity to coordinator * fix tests --- homeassistant/components/fritz/common.py | 11 ++++-- homeassistant/components/fritz/update.py | 38 ++++++++++++------ tests/components/fritz/test_update.py | 49 +++++++++++++++--------- 3 files changed, 63 insertions(+), 35 deletions(-) diff --git a/homeassistant/components/fritz/common.py b/homeassistant/components/fritz/common.py index 69773778121..76368175ca0 100644 --- a/homeassistant/components/fritz/common.py +++ b/homeassistant/components/fritz/common.py @@ -1096,7 +1096,7 @@ class FritzBoxBaseEntity: class FritzRequireKeysMixin: """Fritz entity description mix in.""" - value_fn: Callable[[FritzStatus, Any], Any] + value_fn: Callable[[FritzStatus, Any], Any] | None @dataclass @@ -1118,9 +1118,12 @@ class FritzBoxBaseCoordinatorEntity(update_coordinator.CoordinatorEntity[AvmWrap ) -> None: """Init device info class.""" super().__init__(avm_wrapper) - self.async_on_remove( - avm_wrapper.register_entity_updates(description.key, description.value_fn) - ) + if description.value_fn is not None: + self.async_on_remove( + avm_wrapper.register_entity_updates( + description.key, description.value_fn + ) + ) self.entity_description = description self._device_name = device_name self._attr_unique_id = f"{avm_wrapper.unique_id}-{description.key}" diff --git a/homeassistant/components/fritz/update.py b/homeassistant/components/fritz/update.py index 03cffc3cae6..80cbe1f4c5c 100644 --- a/homeassistant/components/fritz/update.py +++ b/homeassistant/components/fritz/update.py @@ -1,20 +1,31 @@ """Support for AVM FRITZ!Box update platform.""" from __future__ import annotations +from dataclasses import dataclass import logging from typing import Any -from homeassistant.components.update import UpdateEntity, UpdateEntityFeature +from homeassistant.components.update import ( + UpdateEntity, + UpdateEntityDescription, + UpdateEntityFeature, +) from homeassistant.config_entries import ConfigEntry +from homeassistant.const import EntityCategory from homeassistant.core import HomeAssistant from homeassistant.helpers.entity_platform import AddEntitiesCallback -from .common import AvmWrapper, FritzBoxBaseEntity +from .common import AvmWrapper, FritzBoxBaseCoordinatorEntity, FritzEntityDescription from .const import DOMAIN _LOGGER = logging.getLogger(__name__) +@dataclass +class FritzUpdateEntityDescription(UpdateEntityDescription, FritzEntityDescription): + """Describes Fritz update entity.""" + + async def async_setup_entry( hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback ) -> None: @@ -27,11 +38,13 @@ async def async_setup_entry( async_add_entities(entities) -class FritzBoxUpdateEntity(FritzBoxBaseEntity, UpdateEntity): +class FritzBoxUpdateEntity(FritzBoxBaseCoordinatorEntity, UpdateEntity): """Mixin for update entity specific attributes.""" + _attr_entity_category = EntityCategory.CONFIG _attr_supported_features = UpdateEntityFeature.INSTALL _attr_title = "FRITZ!OS" + entity_description: FritzUpdateEntityDescription def __init__( self, @@ -39,29 +52,30 @@ class FritzBoxUpdateEntity(FritzBoxBaseEntity, UpdateEntity): device_friendly_name: str, ) -> None: """Init FRITZ!Box connectivity class.""" - self._attr_name = f"{device_friendly_name} FRITZ!OS" - self._attr_unique_id = f"{avm_wrapper.unique_id}-update" - super().__init__(avm_wrapper, device_friendly_name) + description = FritzUpdateEntityDescription( + key="update", name="FRITZ!OS", value_fn=None + ) + super().__init__(avm_wrapper, device_friendly_name, description) @property def installed_version(self) -> str | None: """Version currently in use.""" - return self._avm_wrapper.current_firmware + return self.coordinator.current_firmware @property def latest_version(self) -> str | None: """Latest version available for install.""" - if self._avm_wrapper.update_available: - return self._avm_wrapper.latest_firmware - return self._avm_wrapper.current_firmware + if self.coordinator.update_available: + return self.coordinator.latest_firmware + return self.coordinator.current_firmware @property def release_url(self) -> str | None: """URL to the full release notes of the latest version available.""" - return self._avm_wrapper.release_url + return self.coordinator.release_url async def async_install( self, version: str | None, backup: bool, **kwargs: Any ) -> None: """Install an update.""" - await self._avm_wrapper.async_trigger_firmware_update() + await self.coordinator.async_trigger_firmware_update() diff --git a/tests/components/fritz/test_update.py b/tests/components/fritz/test_update.py index dbff4713553..bc677e28ebe 100644 --- a/tests/components/fritz/test_update.py +++ b/tests/components/fritz/test_update.py @@ -8,11 +8,25 @@ from homeassistant.config_entries import ConfigEntryState from homeassistant.core import HomeAssistant from homeassistant.setup import async_setup_component -from .const import MOCK_FIRMWARE_AVAILABLE, MOCK_FIRMWARE_RELEASE_URL, MOCK_USER_DATA +from .const import ( + MOCK_FB_SERVICES, + MOCK_FIRMWARE_AVAILABLE, + MOCK_FIRMWARE_RELEASE_URL, + MOCK_USER_DATA, +) from tests.common import MockConfigEntry from tests.typing import ClientSessionGenerator +AVAILABLE_UPDATE = { + "UserInterface1": { + "GetInfo": { + "NewX_AVM-DE_Version": MOCK_FIRMWARE_AVAILABLE, + "NewX_AVM-DE_InfoURL": MOCK_FIRMWARE_RELEASE_URL, + }, + } +} + async def test_update_entities_initialized( hass: HomeAssistant, @@ -41,23 +55,21 @@ async def test_update_available( ) -> None: """Test update entities.""" - with patch( - "homeassistant.components.fritz.common.FritzBoxTools._update_device_info", - return_value=(True, MOCK_FIRMWARE_AVAILABLE, MOCK_FIRMWARE_RELEASE_URL), - ): - entry = MockConfigEntry(domain=DOMAIN, data=MOCK_USER_DATA) - entry.add_to_hass(hass) + fc_class_mock().override_services({**MOCK_FB_SERVICES, **AVAILABLE_UPDATE}) - assert await async_setup_component(hass, DOMAIN, {}) - await hass.async_block_till_done() - assert entry.state == ConfigEntryState.LOADED + entry = MockConfigEntry(domain=DOMAIN, data=MOCK_USER_DATA) + entry.add_to_hass(hass) - update = hass.states.get("update.mock_title_fritz_os") - assert update is not None - assert update.state == "on" - assert update.attributes.get("installed_version") == "7.29" - assert update.attributes.get("latest_version") == MOCK_FIRMWARE_AVAILABLE - assert update.attributes.get("release_url") == MOCK_FIRMWARE_RELEASE_URL + assert await async_setup_component(hass, DOMAIN, {}) + await hass.async_block_till_done() + assert entry.state == ConfigEntryState.LOADED + + update = hass.states.get("update.mock_title_fritz_os") + assert update is not None + assert update.state == "on" + assert update.attributes.get("installed_version") == "7.29" + assert update.attributes.get("latest_version") == MOCK_FIRMWARE_AVAILABLE + assert update.attributes.get("release_url") == MOCK_FIRMWARE_RELEASE_URL async def test_no_update_available( @@ -90,10 +102,9 @@ async def test_available_update_can_be_installed( ) -> None: """Test update entities.""" + fc_class_mock().override_services({**MOCK_FB_SERVICES, **AVAILABLE_UPDATE}) + with patch( - "homeassistant.components.fritz.common.FritzBoxTools._update_device_info", - return_value=(True, MOCK_FIRMWARE_AVAILABLE, MOCK_FIRMWARE_RELEASE_URL), - ), patch( "homeassistant.components.fritz.common.FritzBoxTools.async_trigger_firmware_update", return_value=True, ) as mocked_update_call: From 68b0f05758fb286f4c170dab4ce872d7a6ccabef Mon Sep 17 00:00:00 2001 From: puddly <32534428+puddly@users.noreply.github.com> Date: Tue, 12 Sep 2023 04:23:55 +0200 Subject: [PATCH 27/34] Bump ZHA dependencies (#100156) --- homeassistant/components/zha/manifest.json | 4 ++-- requirements_all.txt | 4 ++-- requirements_test_all.txt | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/homeassistant/components/zha/manifest.json b/homeassistant/components/zha/manifest.json index cce223fac11..c3fa6b1ff01 100644 --- a/homeassistant/components/zha/manifest.json +++ b/homeassistant/components/zha/manifest.json @@ -25,9 +25,9 @@ "pyserial==3.5", "pyserial-asyncio==0.6", "zha-quirks==0.0.103", - "zigpy-deconz==0.21.0", + "zigpy-deconz==0.21.1", "zigpy==0.57.1", - "zigpy-xbee==0.18.1", + "zigpy-xbee==0.18.2", "zigpy-zigate==0.11.0", "zigpy-znp==0.11.4", "universal-silabs-flasher==0.0.13" diff --git a/requirements_all.txt b/requirements_all.txt index b9b43688779..8d7ed7bff24 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2781,10 +2781,10 @@ zhong-hong-hvac==1.0.9 ziggo-mediabox-xl==1.1.0 # homeassistant.components.zha -zigpy-deconz==0.21.0 +zigpy-deconz==0.21.1 # homeassistant.components.zha -zigpy-xbee==0.18.1 +zigpy-xbee==0.18.2 # homeassistant.components.zha zigpy-zigate==0.11.0 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index bcc92a956eb..a6b521e5c1e 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -2045,10 +2045,10 @@ zeversolar==0.3.1 zha-quirks==0.0.103 # homeassistant.components.zha -zigpy-deconz==0.21.0 +zigpy-deconz==0.21.1 # homeassistant.components.zha -zigpy-xbee==0.18.1 +zigpy-xbee==0.18.2 # homeassistant.components.zha zigpy-zigate==0.11.0 From 1cd80c5b78ccca3b030b3120ddc976f3d562d6d2 Mon Sep 17 00:00:00 2001 From: Raman Gupta <7243222+raman325@users.noreply.github.com> Date: Mon, 11 Sep 2023 23:03:47 -0400 Subject: [PATCH 28/34] Bump zwave-js-server-python to 0.51.2 (#100159) --- homeassistant/components/zwave_js/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/zwave_js/manifest.json b/homeassistant/components/zwave_js/manifest.json index 080074451bd..4ea46099f14 100644 --- a/homeassistant/components/zwave_js/manifest.json +++ b/homeassistant/components/zwave_js/manifest.json @@ -9,7 +9,7 @@ "iot_class": "local_push", "loggers": ["zwave_js_server"], "quality_scale": "platinum", - "requirements": ["pyserial==3.5", "zwave-js-server-python==0.51.1"], + "requirements": ["pyserial==3.5", "zwave-js-server-python==0.51.2"], "usb": [ { "vid": "0658", diff --git a/requirements_all.txt b/requirements_all.txt index 8d7ed7bff24..e48adf70f51 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2799,7 +2799,7 @@ zigpy==0.57.1 zm-py==0.5.2 # homeassistant.components.zwave_js -zwave-js-server-python==0.51.1 +zwave-js-server-python==0.51.2 # homeassistant.components.zwave_me zwave-me-ws==0.4.3 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index a6b521e5c1e..74485ef47ec 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -2060,7 +2060,7 @@ zigpy-znp==0.11.4 zigpy==0.57.1 # homeassistant.components.zwave_js -zwave-js-server-python==0.51.1 +zwave-js-server-python==0.51.2 # homeassistant.components.zwave_me zwave-me-ws==0.4.3 From 3c27283fc1252a68272b55e2d7d3d5eab2b2ff36 Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Tue, 12 Sep 2023 08:59:39 +0200 Subject: [PATCH 29/34] Adjust tasmota sensor device class and icon mapping (#100168) --- homeassistant/components/tasmota/sensor.py | 18 +++++++----------- 1 file changed, 7 insertions(+), 11 deletions(-) diff --git a/homeassistant/components/tasmota/sensor.py b/homeassistant/components/tasmota/sensor.py index ddcdb3e8c26..8365fd97ca4 100644 --- a/homeassistant/components/tasmota/sensor.py +++ b/homeassistant/components/tasmota/sensor.py @@ -88,12 +88,10 @@ SENSOR_DEVICE_CLASS_ICON_MAP: dict[str, dict[str, Any]] = { hc.SENSOR_COLOR_GREEN: {ICON: "mdi:palette"}, hc.SENSOR_COLOR_RED: {ICON: "mdi:palette"}, hc.SENSOR_CURRENT: { - ICON: "mdi:alpha-a-circle-outline", DEVICE_CLASS: SensorDeviceClass.CURRENT, STATE_CLASS: SensorStateClass.MEASUREMENT, }, hc.SENSOR_CURRENTNEUTRAL: { - ICON: "mdi:alpha-a-circle-outline", DEVICE_CLASS: SensorDeviceClass.CURRENT, STATE_CLASS: SensorStateClass.MEASUREMENT, }, @@ -103,11 +101,14 @@ SENSOR_DEVICE_CLASS_ICON_MAP: dict[str, dict[str, Any]] = { STATE_CLASS: SensorStateClass.MEASUREMENT, }, hc.SENSOR_DISTANCE: { - ICON: "mdi:leak", DEVICE_CLASS: SensorDeviceClass.DISTANCE, STATE_CLASS: SensorStateClass.MEASUREMENT, }, hc.SENSOR_ECO2: {ICON: "mdi:molecule-co2"}, + hc.SENSOR_ENERGY: { + DEVICE_CLASS: SensorDeviceClass.ENERGY, + STATE_CLASS: SensorStateClass.TOTAL, + }, hc.SENSOR_FREQUENCY: { DEVICE_CLASS: SensorDeviceClass.FREQUENCY, STATE_CLASS: SensorStateClass.MEASUREMENT, @@ -122,10 +123,7 @@ SENSOR_DEVICE_CLASS_ICON_MAP: dict[str, dict[str, Any]] = { }, hc.SENSOR_STATUS_IP: {ICON: "mdi:ip-network"}, hc.SENSOR_STATUS_LINK_COUNT: {ICON: "mdi:counter"}, - hc.SENSOR_MOISTURE: { - DEVICE_CLASS: SensorDeviceClass.MOISTURE, - ICON: "mdi:cup-water", - }, + hc.SENSOR_MOISTURE: {DEVICE_CLASS: SensorDeviceClass.MOISTURE}, hc.SENSOR_STATUS_MQTT_COUNT: {ICON: "mdi:counter"}, hc.SENSOR_PB0_3: {ICON: "mdi:flask"}, hc.SENSOR_PB0_5: {ICON: "mdi:flask"}, @@ -146,7 +144,6 @@ SENSOR_DEVICE_CLASS_ICON_MAP: dict[str, dict[str, Any]] = { STATE_CLASS: SensorStateClass.MEASUREMENT, }, hc.SENSOR_POWERFACTOR: { - ICON: "mdi:alpha-f-circle-outline", DEVICE_CLASS: SensorDeviceClass.POWER_FACTOR, STATE_CLASS: SensorStateClass.MEASUREMENT, }, @@ -162,7 +159,7 @@ SENSOR_DEVICE_CLASS_ICON_MAP: dict[str, dict[str, Any]] = { DEVICE_CLASS: SensorDeviceClass.PRESSURE, STATE_CLASS: SensorStateClass.MEASUREMENT, }, - hc.SENSOR_PROXIMITY: {DEVICE_CLASS: SensorDeviceClass.DISTANCE, ICON: "mdi:ruler"}, + hc.SENSOR_PROXIMITY: {ICON: "mdi:ruler"}, hc.SENSOR_REACTIVE_ENERGYEXPORT: {STATE_CLASS: SensorStateClass.TOTAL}, hc.SENSOR_REACTIVE_ENERGYIMPORT: {STATE_CLASS: SensorStateClass.TOTAL}, hc.SENSOR_REACTIVE_POWERUSAGE: { @@ -195,11 +192,10 @@ SENSOR_DEVICE_CLASS_ICON_MAP: dict[str, dict[str, Any]] = { hc.SENSOR_TOTAL_START_TIME: {ICON: "mdi:progress-clock"}, hc.SENSOR_TVOC: {ICON: "mdi:air-filter"}, hc.SENSOR_VOLTAGE: { - ICON: "mdi:alpha-v-circle-outline", + DEVICE_CLASS: SensorDeviceClass.VOLTAGE, STATE_CLASS: SensorStateClass.MEASUREMENT, }, hc.SENSOR_WEIGHT: { - ICON: "mdi:scale", DEVICE_CLASS: SensorDeviceClass.WEIGHT, STATE_CLASS: SensorStateClass.MEASUREMENT, }, From 1b494fb4bae44cfa322a85e1510dfd51ee1992ee Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Tue, 12 Sep 2023 09:58:05 +0200 Subject: [PATCH 30/34] Bump hatasmota to 0.7.3 (#100169) --- .../components/tasmota/manifest.json | 2 +- homeassistant/components/tasmota/sensor.py | 1 - requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- tests/components/tasmota/test_sensor.py | 214 ++++++++++++++++++ 5 files changed, 217 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/tasmota/manifest.json b/homeassistant/components/tasmota/manifest.json index fa34665cd73..42fc849a2cf 100644 --- a/homeassistant/components/tasmota/manifest.json +++ b/homeassistant/components/tasmota/manifest.json @@ -8,5 +8,5 @@ "iot_class": "local_push", "loggers": ["hatasmota"], "mqtt": ["tasmota/discovery/#"], - "requirements": ["HATasmota==0.7.2"] + "requirements": ["HATasmota==0.7.3"] } diff --git a/homeassistant/components/tasmota/sensor.py b/homeassistant/components/tasmota/sensor.py index 8365fd97ca4..e718c0fdcf4 100644 --- a/homeassistant/components/tasmota/sensor.py +++ b/homeassistant/components/tasmota/sensor.py @@ -216,7 +216,6 @@ SENSOR_UNIT_MAP = { hc.LIGHT_LUX: LIGHT_LUX, hc.MASS_KILOGRAMS: UnitOfMass.KILOGRAMS, hc.PERCENTAGE: PERCENTAGE, - hc.POWER_FACTOR: None, hc.POWER_WATT: UnitOfPower.WATT, hc.PRESSURE_HPA: UnitOfPressure.HPA, hc.REACTIVE_POWER: POWER_VOLT_AMPERE_REACTIVE, diff --git a/requirements_all.txt b/requirements_all.txt index e48adf70f51..f035cbe863b 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -29,7 +29,7 @@ DoorBirdPy==2.1.0 HAP-python==4.7.1 # homeassistant.components.tasmota -HATasmota==0.7.2 +HATasmota==0.7.3 # homeassistant.components.mastodon Mastodon.py==1.5.1 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 74485ef47ec..fdd5656c8c0 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -28,7 +28,7 @@ DoorBirdPy==2.1.0 HAP-python==4.7.1 # homeassistant.components.tasmota -HATasmota==0.7.2 +HATasmota==0.7.3 # homeassistant.components.doods # homeassistant.components.generic diff --git a/tests/components/tasmota/test_sensor.py b/tests/components/tasmota/test_sensor.py index c14c7ffe53c..2f50a84ffdd 100644 --- a/tests/components/tasmota/test_sensor.py +++ b/tests/components/tasmota/test_sensor.py @@ -137,6 +137,27 @@ DICT_SENSOR_CONFIG_2 = { } } +NUMBERED_SENSOR_CONFIG = { + "sn": { + "Time": "2020-09-25T12:47:15", + "ANALOG": { + "Temperature1": 2.4, + "Temperature2": 2.4, + "Illuminance3": 2.4, + }, + "TempUnit": "C", + } +} + +NUMBERED_SENSOR_CONFIG_2 = { + "sn": { + "Time": "2020-09-25T12:47:15", + "ANALOG": { + "CTEnergy1": {"Energy": 0.5, "Power": 2300, "Voltage": 230, "Current": 10}, + }, + "TempUnit": "C", + } +} TEMPERATURE_SENSOR_CONFIG = { "sn": { @@ -343,6 +364,118 @@ TEMPERATURE_SENSOR_CONFIG = { }, ), ), + ( + NUMBERED_SENSOR_CONFIG, + [ + "sensor.tasmota_analog_temperature1", + "sensor.tasmota_analog_temperature2", + "sensor.tasmota_analog_illuminance3", + ], + ( + ( + '{"ANALOG":{"Temperature1":1.2,"Temperature2":3.4,' + '"Illuminance3": 5.6}}' + ), + ( + '{"StatusSNS":{"ANALOG":{"Temperature1": 7.8,"Temperature2": 9.0,' + '"Illuminance3":1.2}}}' + ), + ), + ( + { + "sensor.tasmota_analog_temperature1": { + "state": "1.2", + "attributes": { + "device_class": "temperature", + ATTR_STATE_CLASS: SensorStateClass.MEASUREMENT, + "unit_of_measurement": "°C", + }, + }, + "sensor.tasmota_analog_temperature2": { + "state": "3.4", + "attributes": { + "device_class": "temperature", + ATTR_STATE_CLASS: SensorStateClass.MEASUREMENT, + "unit_of_measurement": "°C", + }, + }, + "sensor.tasmota_analog_illuminance3": { + "state": "5.6", + "attributes": { + "device_class": "illuminance", + ATTR_STATE_CLASS: SensorStateClass.MEASUREMENT, + "unit_of_measurement": "lx", + }, + }, + }, + { + "sensor.tasmota_analog_temperature1": {"state": "7.8"}, + "sensor.tasmota_analog_temperature2": {"state": "9.0"}, + "sensor.tasmota_analog_illuminance3": {"state": "1.2"}, + }, + ), + ), + ( + NUMBERED_SENSOR_CONFIG_2, + [ + "sensor.tasmota_analog_ctenergy1_energy", + "sensor.tasmota_analog_ctenergy1_power", + "sensor.tasmota_analog_ctenergy1_voltage", + "sensor.tasmota_analog_ctenergy1_current", + ], + ( + ( + '{"ANALOG":{"CTEnergy1":' + '{"Energy":0.5,"Power":2300,"Voltage":230,"Current":10}}}' + ), + ( + '{"StatusSNS":{"ANALOG":{"CTEnergy1":' + '{"Energy":1.0,"Power":1150,"Voltage":230,"Current":5}}}}' + ), + ), + ( + { + "sensor.tasmota_analog_ctenergy1_energy": { + "state": "0.5", + "attributes": { + "device_class": "energy", + ATTR_STATE_CLASS: SensorStateClass.TOTAL, + "unit_of_measurement": "kWh", + }, + }, + "sensor.tasmota_analog_ctenergy1_power": { + "state": "2300", + "attributes": { + "device_class": "power", + ATTR_STATE_CLASS: SensorStateClass.MEASUREMENT, + "unit_of_measurement": "W", + }, + }, + "sensor.tasmota_analog_ctenergy1_voltage": { + "state": "230", + "attributes": { + "device_class": "voltage", + ATTR_STATE_CLASS: SensorStateClass.MEASUREMENT, + "unit_of_measurement": "V", + }, + }, + "sensor.tasmota_analog_ctenergy1_current": { + "state": "10", + "attributes": { + "device_class": "current", + ATTR_STATE_CLASS: SensorStateClass.MEASUREMENT, + "unit_of_measurement": "A", + }, + }, + }, + { + "sensor.tasmota_analog_ctenergy1_energy": {"state": "1.0"}, + "sensor.tasmota_analog_ctenergy1_power": {"state": "1150"}, + "sensor.tasmota_analog_ctenergy1_voltage": {"state": "230"}, + "sensor.tasmota_analog_ctenergy1_current": {"state": "5"}, + }, + ), + ), ], ) async def test_controlling_state_via_mqtt( @@ -409,6 +542,87 @@ async def test_controlling_state_via_mqtt( assert state.attributes.get(attribute) == expected +@pytest.mark.parametrize( + ("sensor_config", "entity_ids", "states"), + [ + ( + # The AS33935 energy sensor is not reporting energy in W + {"sn": {"Time": "2020-09-25T12:47:15", "AS3935": {"Energy": None}}}, + ["sensor.tasmota_as3935_energy"], + { + "sensor.tasmota_as3935_energy": { + "device_class": None, + "state_class": None, + "unit_of_measurement": None, + }, + }, + ), + ( + # The AS33935 energy sensor is not reporting energy in W + {"sn": {"Time": "2020-09-25T12:47:15", "LD2410": {"Energy": None}}}, + ["sensor.tasmota_ld2410_energy"], + { + "sensor.tasmota_ld2410_energy": { + "device_class": None, + "state_class": None, + "unit_of_measurement": None, + }, + }, + ), + ( + # Check other energy sensors work + {"sn": {"Time": "2020-09-25T12:47:15", "Other": {"Energy": None}}}, + ["sensor.tasmota_other_energy"], + { + "sensor.tasmota_other_energy": { + "device_class": "energy", + "state_class": "total", + "unit_of_measurement": "kWh", + }, + }, + ), + ], +) +async def test_quantity_override( + hass: HomeAssistant, + mqtt_mock: MqttMockHAClient, + setup_tasmota, + sensor_config, + entity_ids, + states, +) -> None: + """Test quantity override for certain sensors.""" + entity_reg = er.async_get(hass) + config = copy.deepcopy(DEFAULT_CONFIG) + sensor_config = copy.deepcopy(sensor_config) + mac = config["mac"] + + async_fire_mqtt_message( + hass, + f"{DEFAULT_PREFIX}/{mac}/config", + json.dumps(config), + ) + await hass.async_block_till_done() + async_fire_mqtt_message( + hass, + f"{DEFAULT_PREFIX}/{mac}/sensors", + json.dumps(sensor_config), + ) + await hass.async_block_till_done() + + for entity_id in entity_ids: + state = hass.states.get(entity_id) + assert state.state == "unavailable" + expected_state = states[entity_id] + for attribute, expected in expected_state.get("attributes", {}).items(): + assert state.attributes.get(attribute) == expected + + entry = entity_reg.async_get(entity_id) + assert entry.disabled is False + assert entry.disabled_by is None + assert entry.entity_category is None + + async def test_bad_indexed_sensor_state_via_mqtt( hass: HomeAssistant, mqtt_mock: MqttMockHAClient, setup_tasmota ) -> None: From f13ce5daffc9d62b4ac5453717c814ca87b5fb95 Mon Sep 17 00:00:00 2001 From: Vincent Knoop Pathuis <48653141+vpathuis@users.noreply.github.com> Date: Tue, 12 Sep 2023 10:45:35 +0200 Subject: [PATCH 31/34] Bump Ultraheat to version 0.5.7 (#100172) --- homeassistant/components/landisgyr_heat_meter/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/landisgyr_heat_meter/manifest.json b/homeassistant/components/landisgyr_heat_meter/manifest.json index a056f1f6564..1bf77d7ab51 100644 --- a/homeassistant/components/landisgyr_heat_meter/manifest.json +++ b/homeassistant/components/landisgyr_heat_meter/manifest.json @@ -6,5 +6,5 @@ "dependencies": ["usb"], "documentation": "https://www.home-assistant.io/integrations/landisgyr_heat_meter", "iot_class": "local_polling", - "requirements": ["ultraheat-api==0.5.1"] + "requirements": ["ultraheat-api==0.5.7"] } diff --git a/requirements_all.txt b/requirements_all.txt index f035cbe863b..e264f5f52d7 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2603,7 +2603,7 @@ twitchAPI==3.10.0 uasiren==0.0.1 # homeassistant.components.landisgyr_heat_meter -ultraheat-api==0.5.1 +ultraheat-api==0.5.7 # homeassistant.components.unifiprotect unifi-discovery==1.1.7 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index fdd5656c8c0..960a25bc321 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1903,7 +1903,7 @@ twitchAPI==3.10.0 uasiren==0.0.1 # homeassistant.components.landisgyr_heat_meter -ultraheat-api==0.5.1 +ultraheat-api==0.5.7 # homeassistant.components.unifiprotect unifi-discovery==1.1.7 From 63647d96ddb87de3d781122b2d4acd163a63e3de Mon Sep 17 00:00:00 2001 From: Jan Bouwhuis Date: Tue, 12 Sep 2023 15:22:37 +0200 Subject: [PATCH 32/34] Fix entity name attribute on mqtt entity is not removed on update (#100187) Fix entity name attribute is not remove on update --- homeassistant/components/mqtt/mixins.py | 5 +++ tests/components/mqtt/test_mixins.py | 60 ++++++++++++++++++++++++- 2 files changed, 64 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/mqtt/mixins.py b/homeassistant/components/mqtt/mixins.py index 3b28bc8804f..4eae1fae30c 100644 --- a/homeassistant/components/mqtt/mixins.py +++ b/homeassistant/components/mqtt/mixins.py @@ -1135,6 +1135,11 @@ class MqttEntity( elif not self._default_to_device_class_name(): # Assign the default name self._attr_name = self._default_name + elif hasattr(self, "_attr_name"): + # An entity name was not set in the config + # don't set the name attribute and derive + # the name from the device_class + delattr(self, "_attr_name") if CONF_DEVICE in config: device_name: str if CONF_NAME not in config[CONF_DEVICE]: diff --git a/tests/components/mqtt/test_mixins.py b/tests/components/mqtt/test_mixins.py index 0647721b4d0..1ca9bf07d72 100644 --- a/tests/components/mqtt/test_mixins.py +++ b/tests/components/mqtt/test_mixins.py @@ -7,6 +7,7 @@ import pytest from homeassistant.components import mqtt, sensor from homeassistant.components.mqtt.sensor import DEFAULT_NAME as DEFAULT_SENSOR_NAME from homeassistant.const import ( + ATTR_FRIENDLY_NAME, EVENT_HOMEASSISTANT_STARTED, EVENT_STATE_CHANGED, Platform, @@ -324,7 +325,6 @@ async def test_default_entity_and_device_name( This is a test helper for the _setup_common_attributes_from_config mixin. """ - # mqtt_mock = await mqtt_mock_entry() events = async_capture_events(hass, ir.EVENT_REPAIRS_ISSUE_REGISTRY_UPDATED) hass.state = CoreState.starting @@ -352,3 +352,61 @@ async def test_default_entity_and_device_name( # Assert that an issues ware registered assert len(events) == issue_events + + +@patch("homeassistant.components.mqtt.PLATFORMS", [Platform.BINARY_SENSOR]) +async def test_name_attribute_is_set_or_not( + hass: HomeAssistant, + mqtt_mock_entry: MqttMockHAClientGenerator, +) -> None: + """Test frendly name with device_class set. + + This is a test helper for the _setup_common_attributes_from_config mixin. + """ + await mqtt_mock_entry() + async_fire_mqtt_message( + hass, + "homeassistant/binary_sensor/bla/config", + '{ "name": "Gate", "state_topic": "test-topic", "device_class": "door", ' + '"object_id": "gate",' + '"device": {"identifiers": "very_unique", "name": "xyz_door_sensor"}' + "}", + ) + await hass.async_block_till_done() + + state = hass.states.get("binary_sensor.gate") + + assert state is not None + assert state.attributes.get(ATTR_FRIENDLY_NAME) == "Gate" + + # Remove the name in a discovery update + async_fire_mqtt_message( + hass, + "homeassistant/binary_sensor/bla/config", + '{ "state_topic": "test-topic", "device_class": "door", ' + '"object_id": "gate",' + '"device": {"identifiers": "very_unique", "name": "xyz_door_sensor"}' + "}", + ) + await hass.async_block_till_done() + + state = hass.states.get("binary_sensor.gate") + + assert state is not None + assert state.attributes.get(ATTR_FRIENDLY_NAME) == "Door" + + # Set the name to `null` in a discovery update + async_fire_mqtt_message( + hass, + "homeassistant/binary_sensor/bla/config", + '{ "name": null, "state_topic": "test-topic", "device_class": "door", ' + '"object_id": "gate",' + '"device": {"identifiers": "very_unique", "name": "xyz_door_sensor"}' + "}", + ) + await hass.async_block_till_done() + + state = hass.states.get("binary_sensor.gate") + + assert state is not None + assert state.attributes.get(ATTR_FRIENDLY_NAME) is None From 7b9ae6755ac4f2cd641591dcc64d83d2dfce2b3a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joakim=20S=C3=B8rensen?= Date: Tue, 12 Sep 2023 15:58:25 +0200 Subject: [PATCH 33/34] Bump hass-nabucasa from 0.70.0 to 0.71.0 (#100193) Bump hass-nabucasa from 0.70.0 to 0.71.1 --- homeassistant/components/cloud/__init__.py | 2 -- homeassistant/components/cloud/const.py | 1 - homeassistant/components/cloud/manifest.json | 2 +- homeassistant/package_constraints.txt | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- tests/components/cloud/test_init.py | 1 - 7 files changed, 4 insertions(+), 8 deletions(-) diff --git a/homeassistant/components/cloud/__init__.py b/homeassistant/components/cloud/__init__.py index 40e5f264caf..4dc242376d9 100644 --- a/homeassistant/components/cloud/__init__.py +++ b/homeassistant/components/cloud/__init__.py @@ -47,7 +47,6 @@ from .const import ( CONF_FILTER, CONF_GOOGLE_ACTIONS, CONF_RELAYER_SERVER, - CONF_REMOTE_SNI_SERVER, CONF_REMOTESTATE_SERVER, CONF_SERVICEHANDLERS_SERVER, CONF_THINGTALK_SERVER, @@ -115,7 +114,6 @@ CONFIG_SCHEMA = vol.Schema( vol.Optional(CONF_ALEXA_SERVER): str, vol.Optional(CONF_CLOUDHOOK_SERVER): str, vol.Optional(CONF_RELAYER_SERVER): str, - vol.Optional(CONF_REMOTE_SNI_SERVER): str, vol.Optional(CONF_REMOTESTATE_SERVER): str, vol.Optional(CONF_THINGTALK_SERVER): str, vol.Optional(CONF_SERVICEHANDLERS_SERVER): str, diff --git a/homeassistant/components/cloud/const.py b/homeassistant/components/cloud/const.py index 7aa39efbf07..bd9d61cde16 100644 --- a/homeassistant/components/cloud/const.py +++ b/homeassistant/components/cloud/const.py @@ -55,7 +55,6 @@ CONF_ACME_SERVER = "acme_server" CONF_ALEXA_SERVER = "alexa_server" CONF_CLOUDHOOK_SERVER = "cloudhook_server" CONF_RELAYER_SERVER = "relayer_server" -CONF_REMOTE_SNI_SERVER = "remote_sni_server" CONF_REMOTESTATE_SERVER = "remotestate_server" CONF_THINGTALK_SERVER = "thingtalk_server" CONF_SERVICEHANDLERS_SERVER = "servicehandlers_server" diff --git a/homeassistant/components/cloud/manifest.json b/homeassistant/components/cloud/manifest.json index a8e28d66291..fe0628f1886 100644 --- a/homeassistant/components/cloud/manifest.json +++ b/homeassistant/components/cloud/manifest.json @@ -8,5 +8,5 @@ "integration_type": "system", "iot_class": "cloud_push", "loggers": ["hass_nabucasa"], - "requirements": ["hass-nabucasa==0.70.0"] + "requirements": ["hass-nabucasa==0.71.0"] } diff --git a/homeassistant/package_constraints.txt b/homeassistant/package_constraints.txt index bd47d696230..343c3be1481 100644 --- a/homeassistant/package_constraints.txt +++ b/homeassistant/package_constraints.txt @@ -19,7 +19,7 @@ cryptography==41.0.3 dbus-fast==1.95.2 fnv-hash-fast==0.4.1 ha-av==10.1.1 -hass-nabucasa==0.70.0 +hass-nabucasa==0.71.0 hassil==1.2.5 home-assistant-bluetooth==1.10.3 home-assistant-frontend==20230911.0 diff --git a/requirements_all.txt b/requirements_all.txt index e264f5f52d7..f4315943472 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -958,7 +958,7 @@ ha-philipsjs==3.1.0 habitipy==0.2.0 # homeassistant.components.cloud -hass-nabucasa==0.70.0 +hass-nabucasa==0.71.0 # homeassistant.components.splunk hass-splunk==0.1.1 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 960a25bc321..cdfdefdb989 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -753,7 +753,7 @@ ha-philipsjs==3.1.0 habitipy==0.2.0 # homeassistant.components.cloud -hass-nabucasa==0.70.0 +hass-nabucasa==0.71.0 # homeassistant.components.conversation hassil==1.2.5 diff --git a/tests/components/cloud/test_init.py b/tests/components/cloud/test_init.py index 28b531b608c..e12775d5a4a 100644 --- a/tests/components/cloud/test_init.py +++ b/tests/components/cloud/test_init.py @@ -32,7 +32,6 @@ async def test_constructor_loads_info_from_config(hass: HomeAssistant) -> None: "relayer_server": "test-relayer-server", "accounts_server": "test-acounts-server", "cloudhook_server": "test-cloudhook-server", - "remote_sni_server": "test-remote-sni-server", "alexa_server": "test-alexa-server", "acme_server": "test-acme-server", "remotestate_server": "test-remotestate-server", From 583ea2fed40e0313be50f5bdde685b4472134377 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Tue, 12 Sep 2023 12:05:06 -0400 Subject: [PATCH 34/34] Bumped version to 2023.9.2 --- homeassistant/const.py | 2 +- pyproject.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/const.py b/homeassistant/const.py index cac54748211..08e12ce58ff 100644 --- a/homeassistant/const.py +++ b/homeassistant/const.py @@ -7,7 +7,7 @@ from typing import Final APPLICATION_NAME: Final = "HomeAssistant" MAJOR_VERSION: Final = 2023 MINOR_VERSION: Final = 9 -PATCH_VERSION: Final = "1" +PATCH_VERSION: Final = "2" __short_version__: Final = f"{MAJOR_VERSION}.{MINOR_VERSION}" __version__: Final = f"{__short_version__}.{PATCH_VERSION}" REQUIRED_PYTHON_VER: Final[tuple[int, int, int]] = (3, 11, 0) diff --git a/pyproject.toml b/pyproject.toml index b74e7914fd7..53f9bf38a32 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "homeassistant" -version = "2023.9.1" +version = "2023.9.2" license = {text = "Apache-2.0"} description = "Open-source home automation platform running on Python 3." readme = "README.rst"