From 4e4ac795956b59bcbea91261c722c690b7e0f3ba Mon Sep 17 00:00:00 2001 From: MatthewFlamm <39341281+MatthewFlamm@users.noreply.github.com> Date: Fri, 3 May 2024 07:06:40 -0400 Subject: [PATCH 001/114] Fix nws forecast coordinators and remove legacy forecast handling (#115857) Co-authored-by: J. Nick Koston --- homeassistant/components/nws/__init__.py | 108 +++++++------------- homeassistant/components/nws/manifest.json | 2 +- homeassistant/components/nws/sensor.py | 9 +- homeassistant/components/nws/weather.py | 112 ++++++--------------- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- tests/components/nws/conftest.py | 1 + tests/components/nws/test_weather.py | 80 +-------------- 8 files changed, 82 insertions(+), 234 deletions(-) diff --git a/homeassistant/components/nws/__init__.py b/homeassistant/components/nws/__init__.py index 34157769b97..840d4d917f7 100644 --- a/homeassistant/components/nws/__init__.py +++ b/homeassistant/components/nws/__init__.py @@ -2,21 +2,18 @@ from __future__ import annotations -from collections.abc import Awaitable, Callable from dataclasses import dataclass import datetime import logging -from typing import TYPE_CHECKING -from pynws import SimpleNWS +from pynws import SimpleNWS, call_with_retry from homeassistant.config_entries import ConfigEntry from homeassistant.const import CONF_API_KEY, CONF_LATITUDE, CONF_LONGITUDE, Platform -from homeassistant.core import HomeAssistant, callback +from homeassistant.core import HomeAssistant from homeassistant.helpers import debounce from homeassistant.helpers.aiohttp_client import async_get_clientsession from homeassistant.helpers.device_registry import DeviceEntryType, DeviceInfo -from homeassistant.helpers.event import async_track_point_in_utc_time from homeassistant.helpers.update_coordinator import TimestampDataUpdateCoordinator from homeassistant.util.dt import utcnow @@ -27,8 +24,10 @@ _LOGGER = logging.getLogger(__name__) PLATFORMS = [Platform.SENSOR, Platform.WEATHER] DEFAULT_SCAN_INTERVAL = datetime.timedelta(minutes=10) -FAILED_SCAN_INTERVAL = datetime.timedelta(minutes=1) -DEBOUNCE_TIME = 60 # in seconds +RETRY_INTERVAL = datetime.timedelta(minutes=1) +RETRY_STOP = datetime.timedelta(minutes=10) + +DEBOUNCE_TIME = 10 * 60 # in seconds def base_unique_id(latitude: float, longitude: float) -> str: @@ -41,62 +40,9 @@ class NWSData: """Data for the National Weather Service integration.""" api: SimpleNWS - coordinator_observation: NwsDataUpdateCoordinator - coordinator_forecast: NwsDataUpdateCoordinator - coordinator_forecast_hourly: NwsDataUpdateCoordinator - - -class NwsDataUpdateCoordinator(TimestampDataUpdateCoordinator[None]): # pylint: disable=hass-enforce-coordinator-module - """NWS data update coordinator. - - Implements faster data update intervals for failed updates and exposes a last successful update time. - """ - - def __init__( - self, - hass: HomeAssistant, - logger: logging.Logger, - *, - name: str, - update_interval: datetime.timedelta, - failed_update_interval: datetime.timedelta, - update_method: Callable[[], Awaitable[None]] | None = None, - request_refresh_debouncer: debounce.Debouncer | None = None, - ) -> None: - """Initialize NWS coordinator.""" - super().__init__( - hass, - logger, - name=name, - update_interval=update_interval, - update_method=update_method, - request_refresh_debouncer=request_refresh_debouncer, - ) - self.failed_update_interval = failed_update_interval - - @callback - def _schedule_refresh(self) -> None: - """Schedule a refresh.""" - if self._unsub_refresh: - self._unsub_refresh() - self._unsub_refresh = None - - # We _floor_ utcnow to create a schedule on a rounded second, - # minimizing the time between the point and the real activation. - # That way we obtain a constant update frequency, - # as long as the update process takes less than a second - if self.last_update_success: - if TYPE_CHECKING: - # the base class allows None, but this one doesn't - assert self.update_interval is not None - update_interval = self.update_interval - else: - update_interval = self.failed_update_interval - self._unsub_refresh = async_track_point_in_utc_time( - self.hass, - self._handle_refresh_interval, - utcnow().replace(microsecond=0) + update_interval, - ) + coordinator_observation: TimestampDataUpdateCoordinator[None] + coordinator_forecast: TimestampDataUpdateCoordinator[None] + coordinator_forecast_hourly: TimestampDataUpdateCoordinator[None] async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: @@ -114,39 +60,57 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: async def update_observation() -> None: """Retrieve recent observations.""" - await nws_data.update_observation(start_time=utcnow() - UPDATE_TIME_PERIOD) + await call_with_retry( + nws_data.update_observation, + RETRY_INTERVAL, + RETRY_STOP, + start_time=utcnow() - UPDATE_TIME_PERIOD, + ) - coordinator_observation = NwsDataUpdateCoordinator( + async def update_forecast() -> None: + """Retrieve twice-daily forecsat.""" + await call_with_retry( + nws_data.update_forecast, + RETRY_INTERVAL, + RETRY_STOP, + ) + + async def update_forecast_hourly() -> None: + """Retrieve hourly forecast.""" + await call_with_retry( + nws_data.update_forecast_hourly, + RETRY_INTERVAL, + RETRY_STOP, + ) + + coordinator_observation = TimestampDataUpdateCoordinator( hass, _LOGGER, name=f"NWS observation station {station}", update_method=update_observation, update_interval=DEFAULT_SCAN_INTERVAL, - failed_update_interval=FAILED_SCAN_INTERVAL, request_refresh_debouncer=debounce.Debouncer( hass, _LOGGER, cooldown=DEBOUNCE_TIME, immediate=True ), ) - coordinator_forecast = NwsDataUpdateCoordinator( + coordinator_forecast = TimestampDataUpdateCoordinator( hass, _LOGGER, name=f"NWS forecast station {station}", - update_method=nws_data.update_forecast, + update_method=update_forecast, update_interval=DEFAULT_SCAN_INTERVAL, - failed_update_interval=FAILED_SCAN_INTERVAL, request_refresh_debouncer=debounce.Debouncer( hass, _LOGGER, cooldown=DEBOUNCE_TIME, immediate=True ), ) - coordinator_forecast_hourly = NwsDataUpdateCoordinator( + coordinator_forecast_hourly = TimestampDataUpdateCoordinator( hass, _LOGGER, name=f"NWS forecast hourly station {station}", - update_method=nws_data.update_forecast_hourly, + update_method=update_forecast_hourly, update_interval=DEFAULT_SCAN_INTERVAL, - failed_update_interval=FAILED_SCAN_INTERVAL, request_refresh_debouncer=debounce.Debouncer( hass, _LOGGER, cooldown=DEBOUNCE_TIME, immediate=True ), diff --git a/homeassistant/components/nws/manifest.json b/homeassistant/components/nws/manifest.json index 4006a145db4..f68d76ee95b 100644 --- a/homeassistant/components/nws/manifest.json +++ b/homeassistant/components/nws/manifest.json @@ -7,5 +7,5 @@ "iot_class": "cloud_polling", "loggers": ["metar", "pynws"], "quality_scale": "platinum", - "requirements": ["pynws==1.6.0"] + "requirements": ["pynws[retry]==1.7.0"] } diff --git a/homeassistant/components/nws/sensor.py b/homeassistant/components/nws/sensor.py index 1d8c5ab045e..447c2dc5cf8 100644 --- a/homeassistant/components/nws/sensor.py +++ b/homeassistant/components/nws/sensor.py @@ -25,7 +25,10 @@ from homeassistant.const import ( ) from homeassistant.core import HomeAssistant from homeassistant.helpers.entity_platform import AddEntitiesCallback -from homeassistant.helpers.update_coordinator import CoordinatorEntity +from homeassistant.helpers.update_coordinator import ( + CoordinatorEntity, + TimestampDataUpdateCoordinator, +) from homeassistant.util.dt import utcnow from homeassistant.util.unit_conversion import ( DistanceConverter, @@ -34,7 +37,7 @@ from homeassistant.util.unit_conversion import ( ) from homeassistant.util.unit_system import US_CUSTOMARY_SYSTEM -from . import NWSData, NwsDataUpdateCoordinator, base_unique_id, device_info +from . import NWSData, base_unique_id, device_info from .const import ATTRIBUTION, CONF_STATION, DOMAIN, OBSERVATION_VALID_TIME PARALLEL_UPDATES = 0 @@ -158,7 +161,7 @@ async def async_setup_entry( ) -class NWSSensor(CoordinatorEntity[NwsDataUpdateCoordinator], SensorEntity): +class NWSSensor(CoordinatorEntity[TimestampDataUpdateCoordinator[None]], SensorEntity): """An NWS Sensor Entity.""" entity_description: NWSSensorEntityDescription diff --git a/homeassistant/components/nws/weather.py b/homeassistant/components/nws/weather.py index 89414f5acf1..c017d579c3a 100644 --- a/homeassistant/components/nws/weather.py +++ b/homeassistant/components/nws/weather.py @@ -2,6 +2,7 @@ from __future__ import annotations +from functools import partial from types import MappingProxyType from typing import TYPE_CHECKING, Any, cast @@ -34,7 +35,6 @@ from homeassistant.const import ( from homeassistant.core import HomeAssistant, callback from homeassistant.helpers import entity_registry as er from homeassistant.helpers.entity_platform import AddEntitiesCallback -from homeassistant.util.dt import utcnow from homeassistant.util.unit_conversion import SpeedConverter, TemperatureConverter from . import NWSData, base_unique_id, device_info @@ -46,7 +46,6 @@ from .const import ( DOMAIN, FORECAST_VALID_TIME, HOURLY, - OBSERVATION_VALID_TIME, ) PARALLEL_UPDATES = 0 @@ -140,96 +139,69 @@ class NWSWeather(CoordinatorWeatherEntity): self.nws = nws_data.api latitude = entry_data[CONF_LATITUDE] longitude = entry_data[CONF_LONGITUDE] - self.coordinator_forecast_legacy = nws_data.coordinator_forecast - self.station = self.nws.station - self.observation: dict[str, Any] | None = None - self._forecast_hourly: list[dict[str, Any]] | None = None - self._forecast_legacy: list[dict[str, Any]] | None = None - self._forecast_twice_daily: list[dict[str, Any]] | None = None + self.station = self.nws.station self._attr_unique_id = _calculate_unique_id(entry_data, DAYNIGHT) self._attr_device_info = device_info(latitude, longitude) self._attr_name = self.station async def async_added_to_hass(self) -> None: - """Set up a listener and load data.""" + """When entity is added to hass.""" await super().async_added_to_hass() - self.async_on_remove( - self.coordinator_forecast_legacy.async_add_listener( - self._handle_legacy_forecast_coordinator_update + self.async_on_remove(partial(self._remove_forecast_listener, "daily")) + self.async_on_remove(partial(self._remove_forecast_listener, "hourly")) + self.async_on_remove(partial(self._remove_forecast_listener, "twice_daily")) + + for forecast_type in ("twice_daily", "hourly"): + if (coordinator := self.forecast_coordinators[forecast_type]) is None: + continue + self.unsub_forecast[forecast_type] = coordinator.async_add_listener( + partial(self._handle_forecast_update, forecast_type) ) - ) - # Load initial data from coordinators - self._handle_coordinator_update() - self._handle_hourly_forecast_coordinator_update() - self._handle_twice_daily_forecast_coordinator_update() - self._handle_legacy_forecast_coordinator_update() - - @callback - def _handle_coordinator_update(self) -> None: - """Load data from integration.""" - self.observation = self.nws.observation - self.async_write_ha_state() - - @callback - def _handle_hourly_forecast_coordinator_update(self) -> None: - """Handle updated data from the hourly forecast coordinator.""" - self._forecast_hourly = self.nws.forecast_hourly - - @callback - def _handle_twice_daily_forecast_coordinator_update(self) -> None: - """Handle updated data from the twice daily forecast coordinator.""" - self._forecast_twice_daily = self.nws.forecast - - @callback - def _handle_legacy_forecast_coordinator_update(self) -> None: - """Handle updated data from the legacy forecast coordinator.""" - self._forecast_legacy = self.nws.forecast - self.async_write_ha_state() @property def native_temperature(self) -> float | None: """Return the current temperature.""" - if self.observation: - return self.observation.get("temperature") + if observation := self.nws.observation: + return observation.get("temperature") return None @property def native_pressure(self) -> int | None: """Return the current pressure.""" - if self.observation: - return self.observation.get("seaLevelPressure") + if observation := self.nws.observation: + return observation.get("seaLevelPressure") return None @property def humidity(self) -> float | None: """Return the name of the sensor.""" - if self.observation: - return self.observation.get("relativeHumidity") + if observation := self.nws.observation: + return observation.get("relativeHumidity") return None @property def native_wind_speed(self) -> float | None: """Return the current windspeed.""" - if self.observation: - return self.observation.get("windSpeed") + if observation := self.nws.observation: + return observation.get("windSpeed") return None @property def wind_bearing(self) -> int | None: """Return the current wind bearing (degrees).""" - if self.observation: - return self.observation.get("windDirection") + if observation := self.nws.observation: + return observation.get("windDirection") return None @property def condition(self) -> str | None: """Return current condition.""" weather = None - if self.observation: - weather = self.observation.get("iconWeather") - time = cast(str, self.observation.get("iconTime")) + if observation := self.nws.observation: + weather = observation.get("iconWeather") + time = cast(str, observation.get("iconTime")) if weather: return convert_condition(time, weather) @@ -238,8 +210,8 @@ class NWSWeather(CoordinatorWeatherEntity): @property def native_visibility(self) -> int | None: """Return visibility.""" - if self.observation: - return self.observation.get("visibility") + if observation := self.nws.observation: + return observation.get("visibility") return None def _forecast( @@ -302,33 +274,12 @@ class NWSWeather(CoordinatorWeatherEntity): @callback def _async_forecast_hourly(self) -> list[Forecast] | None: """Return the hourly forecast in native units.""" - return self._forecast(self._forecast_hourly, HOURLY) + return self._forecast(self.nws.forecast_hourly, HOURLY) @callback def _async_forecast_twice_daily(self) -> list[Forecast] | None: """Return the twice daily forecast in native units.""" - return self._forecast(self._forecast_twice_daily, DAYNIGHT) - - @property - def available(self) -> bool: - """Return if state is available.""" - last_success = ( - self.coordinator.last_update_success - and self.coordinator_forecast_legacy.last_update_success - ) - if ( - self.coordinator.last_update_success_time - and self.coordinator_forecast_legacy.last_update_success_time - ): - last_success_time = ( - utcnow() - self.coordinator.last_update_success_time - < OBSERVATION_VALID_TIME - and utcnow() - self.coordinator_forecast_legacy.last_update_success_time - < FORECAST_VALID_TIME - ) - else: - last_success_time = False - return last_success or last_success_time + return self._forecast(self.nws.forecast, DAYNIGHT) async def async_update(self) -> None: """Update the entity. @@ -336,4 +287,7 @@ class NWSWeather(CoordinatorWeatherEntity): Only used by the generic entity update service. """ await self.coordinator.async_request_refresh() - await self.coordinator_forecast_legacy.async_request_refresh() + + for forecast_type in ("twice_daily", "hourly"): + if (coordinator := self.forecast_coordinators[forecast_type]) is not None: + await coordinator.async_request_refresh() diff --git a/requirements_all.txt b/requirements_all.txt index f391511e607..c31b3d92b5e 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2001,7 +2001,7 @@ pynobo==1.8.1 pynuki==1.6.3 # homeassistant.components.nws -pynws==1.6.0 +pynws[retry]==1.7.0 # homeassistant.components.nx584 pynx584==0.5 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 140741518d1..8f1786020fb 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1564,7 +1564,7 @@ pynobo==1.8.1 pynuki==1.6.3 # homeassistant.components.nws -pynws==1.6.0 +pynws[retry]==1.7.0 # homeassistant.components.nx584 pynx584==0.5 diff --git a/tests/components/nws/conftest.py b/tests/components/nws/conftest.py index ac2c281c57b..48401fe87ba 100644 --- a/tests/components/nws/conftest.py +++ b/tests/components/nws/conftest.py @@ -11,6 +11,7 @@ from .const import DEFAULT_FORECAST, DEFAULT_OBSERVATION @pytest.fixture def mock_simple_nws(): """Mock pynws SimpleNWS with default values.""" + with patch("homeassistant.components.nws.SimpleNWS") as mock_nws: instance = mock_nws.return_value instance.set_station = AsyncMock(return_value=None) diff --git a/tests/components/nws/test_weather.py b/tests/components/nws/test_weather.py index ad40b576a8a..87aae18be60 100644 --- a/tests/components/nws/test_weather.py +++ b/tests/components/nws/test_weather.py @@ -13,7 +13,6 @@ from homeassistant.components.weather import ( ATTR_CONDITION_CLEAR_NIGHT, ATTR_CONDITION_SUNNY, DOMAIN as WEATHER_DOMAIN, - LEGACY_SERVICE_GET_FORECAST, SERVICE_GET_FORECASTS, ) from homeassistant.const import STATE_UNAVAILABLE, STATE_UNKNOWN @@ -181,7 +180,7 @@ async def test_entity_refresh(hass: HomeAssistant, mock_simple_nws, no_sensor) - await hass.async_block_till_done() assert instance.update_observation.call_count == 2 assert instance.update_forecast.call_count == 2 - instance.update_forecast_hourly.assert_called_once() + assert instance.update_forecast_hourly.call_count == 2 async def test_error_observation( @@ -189,18 +188,8 @@ async def test_error_observation( ) -> None: """Test error during update observation.""" utc_time = dt_util.utcnow() - with ( - patch("homeassistant.components.nws.utcnow") as mock_utc, - patch("homeassistant.components.nws.weather.utcnow") as mock_utc_weather, - ): - - def increment_time(time): - mock_utc.return_value += time - mock_utc_weather.return_value += time - async_fire_time_changed(hass, mock_utc.return_value) - + with patch("homeassistant.components.nws.utcnow") as mock_utc: mock_utc.return_value = utc_time - mock_utc_weather.return_value = utc_time instance = mock_simple_nws.return_value # first update fails instance.update_observation.side_effect = aiohttp.ClientError @@ -219,68 +208,6 @@ async def test_error_observation( assert state assert state.state == STATE_UNAVAILABLE - # second update happens faster and succeeds - instance.update_observation.side_effect = None - increment_time(timedelta(minutes=1)) - await hass.async_block_till_done() - - assert instance.update_observation.call_count == 2 - - state = hass.states.get("weather.abc") - assert state - assert state.state == ATTR_CONDITION_SUNNY - - # third udate fails, but data is cached - instance.update_observation.side_effect = aiohttp.ClientError - - increment_time(timedelta(minutes=10)) - await hass.async_block_till_done() - - assert instance.update_observation.call_count == 3 - - state = hass.states.get("weather.abc") - assert state - assert state.state == ATTR_CONDITION_SUNNY - - # after 20 minutes data caching expires, data is no longer shown - increment_time(timedelta(minutes=10)) - await hass.async_block_till_done() - - state = hass.states.get("weather.abc") - assert state - assert state.state == STATE_UNAVAILABLE - - -async def test_error_forecast(hass: HomeAssistant, mock_simple_nws, no_sensor) -> None: - """Test error during update forecast.""" - instance = mock_simple_nws.return_value - instance.update_forecast.side_effect = aiohttp.ClientError - - entry = MockConfigEntry( - domain=nws.DOMAIN, - data=NWS_CONFIG, - ) - entry.add_to_hass(hass) - await hass.config_entries.async_setup(entry.entry_id) - await hass.async_block_till_done() - - instance.update_forecast.assert_called_once() - - state = hass.states.get("weather.abc") - assert state - assert state.state == STATE_UNAVAILABLE - - instance.update_forecast.side_effect = None - - async_fire_time_changed(hass, dt_util.utcnow() + timedelta(minutes=1)) - await hass.async_block_till_done() - - assert instance.update_forecast.call_count == 2 - - state = hass.states.get("weather.abc") - assert state - assert state.state == ATTR_CONDITION_SUNNY - async def test_new_config_entry(hass: HomeAssistant, no_sensor) -> None: """Test the expected entities are created.""" @@ -304,7 +231,6 @@ async def test_new_config_entry(hass: HomeAssistant, no_sensor) -> None: ("service"), [ SERVICE_GET_FORECASTS, - LEGACY_SERVICE_GET_FORECAST, ], ) async def test_forecast_service( @@ -355,7 +281,7 @@ async def test_forecast_service( assert instance.update_observation.call_count == 2 assert instance.update_forecast.call_count == 2 - assert instance.update_forecast_hourly.call_count == 1 + assert instance.update_forecast_hourly.call_count == 2 for forecast_type in ("twice_daily", "hourly"): response = await hass.services.async_call( From 624e4a2b483f55d6e4c8a50fe9193506b03ba8a0 Mon Sep 17 00:00:00 2001 From: GraceGRD <123941606+GraceGRD@users.noreply.github.com> Date: Wed, 1 May 2024 23:13:09 +0200 Subject: [PATCH 002/114] Bump opentherm_gw to 2.2.0 (#116527) --- homeassistant/components/opentherm_gw/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/opentherm_gw/manifest.json b/homeassistant/components/opentherm_gw/manifest.json index 50e0eab2643..b6ebef6e83c 100644 --- a/homeassistant/components/opentherm_gw/manifest.json +++ b/homeassistant/components/opentherm_gw/manifest.json @@ -6,5 +6,5 @@ "documentation": "https://www.home-assistant.io/integrations/opentherm_gw", "iot_class": "local_push", "loggers": ["pyotgw"], - "requirements": ["pyotgw==2.1.3"] + "requirements": ["pyotgw==2.2.0"] } diff --git a/requirements_all.txt b/requirements_all.txt index c31b3d92b5e..61789f0369a 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2031,7 +2031,7 @@ pyoppleio-legacy==1.0.8 pyosoenergyapi==1.1.3 # homeassistant.components.opentherm_gw -pyotgw==2.1.3 +pyotgw==2.2.0 # homeassistant.auth.mfa_modules.notify # homeassistant.auth.mfa_modules.totp diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 8f1786020fb..8366bc7fb6d 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1588,7 +1588,7 @@ pyopnsense==0.4.0 pyosoenergyapi==1.1.3 # homeassistant.components.opentherm_gw -pyotgw==2.1.3 +pyotgw==2.2.0 # homeassistant.auth.mfa_modules.notify # homeassistant.auth.mfa_modules.totp From 49de59432efad76608571d44bec57eb551255fc3 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Wed, 1 May 2024 19:23:43 -0500 Subject: [PATCH 003/114] Add a lock to homekit_controller platform loads (#116539) --- .../homekit_controller/connection.py | 32 +++++++++++-------- 1 file changed, 18 insertions(+), 14 deletions(-) diff --git a/homeassistant/components/homekit_controller/connection.py b/homeassistant/components/homekit_controller/connection.py index 78beb7bfffa..78190634aff 100644 --- a/homeassistant/components/homekit_controller/connection.py +++ b/homeassistant/components/homekit_controller/connection.py @@ -153,6 +153,7 @@ class HKDevice: self._subscriptions: dict[tuple[int, int], set[CALLBACK_TYPE]] = {} self._pending_subscribes: set[tuple[int, int]] = set() self._subscribe_timer: CALLBACK_TYPE | None = None + self._load_platforms_lock = asyncio.Lock() @property def entity_map(self) -> Accessories: @@ -327,7 +328,8 @@ class HKDevice: ) # BLE devices always get an RSSI sensor as well if "sensor" not in self.platforms: - await self._async_load_platforms({"sensor"}) + async with self._load_platforms_lock: + await self._async_load_platforms({"sensor"}) @callback def _async_start_polling(self) -> None: @@ -804,6 +806,7 @@ class HKDevice: async def _async_load_platforms(self, platforms: set[str]) -> None: """Load a group of platforms.""" + assert self._load_platforms_lock.locked(), "Must be called with lock held" if not (to_load := platforms - self.platforms): return self.platforms.update(to_load) @@ -813,22 +816,23 @@ class HKDevice: async def async_load_platforms(self) -> None: """Load any platforms needed by this HomeKit device.""" - to_load: set[str] = set() - for accessory in self.entity_map.accessories: - for service in accessory.services: - if service.type in HOMEKIT_ACCESSORY_DISPATCH: - platform = HOMEKIT_ACCESSORY_DISPATCH[service.type] - if platform not in self.platforms: - to_load.add(platform) - - for char in service.characteristics: - if char.type in CHARACTERISTIC_PLATFORMS: - platform = CHARACTERISTIC_PLATFORMS[char.type] + async with self._load_platforms_lock: + to_load: set[str] = set() + for accessory in self.entity_map.accessories: + for service in accessory.services: + if service.type in HOMEKIT_ACCESSORY_DISPATCH: + platform = HOMEKIT_ACCESSORY_DISPATCH[service.type] if platform not in self.platforms: to_load.add(platform) - if to_load: - await self._async_load_platforms(to_load) + for char in service.characteristics: + if char.type in CHARACTERISTIC_PLATFORMS: + platform = CHARACTERISTIC_PLATFORMS[char.type] + if platform not in self.platforms: + to_load.add(platform) + + if to_load: + await self._async_load_platforms(to_load) @callback def async_update_available_state(self, *_: Any) -> None: From ea6a9b83162eb8448056ffc1522ae67ad7cf3f2f Mon Sep 17 00:00:00 2001 From: Jan Bouwhuis Date: Wed, 1 May 2024 21:19:55 +0200 Subject: [PATCH 004/114] Fix MQTT discovery cooldown too short with large setup (#116550) * Fix MQTT discovery cooldown too short with large setup * Set to 5 sec * Only change the discovery cooldown * Fire immediatly when teh debouncing period is over --- homeassistant/components/mqtt/client.py | 16 ++++++++++++---- homeassistant/components/mqtt/discovery.py | 4 ++-- 2 files changed, 14 insertions(+), 6 deletions(-) diff --git a/homeassistant/components/mqtt/client.py b/homeassistant/components/mqtt/client.py index d79492ccb27..4fa9f4a1d49 100644 --- a/homeassistant/components/mqtt/client.py +++ b/homeassistant/components/mqtt/client.py @@ -83,7 +83,7 @@ if TYPE_CHECKING: _LOGGER = logging.getLogger(__name__) -DISCOVERY_COOLDOWN = 2 +DISCOVERY_COOLDOWN = 5 INITIAL_SUBSCRIBE_COOLDOWN = 1.0 SUBSCRIBE_COOLDOWN = 0.1 UNSUBSCRIBE_COOLDOWN = 0.1 @@ -349,6 +349,12 @@ class EnsureJobAfterCooldown: self._task = create_eager_task(self._async_job()) self._task.add_done_callback(self._async_task_done) + async def async_fire(self) -> None: + """Execute the job immediately.""" + if self._task: + await self._task + self._async_execute() + @callback def _async_cancel_timer(self) -> None: """Cancel any pending task.""" @@ -846,7 +852,7 @@ class MQTT: for topic, qos in subscriptions.items(): _LOGGER.debug("Subscribing to %s, mid: %s, qos: %s", topic, mid, qos) - self._last_subscribe = time.time() + self._last_subscribe = time.monotonic() if result == 0: await self._wait_for_mid(mid) @@ -876,6 +882,8 @@ class MQTT: await self._ha_started.wait() # Wait for Home Assistant to start await self._discovery_cooldown() # Wait for MQTT discovery to cool down # Update subscribe cooldown period to a shorter time + # and make sure we flush the debouncer + await self._subscribe_debouncer.async_fire() self._subscribe_debouncer.set_timeout(SUBSCRIBE_COOLDOWN) await self.async_publish( topic=birth_message.topic, @@ -1121,7 +1129,7 @@ class MQTT: async def _discovery_cooldown(self) -> None: """Wait until all discovery and subscriptions are processed.""" - now = time.time() + now = time.monotonic() # Reset discovery and subscribe cooldowns self._mqtt_data.last_discovery = now self._last_subscribe = now @@ -1133,7 +1141,7 @@ class MQTT: ) while now < wait_until: await asyncio.sleep(wait_until - now) - now = time.time() + now = time.monotonic() last_discovery = self._mqtt_data.last_discovery last_subscribe = ( now if self._pending_subscriptions else self._last_subscribe diff --git a/homeassistant/components/mqtt/discovery.py b/homeassistant/components/mqtt/discovery.py index e330cd9b44b..08d86c1a1a4 100644 --- a/homeassistant/components/mqtt/discovery.py +++ b/homeassistant/components/mqtt/discovery.py @@ -177,7 +177,7 @@ async def async_start( # noqa: C901 @callback def async_discovery_message_received(msg: ReceiveMessage) -> None: # noqa: C901 """Process the received message.""" - mqtt_data.last_discovery = time.time() + mqtt_data.last_discovery = time.monotonic() payload = msg.payload topic = msg.topic topic_trimmed = topic.replace(f"{discovery_topic}/", "", 1) @@ -370,7 +370,7 @@ async def async_start( # noqa: C901 ) ) - mqtt_data.last_discovery = time.time() + mqtt_data.last_discovery = time.monotonic() mqtt_integrations = await async_get_mqtt(hass) for integration, topics in mqtt_integrations.items(): From 5da6f83d10afbce4e1ff899223351bd05c46c289 Mon Sep 17 00:00:00 2001 From: Glenn Waters Date: Wed, 1 May 2024 16:32:56 -0400 Subject: [PATCH 005/114] Bump upb_lib to 0.5.6 (#116558) --- homeassistant/components/upb/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/upb/manifest.json b/homeassistant/components/upb/manifest.json index 240660ac89f..a5e32dd298e 100644 --- a/homeassistant/components/upb/manifest.json +++ b/homeassistant/components/upb/manifest.json @@ -6,5 +6,5 @@ "documentation": "https://www.home-assistant.io/integrations/upb", "iot_class": "local_push", "loggers": ["upb_lib"], - "requirements": ["upb-lib==0.5.4"] + "requirements": ["upb-lib==0.5.6"] } diff --git a/requirements_all.txt b/requirements_all.txt index 61789f0369a..452de53e4e4 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2779,7 +2779,7 @@ unifiled==0.11 universal-silabs-flasher==0.0.18 # homeassistant.components.upb -upb-lib==0.5.4 +upb-lib==0.5.6 # homeassistant.components.upcloud upcloud-api==2.0.0 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 8366bc7fb6d..4f21b948bda 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -2147,7 +2147,7 @@ unifi-discovery==1.1.8 universal-silabs-flasher==0.0.18 # homeassistant.components.upb -upb-lib==0.5.4 +upb-lib==0.5.6 # homeassistant.components.upcloud upcloud-api==2.0.0 From 65839067e33bc95fdc2d577b2fba2f0b01bdada5 Mon Sep 17 00:00:00 2001 From: Glenn Waters Date: Wed, 1 May 2024 20:51:04 -0400 Subject: [PATCH 006/114] Bump elkm1_lib to 2.2.7 (#116564) Co-authored-by: J. Nick Koston --- homeassistant/components/elkm1/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/elkm1/manifest.json b/homeassistant/components/elkm1/manifest.json index 3ec5be46d41..5edab8463f7 100644 --- a/homeassistant/components/elkm1/manifest.json +++ b/homeassistant/components/elkm1/manifest.json @@ -15,5 +15,5 @@ "documentation": "https://www.home-assistant.io/integrations/elkm1", "iot_class": "local_push", "loggers": ["elkm1_lib"], - "requirements": ["elkm1-lib==2.2.6"] + "requirements": ["elkm1-lib==2.2.7"] } diff --git a/requirements_all.txt b/requirements_all.txt index 452de53e4e4..b72209ee772 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -777,7 +777,7 @@ elgato==5.1.2 eliqonline==1.2.2 # homeassistant.components.elkm1 -elkm1-lib==2.2.6 +elkm1-lib==2.2.7 # homeassistant.components.elmax elmax-api==0.0.4 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 4f21b948bda..e49c42a4594 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -637,7 +637,7 @@ electrickiwi-api==0.8.5 elgato==5.1.2 # homeassistant.components.elkm1 -elkm1-lib==2.2.6 +elkm1-lib==2.2.7 # homeassistant.components.elmax elmax-api==0.0.4 From 523de94184056760b1c69d422354a4b6c04e56a1 Mon Sep 17 00:00:00 2001 From: Marcel van der Veldt Date: Fri, 3 May 2024 13:27:01 +0200 Subject: [PATCH 007/114] Fix Matter startup when Matter bridge is present (#116569) --- homeassistant/components/matter/light.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/homeassistant/components/matter/light.py b/homeassistant/components/matter/light.py index 9d80ebc38f6..da72798dda1 100644 --- a/homeassistant/components/matter/light.py +++ b/homeassistant/components/matter/light.py @@ -398,6 +398,8 @@ class MatterLight(MatterEntity, LightEntity): def _check_transition_blocklist(self) -> None: """Check if this device is reported to have non working transitions.""" device_info = self._endpoint.device_info + if isinstance(device_info, clusters.BridgedDeviceBasicInformation): + return if ( device_info.vendorID, device_info.productID, From 99ab8d29561e51d30182e66933ba33522ad9d1ec Mon Sep 17 00:00:00 2001 From: Tomasz Date: Thu, 2 May 2024 00:21:40 +0200 Subject: [PATCH 008/114] Bump sanix to 1.0.6 (#116570) dependency version bump --- homeassistant/components/sanix/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/sanix/manifest.json b/homeassistant/components/sanix/manifest.json index 4e1c6d56add..facf8f7a4dd 100644 --- a/homeassistant/components/sanix/manifest.json +++ b/homeassistant/components/sanix/manifest.json @@ -5,5 +5,5 @@ "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/sanix", "iot_class": "cloud_polling", - "requirements": ["sanix==1.0.5"] + "requirements": ["sanix==1.0.6"] } diff --git a/requirements_all.txt b/requirements_all.txt index b72209ee772..7c90437ba27 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2499,7 +2499,7 @@ samsungctl[websocket]==0.7.1 samsungtvws[async,encrypted]==2.6.0 # homeassistant.components.sanix -sanix==1.0.5 +sanix==1.0.6 # homeassistant.components.satel_integra satel-integra==0.3.7 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index e49c42a4594..05106fefc6d 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1939,7 +1939,7 @@ samsungctl[websocket]==0.7.1 samsungtvws[async,encrypted]==2.6.0 # homeassistant.components.sanix -sanix==1.0.5 +sanix==1.0.6 # homeassistant.components.screenlogic screenlogicpy==0.10.0 From fabbe2f28fd18cfb35fc3038481b8eacbec2acda Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A5le=20Stor=C3=B8=20Hauknes?= Date: Thu, 2 May 2024 02:19:40 +0200 Subject: [PATCH 009/114] Fix Airthings BLE model names (#116579) --- homeassistant/components/airthings_ble/sensor.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/airthings_ble/sensor.py b/homeassistant/components/airthings_ble/sensor.py index 8031b802eae..3b012ed7316 100644 --- a/homeassistant/components/airthings_ble/sensor.py +++ b/homeassistant/components/airthings_ble/sensor.py @@ -225,7 +225,7 @@ class AirthingsSensor( manufacturer=airthings_device.manufacturer, hw_version=airthings_device.hw_version, sw_version=airthings_device.sw_version, - model=airthings_device.model.name, + model=airthings_device.model.product_name, ) @property From 0e488ef50512a1d8e6b02182d9017ecc9d25561a Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Thu, 2 May 2024 15:57:47 +0200 Subject: [PATCH 010/114] Improve coordinator in Ondilo ico (#116596) * Improve coordinator in Ondilo ico * Improve coordinator in Ondilo ico --- .coveragerc | 1 + .../components/ondilo_ico/__init__.py | 10 ++- .../components/ondilo_ico/coordinator.py | 37 ++++++++++ homeassistant/components/ondilo_ico/sensor.py | 68 +++---------------- 4 files changed, 57 insertions(+), 59 deletions(-) create mode 100644 homeassistant/components/ondilo_ico/coordinator.py diff --git a/.coveragerc b/.coveragerc index 1ccb9e461df..10dedd43e81 100644 --- a/.coveragerc +++ b/.coveragerc @@ -939,6 +939,7 @@ omit = homeassistant/components/omnilogic/switch.py homeassistant/components/ondilo_ico/__init__.py homeassistant/components/ondilo_ico/api.py + homeassistant/components/ondilo_ico/coordinator.py homeassistant/components/ondilo_ico/sensor.py homeassistant/components/onkyo/media_player.py homeassistant/components/onvif/__init__.py diff --git a/homeassistant/components/ondilo_ico/__init__.py b/homeassistant/components/ondilo_ico/__init__.py index 5dccca54772..aa541c470f1 100644 --- a/homeassistant/components/ondilo_ico/__init__.py +++ b/homeassistant/components/ondilo_ico/__init__.py @@ -7,6 +7,7 @@ from homeassistant.helpers import config_entry_oauth2_flow from . import api, config_flow from .const import DOMAIN +from .coordinator import OndiloIcoCoordinator from .oauth_impl import OndiloOauth2Implementation PLATFORMS = [Platform.SENSOR] @@ -26,8 +27,13 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: ) ) - hass.data.setdefault(DOMAIN, {}) - hass.data[DOMAIN][entry.entry_id] = api.OndiloClient(hass, entry, implementation) + coordinator = OndiloIcoCoordinator( + hass, api.OndiloClient(hass, entry, implementation) + ) + + await coordinator.async_config_entry_first_refresh() + + hass.data.setdefault(DOMAIN, {})[entry.entry_id] = coordinator await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS) diff --git a/homeassistant/components/ondilo_ico/coordinator.py b/homeassistant/components/ondilo_ico/coordinator.py new file mode 100644 index 00000000000..d3e9b4a4e11 --- /dev/null +++ b/homeassistant/components/ondilo_ico/coordinator.py @@ -0,0 +1,37 @@ +"""Define an object to coordinate fetching Ondilo ICO data.""" + +from datetime import timedelta +import logging +from typing import Any + +from ondilo import OndiloError + +from homeassistant.core import HomeAssistant +from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed + +from . import DOMAIN +from .api import OndiloClient + +_LOGGER = logging.getLogger(__name__) + + +class OndiloIcoCoordinator(DataUpdateCoordinator[list[dict[str, Any]]]): + """Class to manage fetching Ondilo ICO data from API.""" + + def __init__(self, hass: HomeAssistant, api: OndiloClient) -> None: + """Initialize.""" + super().__init__( + hass, + logger=_LOGGER, + name=DOMAIN, + update_interval=timedelta(minutes=5), + ) + self.api = api + + async def _async_update_data(self) -> list[dict[str, Any]]: + """Fetch data from API endpoint.""" + try: + return await self.hass.async_add_executor_job(self.api.get_all_pools_data) + + except OndiloError as err: + raise UpdateFailed(f"Error communicating with API: {err}") from err diff --git a/homeassistant/components/ondilo_ico/sensor.py b/homeassistant/components/ondilo_ico/sensor.py index 17569fd784f..5f21fb6a909 100644 --- a/homeassistant/components/ondilo_ico/sensor.py +++ b/homeassistant/components/ondilo_ico/sensor.py @@ -2,12 +2,6 @@ from __future__ import annotations -from datetime import timedelta -import logging -from typing import Any - -from ondilo import OndiloError - from homeassistant.components.sensor import ( SensorDeviceClass, SensorEntity, @@ -24,14 +18,10 @@ from homeassistant.const import ( from homeassistant.core import HomeAssistant from homeassistant.helpers.device_registry import DeviceInfo from homeassistant.helpers.entity_platform import AddEntitiesCallback -from homeassistant.helpers.update_coordinator import ( - CoordinatorEntity, - DataUpdateCoordinator, - UpdateFailed, -) +from homeassistant.helpers.update_coordinator import CoordinatorEntity -from .api import OndiloClient from .const import DOMAIN +from .coordinator import OndiloIcoCoordinator SENSOR_TYPES: tuple[SensorEntityDescription, ...] = ( SensorEntityDescription( @@ -78,66 +68,30 @@ SENSOR_TYPES: tuple[SensorEntityDescription, ...] = ( ) -SCAN_INTERVAL = timedelta(minutes=5) -_LOGGER = logging.getLogger(__name__) - - async def async_setup_entry( hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback ) -> None: """Set up the Ondilo ICO sensors.""" - api: OndiloClient = hass.data[DOMAIN][entry.entry_id] + coordinator: OndiloIcoCoordinator = hass.data[DOMAIN][entry.entry_id] - async def async_update_data() -> list[dict[str, Any]]: - """Fetch data from API endpoint. - - This is the place to pre-process the data to lookup tables - so entities can quickly look up their data. - """ - try: - return await hass.async_add_executor_job(api.get_all_pools_data) - - except OndiloError as err: - raise UpdateFailed(f"Error communicating with API: {err}") from err - - coordinator = DataUpdateCoordinator( - hass, - _LOGGER, - # Name of the data. For logging purposes. - name="sensor", - update_method=async_update_data, - # Polling interval. Will only be polled if there are subscribers. - update_interval=SCAN_INTERVAL, + async_add_entities( + OndiloICO(coordinator, poolidx, description) + for poolidx, pool in enumerate(coordinator.data) + for sensor in pool["sensors"] + for description in SENSOR_TYPES + if description.key == sensor["data_type"] ) - # Fetch initial data so we have data when entities subscribe - await coordinator.async_refresh() - entities = [] - for poolidx, pool in enumerate(coordinator.data): - entities.extend( - [ - OndiloICO(coordinator, poolidx, description) - for sensor in pool["sensors"] - for description in SENSOR_TYPES - if description.key == sensor["data_type"] - ] - ) - - async_add_entities(entities) - - -class OndiloICO( - CoordinatorEntity[DataUpdateCoordinator[list[dict[str, Any]]]], SensorEntity -): +class OndiloICO(CoordinatorEntity[OndiloIcoCoordinator], SensorEntity): """Representation of a Sensor.""" _attr_has_entity_name = True def __init__( self, - coordinator: DataUpdateCoordinator[list[dict[str, Any]]], + coordinator: OndiloIcoCoordinator, poolidx: int, description: SensorEntityDescription, ) -> None: From 575a3da772d7c4bec39dbdb519554372c00890cf Mon Sep 17 00:00:00 2001 From: Matthias Alphart Date: Thu, 2 May 2024 11:44:32 +0200 Subject: [PATCH 011/114] Fix inheritance order for KNX notify (#116600) --- homeassistant/components/knx/notify.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/knx/notify.py b/homeassistant/components/knx/notify.py index e208e4fd646..f206ee62ece 100644 --- a/homeassistant/components/knx/notify.py +++ b/homeassistant/components/knx/notify.py @@ -97,7 +97,7 @@ def _create_notification_instance(xknx: XKNX, config: ConfigType) -> XknxNotific ) -class KNXNotify(NotifyEntity, KnxEntity): +class KNXNotify(KnxEntity, NotifyEntity): """Representation of a KNX notification entity.""" _device: XknxNotification From 7c1502fa059610290d4cb0e7391bd36a5bafcc46 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A5le=20Stor=C3=B8=20Hauknes?= Date: Thu, 2 May 2024 16:37:02 +0200 Subject: [PATCH 012/114] Bump Airthings BLE to 0.8.0 (#116616) Co-authored-by: J. Nick Koston --- homeassistant/components/airthings_ble/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/airthings_ble/manifest.json b/homeassistant/components/airthings_ble/manifest.json index 3f7bd02a33e..d93e3a0b8cb 100644 --- a/homeassistant/components/airthings_ble/manifest.json +++ b/homeassistant/components/airthings_ble/manifest.json @@ -24,5 +24,5 @@ "dependencies": ["bluetooth_adapters"], "documentation": "https://www.home-assistant.io/integrations/airthings_ble", "iot_class": "local_polling", - "requirements": ["airthings-ble==0.7.1"] + "requirements": ["airthings-ble==0.8.0"] } diff --git a/requirements_all.txt b/requirements_all.txt index 7c90437ba27..620a1aa2a15 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -413,7 +413,7 @@ aioymaps==1.2.2 airly==1.1.0 # homeassistant.components.airthings_ble -airthings-ble==0.7.1 +airthings-ble==0.8.0 # homeassistant.components.airthings airthings-cloud==0.2.0 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 05106fefc6d..8adb04408e1 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -386,7 +386,7 @@ aioymaps==1.2.2 airly==1.1.0 # homeassistant.components.airthings_ble -airthings-ble==0.7.1 +airthings-ble==0.8.0 # homeassistant.components.airthings airthings-cloud==0.2.0 From 8193b82f4a37613e9ccfce62b616c7e27eee471e Mon Sep 17 00:00:00 2001 From: Kevin Stillhammer Date: Thu, 2 May 2024 16:54:06 +0200 Subject: [PATCH 013/114] Bump pywaze to 1.0.1 (#116621) Co-authored-by: J. Nick Koston --- 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 4fc08cf983d..ce7c9105781 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==1.0.0"] + "requirements": ["pywaze==1.0.1"] } diff --git a/requirements_all.txt b/requirements_all.txt index 620a1aa2a15..c5710500ad5 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2370,7 +2370,7 @@ pyvlx==0.2.21 pyvolumio==0.1.5 # homeassistant.components.waze_travel_time -pywaze==1.0.0 +pywaze==1.0.1 # homeassistant.components.weatherflow pyweatherflowudp==1.4.5 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 8adb04408e1..84f034d453f 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1843,7 +1843,7 @@ pyvlx==0.2.21 pyvolumio==0.1.5 # homeassistant.components.waze_travel_time -pywaze==1.0.0 +pywaze==1.0.1 # homeassistant.components.weatherflow pyweatherflowudp==1.4.5 From c338f1b964e8f5727f284b9d51fddeaa7e42a3cc Mon Sep 17 00:00:00 2001 From: Marc Mueller <30130371+cdce8p@users.noreply.github.com> Date: Thu, 2 May 2024 16:12:26 +0200 Subject: [PATCH 014/114] Add constraint for tuf (#116627) --- homeassistant/package_constraints.txt | 5 +++++ script/gen_requirements_all.py | 5 +++++ 2 files changed, 10 insertions(+) diff --git a/homeassistant/package_constraints.txt b/homeassistant/package_constraints.txt index b1c0391022a..2c038ed3927 100644 --- a/homeassistant/package_constraints.txt +++ b/homeassistant/package_constraints.txt @@ -192,3 +192,8 @@ pycountry>=23.12.11 # scapy<2.5.0 will not work with python3.12 scapy>=2.5.0 + +# tuf isn't updated to deal with breaking changes in securesystemslib==1.0. +# Only tuf>=4 includes a constraint to <1.0. +# https://github.com/theupdateframework/python-tuf/releases/tag/v4.0.0 +tuf>=4.0.0 diff --git a/script/gen_requirements_all.py b/script/gen_requirements_all.py index a5db9997d9d..b611b050c7d 100755 --- a/script/gen_requirements_all.py +++ b/script/gen_requirements_all.py @@ -214,6 +214,11 @@ pycountry>=23.12.11 # scapy<2.5.0 will not work with python3.12 scapy>=2.5.0 + +# tuf isn't updated to deal with breaking changes in securesystemslib==1.0. +# Only tuf>=4 includes a constraint to <1.0. +# https://github.com/theupdateframework/python-tuf/releases/tag/v4.0.0 +tuf>=4.0.0 """ GENERATED_MESSAGE = ( From 6be25c784d5141acd67256708e9149ff724f162c Mon Sep 17 00:00:00 2001 From: Robert Svensson Date: Thu, 2 May 2024 19:53:17 +0200 Subject: [PATCH 015/114] Bump aiounifi to v77 (#116639) --- homeassistant/components/unifi/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/unifi/manifest.json b/homeassistant/components/unifi/manifest.json index 982d654c8fe..504c2f505a7 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==76"], + "requirements": ["aiounifi==77"], "ssdp": [ { "manufacturer": "Ubiquiti Networks", diff --git a/requirements_all.txt b/requirements_all.txt index c5710500ad5..c54606830dd 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -386,7 +386,7 @@ aiotankerkoenig==0.4.1 aiotractive==0.5.6 # homeassistant.components.unifi -aiounifi==76 +aiounifi==77 # homeassistant.components.vlc_telnet aiovlc==0.1.0 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 84f034d453f..23730ab802e 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -359,7 +359,7 @@ aiotankerkoenig==0.4.1 aiotractive==0.5.6 # homeassistant.components.unifi -aiounifi==76 +aiounifi==77 # homeassistant.components.vlc_telnet aiovlc==0.1.0 From c36fd5550b8cecf9e405c3de22253cebd9f8b37f Mon Sep 17 00:00:00 2001 From: Galorhallen <12990764+Galorhallen@users.noreply.github.com> Date: Fri, 3 May 2024 13:07:45 +0200 Subject: [PATCH 016/114] Bump govee-light-local library and fix wrong information for Govee lights (#116651) --- homeassistant/components/govee_light_local/light.py | 4 ++-- homeassistant/components/govee_light_local/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/homeassistant/components/govee_light_local/light.py b/homeassistant/components/govee_light_local/light.py index 836f48d2ea9..60bf07e8e19 100644 --- a/homeassistant/components/govee_light_local/light.py +++ b/homeassistant/components/govee_light_local/light.py @@ -17,7 +17,7 @@ from homeassistant.components.light import ( ) from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant, callback -from homeassistant.helpers.device_registry import CONNECTION_NETWORK_MAC, DeviceInfo +from homeassistant.helpers.device_registry import DeviceInfo from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.update_coordinator import CoordinatorEntity @@ -94,7 +94,7 @@ class GoveeLight(CoordinatorEntity[GoveeLocalApiCoordinator], LightEntity): name=device.sku, manufacturer=MANUFACTURER, model=device.sku, - connections={(CONNECTION_NETWORK_MAC, device.fingerprint)}, + serial_number=device.fingerprint, ) @property diff --git a/homeassistant/components/govee_light_local/manifest.json b/homeassistant/components/govee_light_local/manifest.json index cb7955f5407..df72a082190 100644 --- a/homeassistant/components/govee_light_local/manifest.json +++ b/homeassistant/components/govee_light_local/manifest.json @@ -6,5 +6,5 @@ "dependencies": ["network"], "documentation": "https://www.home-assistant.io/integrations/govee_light_local", "iot_class": "local_push", - "requirements": ["govee-local-api==1.4.4"] + "requirements": ["govee-local-api==1.4.5"] } diff --git a/requirements_all.txt b/requirements_all.txt index c54606830dd..362b8389df2 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -983,7 +983,7 @@ gotailwind==0.2.2 govee-ble==0.31.2 # homeassistant.components.govee_light_local -govee-local-api==1.4.4 +govee-local-api==1.4.5 # homeassistant.components.remote_rpi_gpio gpiozero==1.6.2 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 23730ab802e..5672dd88dab 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -806,7 +806,7 @@ gotailwind==0.2.2 govee-ble==0.31.2 # homeassistant.components.govee_light_local -govee-local-api==1.4.4 +govee-local-api==1.4.5 # homeassistant.components.gpsd gps3==0.33.3 From abeb65e43d54136534c8c17d6eec14a07f89ee27 Mon Sep 17 00:00:00 2001 From: puddly <32534428+puddly@users.noreply.github.com> Date: Thu, 2 May 2024 20:31:28 -0400 Subject: [PATCH 017/114] Bump ZHA dependency bellows to 0.38.4 (#116660) Bump ZHA dependencies Co-authored-by: TheJulianJES --- homeassistant/components/zha/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/zha/manifest.json b/homeassistant/components/zha/manifest.json index b1511b2f5bb..7a407a2eb33 100644 --- a/homeassistant/components/zha/manifest.json +++ b/homeassistant/components/zha/manifest.json @@ -21,7 +21,7 @@ "universal_silabs_flasher" ], "requirements": [ - "bellows==0.38.3", + "bellows==0.38.4", "pyserial==3.5", "pyserial-asyncio==0.6", "zha-quirks==0.0.115", diff --git a/requirements_all.txt b/requirements_all.txt index 362b8389df2..8f15e0be921 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -541,7 +541,7 @@ beautifulsoup4==4.12.3 # beewi-smartclim==0.0.10 # homeassistant.components.zha -bellows==0.38.3 +bellows==0.38.4 # homeassistant.components.bmw_connected_drive bimmer-connected[china]==0.15.2 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 5672dd88dab..a20a6a92811 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -466,7 +466,7 @@ base36==0.1.1 beautifulsoup4==4.12.3 # homeassistant.components.zha -bellows==0.38.3 +bellows==0.38.4 # homeassistant.components.bmw_connected_drive bimmer-connected[china]==0.15.2 From ac302f38b1926c404dc7c68039c27faee8f57422 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Thu, 2 May 2024 18:15:56 -0500 Subject: [PATCH 018/114] Bump habluetooth to 2.8.1 (#116661) --- 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 4bb84ab6dc3..754e8faf996 100644 --- a/homeassistant/components/bluetooth/manifest.json +++ b/homeassistant/components/bluetooth/manifest.json @@ -20,6 +20,6 @@ "bluetooth-auto-recovery==1.4.2", "bluetooth-data-tools==1.19.0", "dbus-fast==2.21.1", - "habluetooth==2.8.0" + "habluetooth==2.8.1" ] } diff --git a/homeassistant/package_constraints.txt b/homeassistant/package_constraints.txt index 2c038ed3927..800e4d90009 100644 --- a/homeassistant/package_constraints.txt +++ b/homeassistant/package_constraints.txt @@ -28,7 +28,7 @@ dbus-fast==2.21.1 fnv-hash-fast==0.5.0 ha-av==10.1.1 ha-ffmpeg==3.2.0 -habluetooth==2.8.0 +habluetooth==2.8.1 hass-nabucasa==0.78.0 hassil==1.6.1 home-assistant-bluetooth==1.12.0 diff --git a/requirements_all.txt b/requirements_all.txt index 8f15e0be921..419c713347b 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1035,7 +1035,7 @@ ha-philipsjs==3.1.1 habitipy==0.2.0 # homeassistant.components.bluetooth -habluetooth==2.8.0 +habluetooth==2.8.1 # homeassistant.components.cloud hass-nabucasa==0.78.0 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index a20a6a92811..31832687250 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -849,7 +849,7 @@ ha-philipsjs==3.1.1 habitipy==0.2.0 # homeassistant.components.bluetooth -habluetooth==2.8.0 +habluetooth==2.8.1 # homeassistant.components.cloud hass-nabucasa==0.78.0 From 66bb3ecac905ae8ab0bd5265d272e9d714ae2b94 Mon Sep 17 00:00:00 2001 From: Glenn Waters Date: Thu, 2 May 2024 19:17:41 -0400 Subject: [PATCH 019/114] Bump env_canada lib to 0.6.2 (#116662) --- homeassistant/components/environment_canada/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/environment_canada/manifest.json b/homeassistant/components/environment_canada/manifest.json index d0c34b0cf9a..f29c8177dfd 100644 --- a/homeassistant/components/environment_canada/manifest.json +++ b/homeassistant/components/environment_canada/manifest.json @@ -6,5 +6,5 @@ "documentation": "https://www.home-assistant.io/integrations/environment_canada", "iot_class": "cloud_polling", "loggers": ["env_canada"], - "requirements": ["env-canada==0.6.0"] + "requirements": ["env-canada==0.6.2"] } diff --git a/requirements_all.txt b/requirements_all.txt index 419c713347b..6f741478f20 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -804,7 +804,7 @@ enocean==0.50 enturclient==0.2.4 # homeassistant.components.environment_canada -env-canada==0.6.0 +env-canada==0.6.2 # homeassistant.components.season ephem==4.1.5 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 31832687250..14951b8a6ec 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -658,7 +658,7 @@ energyzero==2.1.0 enocean==0.50 # homeassistant.components.environment_canada -env-canada==0.6.0 +env-canada==0.6.2 # homeassistant.components.season ephem==4.1.5 From 7a56ba1506fd4fec10d7ee6f02a9e9860896076b Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Fri, 3 May 2024 05:17:01 -0500 Subject: [PATCH 020/114] Block dreame_vacuum versions older than 1.0.4 (#116673) --- homeassistant/loader.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/homeassistant/loader.py b/homeassistant/loader.py index 1a72c8eb351..89c3442be6a 100644 --- a/homeassistant/loader.py +++ b/homeassistant/loader.py @@ -90,7 +90,12 @@ class BlockedIntegration: BLOCKED_CUSTOM_INTEGRATIONS: dict[str, BlockedIntegration] = { # Added in 2024.3.0 because of https://github.com/home-assistant/core/issues/112464 - "start_time": BlockedIntegration(AwesomeVersion("1.1.7"), "breaks Home Assistant") + "start_time": BlockedIntegration(AwesomeVersion("1.1.7"), "breaks Home Assistant"), + # Added in 2024.5.1 because of + # https://community.home-assistant.io/t/psa-2024-5-upgrade-failure-and-dreame-vacuum-custom-integration/724612 + "dreame_vacuum": BlockedIntegration( + AwesomeVersion("1.0.4"), "crashes Home Assistant" + ), } DATA_COMPONENTS = "components" From a4f9a645889b628c82872102dc4706071d97dfe9 Mon Sep 17 00:00:00 2001 From: Marc Mueller <30130371+cdce8p@users.noreply.github.com> Date: Fri, 3 May 2024 13:07:12 +0200 Subject: [PATCH 021/114] Fix fyta test timezone handling (#116689) --- tests/components/fyta/test_config_flow.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/components/fyta/test_config_flow.py b/tests/components/fyta/test_config_flow.py index 69478d04ca0..dedb468a617 100644 --- a/tests/components/fyta/test_config_flow.py +++ b/tests/components/fyta/test_config_flow.py @@ -21,7 +21,7 @@ from tests.common import MockConfigEntry USERNAME = "fyta_user" PASSWORD = "fyta_pass" ACCESS_TOKEN = "123xyz" -EXPIRATION = datetime.fromisoformat("2024-12-31T10:00:00").astimezone(UTC) +EXPIRATION = datetime.fromisoformat("2024-12-31T10:00:00").replace(tzinfo=UTC) async def test_user_flow( From 7e8cbafc6f4ac511c8d99f5264c8def76f4983db Mon Sep 17 00:00:00 2001 From: Felipe Martins Diel <41558831+felipediel@users.noreply.github.com> Date: Fri, 3 May 2024 08:11:22 -0300 Subject: [PATCH 022/114] Fix BroadlinkRemote._learn_command() (#116692) --- homeassistant/components/broadlink/remote.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/broadlink/remote.py b/homeassistant/components/broadlink/remote.py index 55368e5ff59..77c9ea0ff98 100644 --- a/homeassistant/components/broadlink/remote.py +++ b/homeassistant/components/broadlink/remote.py @@ -373,8 +373,11 @@ class BroadlinkRemote(BroadlinkEntity, RemoteEntity, RestoreEntity): start_time = dt_util.utcnow() while (dt_util.utcnow() - start_time) < LEARNING_TIMEOUT: await asyncio.sleep(1) - found = await device.async_request(device.api.check_frequency)[0] - if found: + is_found, frequency = await device.async_request( + device.api.check_frequency + ) + if is_found: + _LOGGER.info("Radiofrequency detected: %s MHz", frequency) break else: await device.async_request(device.api.cancel_sweep_frequency) From 9d2fd8217f1ed7e03afb2aca7014d7625c03cea3 Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Fri, 3 May 2024 13:38:38 +0200 Subject: [PATCH 023/114] Bump version to 2024.5.1 --- 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 eb46817bd34..31dc771d966 100644 --- a/homeassistant/const.py +++ b/homeassistant/const.py @@ -23,7 +23,7 @@ if TYPE_CHECKING: APPLICATION_NAME: Final = "HomeAssistant" MAJOR_VERSION: Final = 2024 MINOR_VERSION: Final = 5 -PATCH_VERSION: Final = "0" +PATCH_VERSION: Final = "1" __short_version__: Final = f"{MAJOR_VERSION}.{MINOR_VERSION}" __version__: Final = f"{__short_version__}.{PATCH_VERSION}" REQUIRED_PYTHON_VER: Final[tuple[int, int, int]] = (3, 12, 0) diff --git a/pyproject.toml b/pyproject.toml index 4dd5653f8ce..ac3c84d67f6 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "homeassistant" -version = "2024.5.0" +version = "2024.5.1" license = {text = "Apache-2.0"} description = "Open-source home automation platform running on Python 3." readme = "README.rst" From f4830216a8b5e27fe204db1d97df49791ed644b2 Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Sat, 4 May 2024 20:17:21 +0200 Subject: [PATCH 024/114] Add workaround for data entry flow show progress (#116704) Co-authored-by: epenet <6771947+epenet@users.noreply.github.com> --- homeassistant/data_entry_flow.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/homeassistant/data_entry_flow.py b/homeassistant/data_entry_flow.py index f628879a7fd..0bd494992b6 100644 --- a/homeassistant/data_entry_flow.py +++ b/homeassistant/data_entry_flow.py @@ -352,6 +352,18 @@ class FlowManager(abc.ABC, Generic[_FlowResultT, _HandlerT]): ) -> _FlowResultT: """Continue a data entry flow.""" result: _FlowResultT | None = None + + # Workaround for flow handlers which have not been upgraded to pass a show + # progress task, needed because of the change to eager tasks in HA Core 2024.5, + # can be removed in HA Core 2024.8. + flow = self._progress.get(flow_id) + if flow and flow.deprecated_show_progress: + if (cur_step := flow.cur_step) and cur_step[ + "type" + ] == FlowResultType.SHOW_PROGRESS: + # Allow the progress task to finish before we call the flow handler + await asyncio.sleep(0) + while not result or result["type"] == FlowResultType.SHOW_PROGRESS_DONE: result = await self._async_configure(flow_id, user_input) flow = self._progress.get(flow_id) From 17c5aa287190c4d61c88a4b7ed0abe5be6f04c65 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sat, 4 May 2024 17:35:44 -0500 Subject: [PATCH 025/114] Improve logging of _TrackPointUTCTime objects (#116711) --- homeassistant/helpers/event.py | 14 ++++++++++---- tests/helpers/test_event.py | 18 ++++++++++++++++++ tests/ignore_uncaught_exceptions.py | 6 ++++++ 3 files changed, 34 insertions(+), 4 deletions(-) diff --git a/homeassistant/helpers/event.py b/homeassistant/helpers/event.py index 5cffe992c0d..5c026064c28 100644 --- a/homeassistant/helpers/event.py +++ b/homeassistant/helpers/event.py @@ -1436,12 +1436,18 @@ class _TrackPointUTCTime: """Initialize track job.""" loop = self.hass.loop self._cancel_callback = loop.call_at( - loop.time() + self.expected_fire_timestamp - time.time(), self._run_action + loop.time() + self.expected_fire_timestamp - time.time(), self ) @callback - def _run_action(self) -> None: - """Call the action.""" + def __call__(self) -> None: + """Call the action. + + We implement this as __call__ so when debug logging logs the object + it shows the name of the job. This is especially helpful when asyncio + debug logging is enabled as we can see the name of the job that is + being called that is blocking the event loop. + """ # Depending on the available clock support (including timer hardware # and the OS kernel) it can happen that we fire a little bit too early # as measured by utcnow(). That is bad when callbacks have assumptions @@ -1450,7 +1456,7 @@ class _TrackPointUTCTime: if (delta := (self.expected_fire_timestamp - time_tracker_timestamp())) > 0: _LOGGER.debug("Called %f seconds too early, rearming", delta) loop = self.hass.loop - self._cancel_callback = loop.call_at(loop.time() + delta, self._run_action) + self._cancel_callback = loop.call_at(loop.time() + delta, self) return self.hass.async_run_hass_job(self.job, self.utc_point_in_time) diff --git a/tests/helpers/test_event.py b/tests/helpers/test_event.py index 07228abcc2c..a6fad968eac 100644 --- a/tests/helpers/test_event.py +++ b/tests/helpers/test_event.py @@ -4819,3 +4819,21 @@ async def test_track_state_change_deprecated( "of `async_track_state_change_event` which is deprecated and " "will be removed in Home Assistant 2025.5. Please report this issue." ) in caplog.text + + +async def test_track_point_in_time_repr( + hass: HomeAssistant, caplog: pytest.LogCaptureFixture +) -> None: + """Test track point in time.""" + + @ha.callback + def _raise_exception(_): + raise RuntimeError("something happened and its poorly described") + + async_track_point_in_utc_time(hass, _raise_exception, dt_util.utcnow()) + async_fire_time_changed(hass) + await hass.async_block_till_done(wait_background_tasks=True) + + assert "Exception in callback _TrackPointUTCTime" in caplog.text + assert "._raise_exception" in caplog.text + await hass.async_block_till_done(wait_background_tasks=True) diff --git a/tests/ignore_uncaught_exceptions.py b/tests/ignore_uncaught_exceptions.py index 3be2093057b..aaf6cbe3efe 100644 --- a/tests/ignore_uncaught_exceptions.py +++ b/tests/ignore_uncaught_exceptions.py @@ -7,6 +7,12 @@ IGNORE_UNCAUGHT_EXCEPTIONS = [ "tests.test_runner", "test_unhandled_exception_traceback", ), + ( + # This test explicitly throws an uncaught exception + # and should not be removed. + "tests.helpers.test_event", + "test_track_point_in_time_repr", + ), ( "test_homeassistant_bridge", "test_homeassistant_bridge_fan_setup", From 6d537e2a6690b0593d0d8a093eb22f9376167e48 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sat, 4 May 2024 10:29:00 -0500 Subject: [PATCH 026/114] Bump aiohttp-isal to 0.3.1 (#116720) --- homeassistant/package_constraints.txt | 2 +- pyproject.toml | 2 +- requirements.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/package_constraints.txt b/homeassistant/package_constraints.txt index 800e4d90009..024cdd3eab7 100644 --- a/homeassistant/package_constraints.txt +++ b/homeassistant/package_constraints.txt @@ -4,7 +4,7 @@ aiodhcpwatcher==1.0.0 aiodiscover==2.1.0 aiodns==3.2.0 aiohttp-fast-url-dispatcher==0.3.0 -aiohttp-isal==0.2.0 +aiohttp-isal==0.3.1 aiohttp==3.9.5 aiohttp_cors==0.7.0 aiohttp_session==2.12.0 diff --git a/pyproject.toml b/pyproject.toml index ac3c84d67f6..51023a501e6 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -28,7 +28,7 @@ dependencies = [ "aiohttp_cors==0.7.0", "aiohttp_session==2.12.0", "aiohttp-fast-url-dispatcher==0.3.0", - "aiohttp-isal==0.2.0", + "aiohttp-isal==0.3.1", "astral==2.2", "async-interrupt==1.1.1", "attrs==23.2.0", diff --git a/requirements.txt b/requirements.txt index 44c60aec07a..df001251a04 100644 --- a/requirements.txt +++ b/requirements.txt @@ -8,7 +8,7 @@ aiohttp==3.9.5 aiohttp_cors==0.7.0 aiohttp_session==2.12.0 aiohttp-fast-url-dispatcher==0.3.0 -aiohttp-isal==0.2.0 +aiohttp-isal==0.3.1 astral==2.2 async-interrupt==1.1.1 attrs==23.2.0 From bbb94d9e1780b01a52be7c3618febb742b9a779b Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Fri, 3 May 2024 23:54:27 +0200 Subject: [PATCH 027/114] Fix Bosch-SHC switch state (#116721) --- homeassistant/components/bosch_shc/switch.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/bosch_shc/switch.py b/homeassistant/components/bosch_shc/switch.py index e6ccd2aa9aa..58370a120f2 100644 --- a/homeassistant/components/bosch_shc/switch.py +++ b/homeassistant/components/bosch_shc/switch.py @@ -43,21 +43,21 @@ SWITCH_TYPES: dict[str, SHCSwitchEntityDescription] = { "smartplug": SHCSwitchEntityDescription( key="smartplug", device_class=SwitchDeviceClass.OUTLET, - on_key="state", + on_key="switchstate", on_value=SHCSmartPlug.PowerSwitchService.State.ON, should_poll=False, ), "smartplugcompact": SHCSwitchEntityDescription( key="smartplugcompact", device_class=SwitchDeviceClass.OUTLET, - on_key="state", + on_key="switchstate", on_value=SHCSmartPlugCompact.PowerSwitchService.State.ON, should_poll=False, ), "lightswitch": SHCSwitchEntityDescription( key="lightswitch", device_class=SwitchDeviceClass.SWITCH, - on_key="state", + on_key="switchstate", on_value=SHCLightSwitch.PowerSwitchService.State.ON, should_poll=False, ), From f068b8cdb8accb8a1b19115004021ef321035cb8 Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Sat, 4 May 2024 17:29:42 +0200 Subject: [PATCH 028/114] Remove suggested UoM from Opower (#116728) --- homeassistant/components/opower/sensor.py | 9 --------- 1 file changed, 9 deletions(-) diff --git a/homeassistant/components/opower/sensor.py b/homeassistant/components/opower/sensor.py index 9f467dce1c6..c75ffb9614b 100644 --- a/homeassistant/components/opower/sensor.py +++ b/homeassistant/components/opower/sensor.py @@ -69,7 +69,6 @@ ELEC_SENSORS: tuple[OpowerEntityDescription, ...] = ( name="Current bill electric cost to date", device_class=SensorDeviceClass.MONETARY, native_unit_of_measurement="USD", - suggested_unit_of_measurement="USD", state_class=SensorStateClass.TOTAL, suggested_display_precision=0, value_fn=lambda data: data.cost_to_date, @@ -79,7 +78,6 @@ ELEC_SENSORS: tuple[OpowerEntityDescription, ...] = ( name="Current bill electric forecasted cost", device_class=SensorDeviceClass.MONETARY, native_unit_of_measurement="USD", - suggested_unit_of_measurement="USD", state_class=SensorStateClass.TOTAL, suggested_display_precision=0, value_fn=lambda data: data.forecasted_cost, @@ -89,7 +87,6 @@ ELEC_SENSORS: tuple[OpowerEntityDescription, ...] = ( name="Typical monthly electric cost", device_class=SensorDeviceClass.MONETARY, native_unit_of_measurement="USD", - suggested_unit_of_measurement="USD", state_class=SensorStateClass.TOTAL, suggested_display_precision=0, value_fn=lambda data: data.typical_cost, @@ -101,7 +98,6 @@ GAS_SENSORS: tuple[OpowerEntityDescription, ...] = ( name="Current bill gas usage to date", device_class=SensorDeviceClass.GAS, native_unit_of_measurement=UnitOfVolume.CENTUM_CUBIC_FEET, - suggested_unit_of_measurement=UnitOfVolume.CENTUM_CUBIC_FEET, state_class=SensorStateClass.TOTAL, suggested_display_precision=0, value_fn=lambda data: data.usage_to_date, @@ -111,7 +107,6 @@ GAS_SENSORS: tuple[OpowerEntityDescription, ...] = ( name="Current bill gas forecasted usage", device_class=SensorDeviceClass.GAS, native_unit_of_measurement=UnitOfVolume.CENTUM_CUBIC_FEET, - suggested_unit_of_measurement=UnitOfVolume.CENTUM_CUBIC_FEET, state_class=SensorStateClass.TOTAL, suggested_display_precision=0, value_fn=lambda data: data.forecasted_usage, @@ -121,7 +116,6 @@ GAS_SENSORS: tuple[OpowerEntityDescription, ...] = ( name="Typical monthly gas usage", device_class=SensorDeviceClass.GAS, native_unit_of_measurement=UnitOfVolume.CENTUM_CUBIC_FEET, - suggested_unit_of_measurement=UnitOfVolume.CENTUM_CUBIC_FEET, state_class=SensorStateClass.TOTAL, suggested_display_precision=0, value_fn=lambda data: data.typical_usage, @@ -131,7 +125,6 @@ GAS_SENSORS: tuple[OpowerEntityDescription, ...] = ( name="Current bill gas cost to date", device_class=SensorDeviceClass.MONETARY, native_unit_of_measurement="USD", - suggested_unit_of_measurement="USD", state_class=SensorStateClass.TOTAL, suggested_display_precision=0, value_fn=lambda data: data.cost_to_date, @@ -141,7 +134,6 @@ GAS_SENSORS: tuple[OpowerEntityDescription, ...] = ( name="Current bill gas forecasted cost", device_class=SensorDeviceClass.MONETARY, native_unit_of_measurement="USD", - suggested_unit_of_measurement="USD", state_class=SensorStateClass.TOTAL, suggested_display_precision=0, value_fn=lambda data: data.forecasted_cost, @@ -151,7 +143,6 @@ GAS_SENSORS: tuple[OpowerEntityDescription, ...] = ( name="Typical monthly gas cost", device_class=SensorDeviceClass.MONETARY, native_unit_of_measurement="USD", - suggested_unit_of_measurement="USD", state_class=SensorStateClass.TOTAL, suggested_display_precision=0, value_fn=lambda data: data.typical_cost, From 57bbd105171da4a79602777ceffbbb17f15faa4d Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 5 May 2024 15:39:45 -0500 Subject: [PATCH 029/114] Refactor statistics to avoid creating tasks (#116743) --- homeassistant/components/statistics/sensor.py | 103 ++++++++++-------- 1 file changed, 58 insertions(+), 45 deletions(-) diff --git a/homeassistant/components/statistics/sensor.py b/homeassistant/components/statistics/sensor.py index 713a8d3e894..fef10f7296f 100644 --- a/homeassistant/components/statistics/sensor.py +++ b/homeassistant/components/statistics/sensor.py @@ -285,6 +285,9 @@ async def async_setup_platform( class StatisticsSensor(SensorEntity): """Representation of a Statistics sensor.""" + _attr_should_poll = False + _attr_icon = ICON + def __init__( self, source_entity_id: str, @@ -298,9 +301,7 @@ class StatisticsSensor(SensorEntity): percentile: int, ) -> None: """Initialize the Statistics sensor.""" - self._attr_icon: str = ICON self._attr_name: str = name - self._attr_should_poll: bool = False self._attr_unique_id: str | None = unique_id self._source_entity_id: str = source_entity_id self.is_binary: bool = ( @@ -326,35 +327,37 @@ class StatisticsSensor(SensorEntity): self._update_listener: CALLBACK_TYPE | None = None + @callback + def _async_stats_sensor_state_listener( + self, + event: Event[EventStateChangedData], + ) -> None: + """Handle the sensor state changes.""" + if (new_state := event.data["new_state"]) is None: + return + self._add_state_to_queue(new_state) + self._async_purge_update_and_schedule() + self.async_write_ha_state() + + @callback + def _async_stats_sensor_startup(self, _: HomeAssistant) -> None: + """Add listener and get recorded state.""" + _LOGGER.debug("Startup for %s", self.entity_id) + self.async_on_remove( + async_track_state_change_event( + self.hass, + [self._source_entity_id], + self._async_stats_sensor_state_listener, + ) + ) + if "recorder" in self.hass.config.components: + self.hass.async_create_task(self._initialize_from_database()) + async def async_added_to_hass(self) -> None: """Register callbacks.""" - - @callback - def async_stats_sensor_state_listener( - event: Event[EventStateChangedData], - ) -> None: - """Handle the sensor state changes.""" - if (new_state := event.data["new_state"]) is None: - return - self._add_state_to_queue(new_state) - self.async_schedule_update_ha_state(True) - - async def async_stats_sensor_startup(_: HomeAssistant) -> None: - """Add listener and get recorded state.""" - _LOGGER.debug("Startup for %s", self.entity_id) - - self.async_on_remove( - async_track_state_change_event( - self.hass, - [self._source_entity_id], - async_stats_sensor_state_listener, - ) - ) - - if "recorder" in self.hass.config.components: - self.hass.async_create_task(self._initialize_from_database()) - - self.async_on_remove(async_at_start(self.hass, async_stats_sensor_startup)) + self.async_on_remove( + async_at_start(self.hass, self._async_stats_sensor_startup) + ) def _add_state_to_queue(self, new_state: State) -> None: """Add the state to the queue.""" @@ -499,7 +502,8 @@ class StatisticsSensor(SensorEntity): self.ages.popleft() self.states.popleft() - def _next_to_purge_timestamp(self) -> datetime | None: + @callback + def _async_next_to_purge_timestamp(self) -> datetime | None: """Find the timestamp when the next purge would occur.""" if self.ages and self._samples_max_age: if self.samples_keep_last and len(self.ages) == 1: @@ -521,6 +525,10 @@ class StatisticsSensor(SensorEntity): async def async_update(self) -> None: """Get the latest data and updates the states.""" + self._async_purge_update_and_schedule() + + def _async_purge_update_and_schedule(self) -> None: + """Purge old states, update the sensor and schedule the next update.""" _LOGGER.debug("%s: updating statistics", self.entity_id) if self._samples_max_age is not None: self._purge_old_states(self._samples_max_age) @@ -531,23 +539,28 @@ class StatisticsSensor(SensorEntity): # If max_age is set, ensure to update again after the defined interval. # By basing updates off the timestamps of sampled data we avoid updating # when none of the observed entities change. - if timestamp := self._next_to_purge_timestamp(): + if timestamp := self._async_next_to_purge_timestamp(): _LOGGER.debug("%s: scheduling update at %s", self.entity_id, timestamp) - if self._update_listener: - self._update_listener() - self._update_listener = None - - @callback - def _scheduled_update(now: datetime) -> None: - """Timer callback for sensor update.""" - _LOGGER.debug("%s: executing scheduled update", self.entity_id) - self.async_schedule_update_ha_state(True) - self._update_listener = None - + self._async_cancel_update_listener() self._update_listener = async_track_point_in_utc_time( - self.hass, _scheduled_update, timestamp + self.hass, self._async_scheduled_update, timestamp ) + @callback + def _async_cancel_update_listener(self) -> None: + """Cancel the scheduled update listener.""" + if self._update_listener: + self._update_listener() + self._update_listener = None + + @callback + def _async_scheduled_update(self, now: datetime) -> None: + """Timer callback for sensor update.""" + _LOGGER.debug("%s: executing scheduled update", self.entity_id) + self._async_cancel_update_listener() + self._async_purge_update_and_schedule() + self.async_write_ha_state() + def _fetch_states_from_database(self) -> list[State]: """Fetch the states from the database.""" _LOGGER.debug("%s: initializing values from the database", self.entity_id) @@ -589,8 +602,8 @@ class StatisticsSensor(SensorEntity): for state in reversed(states): self._add_state_to_queue(state) - self.async_schedule_update_ha_state(True) - + self._async_purge_update_and_schedule() + self.async_write_ha_state() _LOGGER.debug("%s: initializing from database completed", self.entity_id) def _update_attributes(self) -> None: From 18bcc61427fddfb2d853a5757a11415cab7f41c2 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sat, 4 May 2024 10:26:14 -0500 Subject: [PATCH 030/114] Bump bluetooth-adapters to 0.19.2 (#116785) --- 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 754e8faf996..fe5867191e2 100644 --- a/homeassistant/components/bluetooth/manifest.json +++ b/homeassistant/components/bluetooth/manifest.json @@ -16,7 +16,7 @@ "requirements": [ "bleak==0.21.1", "bleak-retry-connector==3.5.0", - "bluetooth-adapters==0.19.1", + "bluetooth-adapters==0.19.2", "bluetooth-auto-recovery==1.4.2", "bluetooth-data-tools==1.19.0", "dbus-fast==2.21.1", diff --git a/homeassistant/package_constraints.txt b/homeassistant/package_constraints.txt index 024cdd3eab7..4fd8ebccd7e 100644 --- a/homeassistant/package_constraints.txt +++ b/homeassistant/package_constraints.txt @@ -17,7 +17,7 @@ awesomeversion==24.2.0 bcrypt==4.1.2 bleak-retry-connector==3.5.0 bleak==0.21.1 -bluetooth-adapters==0.19.1 +bluetooth-adapters==0.19.2 bluetooth-auto-recovery==1.4.2 bluetooth-data-tools==1.19.0 cached_ipaddress==0.3.0 diff --git a/requirements_all.txt b/requirements_all.txt index 6f741478f20..462d33d69d0 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -579,7 +579,7 @@ bluemaestro-ble==0.2.3 # bluepy==1.3.0 # homeassistant.components.bluetooth -bluetooth-adapters==0.19.1 +bluetooth-adapters==0.19.2 # homeassistant.components.bluetooth bluetooth-auto-recovery==1.4.2 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 14951b8a6ec..5233e7e70f8 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -494,7 +494,7 @@ bluecurrent-api==1.2.3 bluemaestro-ble==0.2.3 # homeassistant.components.bluetooth -bluetooth-adapters==0.19.1 +bluetooth-adapters==0.19.2 # homeassistant.components.bluetooth bluetooth-auto-recovery==1.4.2 From 79460cb017f6c3d2985f78d502728b70cdded0d6 Mon Sep 17 00:00:00 2001 From: Patrick Decat Date: Sat, 4 May 2024 20:16:58 +0200 Subject: [PATCH 031/114] fix UnboundLocalError on modified_statistic_ids in compile_statistics (#116795) Co-authored-by: J. Nick Koston --- homeassistant/components/recorder/statistics.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/homeassistant/components/recorder/statistics.py b/homeassistant/components/recorder/statistics.py index 41cf4e22b53..572731a9fed 100644 --- a/homeassistant/components/recorder/statistics.py +++ b/homeassistant/components/recorder/statistics.py @@ -485,6 +485,12 @@ def compile_statistics(instance: Recorder, start: datetime, fire_events: bool) - The actual calculation is delegated to the platforms. """ + # Define modified_statistic_ids outside of the "with" statement as + # _compile_statistics may raise and be trapped by + # filter_unique_constraint_integrity_error which would make + # modified_statistic_ids unbound. + modified_statistic_ids: set[str] | None = None + # Return if we already have 5-minute statistics for the requested period with session_scope( session=instance.get_session(), From ad7688197ff6ae4869bb29b741360f5c3759ba71 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 5 May 2024 05:09:57 -0500 Subject: [PATCH 032/114] Ensure all synology_dsm coordinators handle expired sessions (#116796) * Ensure all synology_dsm coordinators handle expired sessions * Ensure all synology_dsm coordinators handle expired sessions * Ensure all synology_dsm coordinators handle expired sessions * handle cancellation * add a debug log message --------- Co-authored-by: mib1185 --- .../components/synology_dsm/__init__.py | 6 ++- .../components/synology_dsm/common.py | 28 ++++++++++- .../components/synology_dsm/coordinator.py | 50 +++++++++++++------ 3 files changed, 68 insertions(+), 16 deletions(-) diff --git a/homeassistant/components/synology_dsm/__init__.py b/homeassistant/components/synology_dsm/__init__.py index 2748b27c93d..6598ed304f7 100644 --- a/homeassistant/components/synology_dsm/__init__.py +++ b/homeassistant/components/synology_dsm/__init__.py @@ -7,6 +7,7 @@ import logging from synology_dsm.api.surveillance_station import SynoSurveillanceStation from synology_dsm.api.surveillance_station.camera import SynoCamera +from synology_dsm.exceptions import SynologyDSMNotLoggedInException from homeassistant.config_entries import ConfigEntry from homeassistant.const import CONF_MAC, CONF_VERIFY_SSL @@ -69,7 +70,10 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: await api.async_setup() except SYNOLOGY_AUTH_FAILED_EXCEPTIONS as err: raise_config_entry_auth_error(err) - except SYNOLOGY_CONNECTION_EXCEPTIONS as err: + except (*SYNOLOGY_CONNECTION_EXCEPTIONS, SynologyDSMNotLoggedInException) as err: + # SynologyDSMNotLoggedInException may be raised even if the user is + # logged in because the session may have expired, and we need to retry + # the login later. if err.args[0] and isinstance(err.args[0], dict): details = err.args[0].get(EXCEPTION_DETAILS, EXCEPTION_UNKNOWN) else: diff --git a/homeassistant/components/synology_dsm/common.py b/homeassistant/components/synology_dsm/common.py index 04e8ae29ceb..c871dd7b705 100644 --- a/homeassistant/components/synology_dsm/common.py +++ b/homeassistant/components/synology_dsm/common.py @@ -2,6 +2,7 @@ from __future__ import annotations +import asyncio from collections.abc import Callable from contextlib import suppress import logging @@ -82,6 +83,31 @@ class SynoApi: self._with_upgrade = True self._with_utilisation = True + self._login_future: asyncio.Future[None] | None = None + + async def async_login(self) -> None: + """Login to the Synology DSM API. + + This function will only login once if called multiple times + by multiple different callers. + + If a login is already in progress, the function will await the + login to complete before returning. + """ + if self._login_future: + return await self._login_future + + self._login_future = self._hass.loop.create_future() + try: + await self.dsm.login() + self._login_future.set_result(None) + except BaseException as err: + if not self._login_future.done(): + self._login_future.set_exception(err) + raise + finally: + self._login_future = None + async def async_setup(self) -> None: """Start interacting with the NAS.""" session = async_get_clientsession(self._hass, self._entry.data[CONF_VERIFY_SSL]) @@ -95,7 +121,7 @@ class SynoApi: timeout=self._entry.options.get(CONF_TIMEOUT) or 10, device_token=self._entry.data.get(CONF_DEVICE_TOKEN), ) - await self.dsm.login() + await self.async_login() # check if surveillance station is used self._with_surveillance_station = bool( diff --git a/homeassistant/components/synology_dsm/coordinator.py b/homeassistant/components/synology_dsm/coordinator.py index 34886828a58..52a3e1de1eb 100644 --- a/homeassistant/components/synology_dsm/coordinator.py +++ b/homeassistant/components/synology_dsm/coordinator.py @@ -2,9 +2,10 @@ from __future__ import annotations +from collections.abc import Awaitable, Callable, Coroutine from datetime import timedelta import logging -from typing import Any, TypeVar +from typing import Any, Concatenate, ParamSpec, TypeVar from synology_dsm.api.surveillance_station.camera import SynoCamera from synology_dsm.exceptions import ( @@ -30,6 +31,36 @@ _LOGGER = logging.getLogger(__name__) _DataT = TypeVar("_DataT") +_T = TypeVar("_T", bound="SynologyDSMUpdateCoordinator") +_P = ParamSpec("_P") + + +def async_re_login_on_expired( + func: Callable[Concatenate[_T, _P], Awaitable[_DataT]], +) -> Callable[Concatenate[_T, _P], Coroutine[Any, Any, _DataT]]: + """Define a wrapper to re-login when expired.""" + + async def _async_wrap(self: _T, *args: _P.args, **kwargs: _P.kwargs) -> _DataT: + for attempts in range(2): + try: + return await func(self, *args, **kwargs) + except SynologyDSMNotLoggedInException: + # If login is expired, try to login again + _LOGGER.debug("login is expired, try to login again") + try: + await self.api.async_login() + except SYNOLOGY_AUTH_FAILED_EXCEPTIONS as err: + raise_config_entry_auth_error(err) + if attempts == 0: + continue + except SYNOLOGY_CONNECTION_EXCEPTIONS as err: + raise UpdateFailed(f"Error communicating with API: {err}") from err + + raise UpdateFailed("Unknown error when communicating with API") + + return _async_wrap + + class SynologyDSMUpdateCoordinator(DataUpdateCoordinator[_DataT]): """DataUpdateCoordinator base class for synology_dsm.""" @@ -72,6 +103,7 @@ class SynologyDSMSwitchUpdateCoordinator( assert info is not None self.version = info["data"]["CMSMinVersion"] + @async_re_login_on_expired async def _async_update_data(self) -> dict[str, dict[str, Any]]: """Fetch all data from api.""" surveillance_station = self.api.surveillance_station @@ -102,21 +134,10 @@ class SynologyDSMCentralUpdateCoordinator(SynologyDSMUpdateCoordinator[None]): ), ) + @async_re_login_on_expired async def _async_update_data(self) -> None: """Fetch all data from api.""" - for attempts in range(2): - try: - await self.api.async_update() - except SynologyDSMNotLoggedInException: - # If login is expired, try to login again - try: - await self.api.dsm.login() - except SYNOLOGY_AUTH_FAILED_EXCEPTIONS as err: - raise_config_entry_auth_error(err) - if attempts == 0: - continue - except SYNOLOGY_CONNECTION_EXCEPTIONS as err: - raise UpdateFailed(f"Error communicating with API: {err}") from err + await self.api.async_update() class SynologyDSMCameraUpdateCoordinator( @@ -133,6 +154,7 @@ class SynologyDSMCameraUpdateCoordinator( """Initialize DataUpdateCoordinator for cameras.""" super().__init__(hass, entry, api, timedelta(seconds=30)) + @async_re_login_on_expired async def _async_update_data(self) -> dict[str, dict[int, SynoCamera]]: """Fetch all camera data from api.""" surveillance_station = self.api.surveillance_station From dbe303d95ee34ace5051d45916e58c15b13a19cf Mon Sep 17 00:00:00 2001 From: Jan Bouwhuis Date: Sat, 4 May 2024 20:18:26 +0200 Subject: [PATCH 033/114] Fix IMAP config entry setup (#116797) --- homeassistant/components/imap/config_flow.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/imap/config_flow.py b/homeassistant/components/imap/config_flow.py index 62ed4d42a07..6f93ce71d84 100644 --- a/homeassistant/components/imap/config_flow.py +++ b/homeassistant/components/imap/config_flow.py @@ -75,7 +75,7 @@ CONFIG_SCHEMA = vol.Schema( vol.Optional(CONF_FOLDER, default="INBOX"): str, vol.Optional(CONF_SEARCH, default="UnSeen UnDeleted"): str, # The default for new entries is to not include text and headers - vol.Optional(CONF_EVENT_MESSAGE_DATA, default=[]): cv.ensure_list, + vol.Optional(CONF_EVENT_MESSAGE_DATA, default=[]): EVENT_MESSAGE_DATA_SELECTOR, } ) CONFIG_SCHEMA_ADVANCED = { From ae28c604e5dfb516f05581bff60bfabe18e4f788 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 5 May 2024 15:37:10 -0500 Subject: [PATCH 034/114] Fix airthings-ble data drop outs when Bluetooth connection is flakey (#116805) * Fix airthings-ble data drop outs when Bluetooth adapter is flakey fixes #116770 * add missing file * update --- homeassistant/components/airthings_ble/__init__.py | 8 +++++++- homeassistant/components/airthings_ble/const.py | 2 ++ homeassistant/components/airthings_ble/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 5 files changed, 12 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/airthings_ble/__init__.py b/homeassistant/components/airthings_ble/__init__.py index 39617a8a019..219a384bae0 100644 --- a/homeassistant/components/airthings_ble/__init__.py +++ b/homeassistant/components/airthings_ble/__init__.py @@ -16,7 +16,7 @@ from homeassistant.exceptions import ConfigEntryNotReady from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed from homeassistant.util.unit_system import METRIC_SYSTEM -from .const import DEFAULT_SCAN_INTERVAL, DOMAIN +from .const import DEFAULT_SCAN_INTERVAL, DOMAIN, MAX_RETRIES_AFTER_STARTUP PLATFORMS: list[Platform] = [Platform.SENSOR] @@ -61,6 +61,12 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: await coordinator.async_config_entry_first_refresh() + # Once its setup and we know we are not going to delay + # the startup of Home Assistant, we can set the max attempts + # to a higher value. If the first connection attempt fails, + # Home Assistant's built-in retry logic will take over. + airthings.set_max_attempts(MAX_RETRIES_AFTER_STARTUP) + hass.data[DOMAIN][entry.entry_id] = coordinator await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS) diff --git a/homeassistant/components/airthings_ble/const.py b/homeassistant/components/airthings_ble/const.py index 96372919e70..fdfebea8bff 100644 --- a/homeassistant/components/airthings_ble/const.py +++ b/homeassistant/components/airthings_ble/const.py @@ -7,3 +7,5 @@ VOLUME_BECQUEREL = "Bq/m³" VOLUME_PICOCURIE = "pCi/L" DEFAULT_SCAN_INTERVAL = 300 + +MAX_RETRIES_AFTER_STARTUP = 5 diff --git a/homeassistant/components/airthings_ble/manifest.json b/homeassistant/components/airthings_ble/manifest.json index d93e3a0b8cb..b86bc314819 100644 --- a/homeassistant/components/airthings_ble/manifest.json +++ b/homeassistant/components/airthings_ble/manifest.json @@ -24,5 +24,5 @@ "dependencies": ["bluetooth_adapters"], "documentation": "https://www.home-assistant.io/integrations/airthings_ble", "iot_class": "local_polling", - "requirements": ["airthings-ble==0.8.0"] + "requirements": ["airthings-ble==0.9.0"] } diff --git a/requirements_all.txt b/requirements_all.txt index 462d33d69d0..e3f2bd0e36c 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -413,7 +413,7 @@ aioymaps==1.2.2 airly==1.1.0 # homeassistant.components.airthings_ble -airthings-ble==0.8.0 +airthings-ble==0.9.0 # homeassistant.components.airthings airthings-cloud==0.2.0 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 5233e7e70f8..27e70c28916 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -386,7 +386,7 @@ aioymaps==1.2.2 airly==1.1.0 # homeassistant.components.airthings_ble -airthings-ble==0.8.0 +airthings-ble==0.9.0 # homeassistant.components.airthings airthings-cloud==0.2.0 From ad5e0949b6b10cb10fe9909e0799bc2d881c664a Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Sat, 4 May 2024 20:09:38 -0400 Subject: [PATCH 035/114] Hide conversation agents that are exposed as agent entities (#116813) --- homeassistant/components/conversation/http.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/homeassistant/components/conversation/http.py b/homeassistant/components/conversation/http.py index beda7ba1550..e582dacf284 100644 --- a/homeassistant/components/conversation/http.py +++ b/homeassistant/components/conversation/http.py @@ -142,6 +142,9 @@ async def websocket_list_agents( agent = manager.async_get_agent(agent_info.id) assert agent is not None + if isinstance(agent, ConversationEntity): + continue + supported_languages = agent.supported_languages if language and supported_languages != MATCH_ALL: supported_languages = language_util.matches( From c049888b00094f17c0c4e8e2c88dc2ac15c9efa0 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 5 May 2024 08:43:39 -0500 Subject: [PATCH 036/114] Fix non-thread-safe state write in lutron event (#116829) fixes #116746 --- homeassistant/components/lutron/event.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/lutron/event.py b/homeassistant/components/lutron/event.py index 710f942a006..f231c33a296 100644 --- a/homeassistant/components/lutron/event.py +++ b/homeassistant/components/lutron/event.py @@ -106,4 +106,4 @@ class LutronEventEntity(LutronKeypad, EventEntity): } self.hass.bus.fire("lutron_event", data) self._trigger_event(action) - self.async_write_ha_state() + self.schedule_update_ha_state() From 421f74cd7f3f2b20c8e402402086571e5286a79e Mon Sep 17 00:00:00 2001 From: Michael <35783820+mib1185@users.noreply.github.com> Date: Sun, 5 May 2024 15:07:18 +0200 Subject: [PATCH 037/114] Increase default timeout to 30 seconds in Synology DSM (#116836) increase default timeout to 30s and use it consequently --- homeassistant/components/synology_dsm/common.py | 3 ++- homeassistant/components/synology_dsm/config_flow.py | 4 +++- homeassistant/components/synology_dsm/const.py | 2 +- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/synology_dsm/common.py b/homeassistant/components/synology_dsm/common.py index c871dd7b705..91c4cfc4ae2 100644 --- a/homeassistant/components/synology_dsm/common.py +++ b/homeassistant/components/synology_dsm/common.py @@ -39,6 +39,7 @@ from homeassistant.helpers.aiohttp_client import async_get_clientsession from .const import ( CONF_DEVICE_TOKEN, + DEFAULT_TIMEOUT, EXCEPTION_DETAILS, EXCEPTION_UNKNOWN, SYNOLOGY_CONNECTION_EXCEPTIONS, @@ -118,7 +119,7 @@ class SynoApi: self._entry.data[CONF_USERNAME], self._entry.data[CONF_PASSWORD], self._entry.data[CONF_SSL], - timeout=self._entry.options.get(CONF_TIMEOUT) or 10, + timeout=self._entry.options.get(CONF_TIMEOUT) or DEFAULT_TIMEOUT, device_token=self._entry.data.get(CONF_DEVICE_TOKEN), ) await self.async_login() diff --git a/homeassistant/components/synology_dsm/config_flow.py b/homeassistant/components/synology_dsm/config_flow.py index 785baa50b29..d6c0c6fe3e8 100644 --- a/homeassistant/components/synology_dsm/config_flow.py +++ b/homeassistant/components/synology_dsm/config_flow.py @@ -179,7 +179,9 @@ class SynologyDSMFlowHandler(ConfigFlow, domain=DOMAIN): port = DEFAULT_PORT session = async_get_clientsession(self.hass, verify_ssl) - api = SynologyDSM(session, host, port, username, password, use_ssl, timeout=30) + api = SynologyDSM( + session, host, port, username, password, use_ssl, timeout=DEFAULT_TIMEOUT + ) errors = {} try: diff --git a/homeassistant/components/synology_dsm/const.py b/homeassistant/components/synology_dsm/const.py index 140e07e975b..35d3008b416 100644 --- a/homeassistant/components/synology_dsm/const.py +++ b/homeassistant/components/synology_dsm/const.py @@ -40,7 +40,7 @@ DEFAULT_PORT = 5000 DEFAULT_PORT_SSL = 5001 # Options DEFAULT_SCAN_INTERVAL = 15 # min -DEFAULT_TIMEOUT = 10 # sec +DEFAULT_TIMEOUT = 30 # sec DEFAULT_SNAPSHOT_QUALITY = SNAPSHOT_PROFILE_BALANCED ENTITY_UNIT_LOAD = "load" From 834c2e2a09730549e8d7fddc0aa90bdb43e3a99f Mon Sep 17 00:00:00 2001 From: Michael <35783820+mib1185@users.noreply.github.com> Date: Sun, 5 May 2024 15:09:26 +0200 Subject: [PATCH 038/114] Avoid duplicate data fetch during Synologs DSM setup (#116839) don't do first refresh of central coordinator, is already done by api.setup before --- homeassistant/components/synology_dsm/__init__.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/homeassistant/components/synology_dsm/__init__.py b/homeassistant/components/synology_dsm/__init__.py index 6598ed304f7..d42dacca638 100644 --- a/homeassistant/components/synology_dsm/__init__.py +++ b/homeassistant/components/synology_dsm/__init__.py @@ -90,12 +90,9 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: ) coordinator_central = SynologyDSMCentralUpdateCoordinator(hass, entry, api) - await coordinator_central.async_config_entry_first_refresh() available_apis = api.dsm.apis - # The central coordinator needs to be refreshed first since - # the next two rely on data from it coordinator_cameras: SynologyDSMCameraUpdateCoordinator | None = None if api.surveillance_station is not None: coordinator_cameras = SynologyDSMCameraUpdateCoordinator(hass, entry, api) From 73eabe821cb80ca1fe2a54df832c2fa1c6dfa519 Mon Sep 17 00:00:00 2001 From: tronikos Date: Sun, 5 May 2024 06:44:40 -0700 Subject: [PATCH 039/114] Bump androidtvremote2 to v0.0.15 (#116844) --- homeassistant/components/androidtv_remote/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/androidtv_remote/manifest.json b/homeassistant/components/androidtv_remote/manifest.json index f45dee34afe..915586b3879 100644 --- a/homeassistant/components/androidtv_remote/manifest.json +++ b/homeassistant/components/androidtv_remote/manifest.json @@ -8,6 +8,6 @@ "iot_class": "local_push", "loggers": ["androidtvremote2"], "quality_scale": "platinum", - "requirements": ["androidtvremote2==0.0.14"], + "requirements": ["androidtvremote2==0.0.15"], "zeroconf": ["_androidtvremote2._tcp.local."] } diff --git a/requirements_all.txt b/requirements_all.txt index e3f2bd0e36c..be856c1fa85 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -437,7 +437,7 @@ amcrest==1.9.8 androidtv[async]==0.0.73 # homeassistant.components.androidtv_remote -androidtvremote2==0.0.14 +androidtvremote2==0.0.15 # homeassistant.components.anel_pwrctrl anel-pwrctrl-homeassistant==0.0.1.dev2 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 27e70c28916..80b1c2a345b 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -404,7 +404,7 @@ amberelectric==1.1.0 androidtv[async]==0.0.73 # homeassistant.components.androidtv_remote -androidtvremote2==0.0.14 +androidtvremote2==0.0.15 # homeassistant.components.anova anova-wifi==0.10.0 From 7c9653e3974ce06c464e5224ef7ec7f64d1bf8f4 Mon Sep 17 00:00:00 2001 From: mletenay Date: Mon, 6 May 2024 01:05:21 +0200 Subject: [PATCH 040/114] Bump goodwe to 0.3.4 (#116849) --- homeassistant/components/goodwe/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/goodwe/manifest.json b/homeassistant/components/goodwe/manifest.json index 6f1bdd2b449..59c259524c8 100644 --- a/homeassistant/components/goodwe/manifest.json +++ b/homeassistant/components/goodwe/manifest.json @@ -6,5 +6,5 @@ "documentation": "https://www.home-assistant.io/integrations/goodwe", "iot_class": "local_polling", "loggers": ["goodwe"], - "requirements": ["goodwe==0.3.2"] + "requirements": ["goodwe==0.3.4"] } diff --git a/requirements_all.txt b/requirements_all.txt index be856c1fa85..bf73d7792e7 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -952,7 +952,7 @@ glances-api==0.6.0 goalzero==0.2.2 # homeassistant.components.goodwe -goodwe==0.3.2 +goodwe==0.3.4 # homeassistant.components.google_mail # homeassistant.components.google_tasks diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 80b1c2a345b..77802e3d5c7 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -781,7 +781,7 @@ glances-api==0.6.0 goalzero==0.2.2 # homeassistant.components.goodwe -goodwe==0.3.2 +goodwe==0.3.4 # homeassistant.components.google_mail # homeassistant.components.google_tasks From 9533f5b49006c6e5b8b31a247571e95ea0072ba5 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 5 May 2024 15:58:38 -0500 Subject: [PATCH 041/114] Fix non-thread-safe operations in amcrest (#116859) * Fix non-thread-safe operations in amcrest fixes #116850 * fix locking * fix locking * fix locking --- homeassistant/components/amcrest/__init__.py | 95 +++++++++++++++----- homeassistant/components/amcrest/camera.py | 5 +- 2 files changed, 75 insertions(+), 25 deletions(-) diff --git a/homeassistant/components/amcrest/__init__.py b/homeassistant/components/amcrest/__init__.py index c12aa6d7916..624e0145b86 100644 --- a/homeassistant/components/amcrest/__init__.py +++ b/homeassistant/components/amcrest/__init__.py @@ -35,7 +35,7 @@ from homeassistant.const import ( HTTP_BASIC_AUTHENTICATION, Platform, ) -from homeassistant.core import HomeAssistant, ServiceCall +from homeassistant.core import HomeAssistant, ServiceCall, callback from homeassistant.exceptions import Unauthorized, UnknownUser from homeassistant.helpers import discovery import homeassistant.helpers.config_validation as cv @@ -177,7 +177,8 @@ class AmcrestChecker(ApiWrapper): """Return event flag that indicates if camera's API is responding.""" return self._async_wrap_event_flag - def _start_recovery(self) -> None: + @callback + def _async_start_recovery(self) -> None: self.available_flag.clear() self.async_available_flag.clear() async_dispatcher_send( @@ -222,50 +223,98 @@ class AmcrestChecker(ApiWrapper): yield except LoginError as ex: async with self._async_wrap_lock: - self._handle_offline(ex) + self._async_handle_offline(ex) raise except AmcrestError: async with self._async_wrap_lock: - self._handle_error() + self._async_handle_error() raise async with self._async_wrap_lock: - self._set_online() + self._async_set_online() - def _handle_offline(self, ex: Exception) -> None: + def _handle_offline_thread_safe(self, ex: Exception) -> bool: + """Handle camera offline status shared between threads and event loop. + + Returns if the camera was online as a bool. + """ with self._wrap_lock: was_online = self.available was_login_err = self._wrap_login_err self._wrap_login_err = True if not was_login_err: _LOGGER.error("%s camera offline: Login error: %s", self._wrap_name, ex) - if was_online: - self._start_recovery() + return was_online - def _handle_error(self) -> None: + def _handle_offline(self, ex: Exception) -> None: + """Handle camera offline status from a thread.""" + if self._handle_offline_thread_safe(ex): + self._hass.loop.call_soon_threadsafe(self._async_start_recovery) + + @callback + def _async_handle_offline(self, ex: Exception) -> None: + if self._handle_offline_thread_safe(ex): + self._async_start_recovery() + + def _handle_error_thread_safe(self) -> bool: + """Handle camera error status shared between threads and event loop. + + Returns if the camera was online and is now offline as + a bool. + """ with self._wrap_lock: was_online = self.available errs = self._wrap_errors = self._wrap_errors + 1 offline = not self.available _LOGGER.debug("%s camera errs: %i", self._wrap_name, errs) - if was_online and offline: - _LOGGER.error("%s camera offline: Too many errors", self._wrap_name) - self._start_recovery() + return was_online and offline - def _set_online(self) -> None: + def _handle_error(self) -> None: + """Handle camera error status from a thread.""" + if self._handle_error_thread_safe(): + _LOGGER.error("%s camera offline: Too many errors", self._wrap_name) + self._hass.loop.call_soon_threadsafe(self._async_start_recovery) + + @callback + def _async_handle_error(self) -> None: + """Handle camera error status from the event loop.""" + if self._handle_error_thread_safe(): + _LOGGER.error("%s camera offline: Too many errors", self._wrap_name) + self._async_start_recovery() + + def _set_online_thread_safe(self) -> bool: + """Set camera online status shared between threads and event loop. + + Returns if the camera was offline as a bool. + """ with self._wrap_lock: was_offline = not self.available self._wrap_errors = 0 self._wrap_login_err = False - if was_offline: - assert self._unsub_recheck is not None - self._unsub_recheck() - self._unsub_recheck = None - _LOGGER.error("%s camera back online", self._wrap_name) - self.available_flag.set() - self.async_available_flag.set() - async_dispatcher_send( - self._hass, service_signal(SERVICE_UPDATE, self._wrap_name) - ) + return was_offline + + def _set_online(self) -> None: + """Set camera online status from a thread.""" + if self._set_online_thread_safe(): + self._hass.loop.call_soon_threadsafe(self._async_signal_online) + + @callback + def _async_set_online(self) -> None: + """Set camera online status from the event loop.""" + if self._set_online_thread_safe(): + self._async_signal_online() + + @callback + def _async_signal_online(self) -> None: + """Signal that camera is back online.""" + assert self._unsub_recheck is not None + self._unsub_recheck() + self._unsub_recheck = None + _LOGGER.error("%s camera back online", self._wrap_name) + self.available_flag.set() + self.async_available_flag.set() + async_dispatcher_send( + self._hass, service_signal(SERVICE_UPDATE, self._wrap_name) + ) async def _wrap_test_online(self, now: datetime) -> None: """Test if camera is back online.""" diff --git a/homeassistant/components/amcrest/camera.py b/homeassistant/components/amcrest/camera.py index 1cbf5af4b70..a55f9c81e64 100644 --- a/homeassistant/components/amcrest/camera.py +++ b/homeassistant/components/amcrest/camera.py @@ -16,7 +16,7 @@ import voluptuous as vol from homeassistant.components.camera import Camera, CameraEntityFeature from homeassistant.components.ffmpeg import FFmpegManager, get_ffmpeg_manager from homeassistant.const import ATTR_ENTITY_ID, CONF_NAME, STATE_OFF, STATE_ON -from homeassistant.core import HomeAssistant +from homeassistant.core import HomeAssistant, callback from homeassistant.helpers import config_validation as cv from homeassistant.helpers.aiohttp_client import ( async_aiohttp_proxy_stream, @@ -325,7 +325,8 @@ class AmcrestCam(Camera): # Other Entity method overrides - async def async_on_demand_update(self) -> None: + @callback + def async_on_demand_update(self) -> None: """Update state.""" self.async_schedule_update_ha_state(True) From ed6788ca3fdce964df5948ed69422db4de457b77 Mon Sep 17 00:00:00 2001 From: Robert Hillis Date: Sun, 5 May 2024 12:54:17 -0400 Subject: [PATCH 042/114] fix radarr coordinator updates (#116874) --- homeassistant/components/radarr/coordinator.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/radarr/coordinator.py b/homeassistant/components/radarr/coordinator.py index 0580fdcc020..47a1862b8ae 100644 --- a/homeassistant/components/radarr/coordinator.py +++ b/homeassistant/components/radarr/coordinator.py @@ -46,7 +46,7 @@ class RadarrDataUpdateCoordinator(DataUpdateCoordinator[T], Generic[T], ABC): """Data update coordinator for the Radarr integration.""" config_entry: ConfigEntry - update_interval = timedelta(seconds=30) + _update_interval = timedelta(seconds=30) def __init__( self, @@ -59,7 +59,7 @@ class RadarrDataUpdateCoordinator(DataUpdateCoordinator[T], Generic[T], ABC): hass=hass, logger=LOGGER, name=DOMAIN, - update_interval=self.update_interval, + update_interval=self._update_interval, ) self.api_client = api_client self.host_configuration = host_configuration @@ -133,7 +133,7 @@ class QueueDataUpdateCoordinator(RadarrDataUpdateCoordinator): class CalendarUpdateCoordinator(RadarrDataUpdateCoordinator[None]): """Calendar update coordinator.""" - update_interval = timedelta(hours=1) + _update_interval = timedelta(hours=1) def __init__( self, From ab113570c3a063870ae297345eb4d3e474823ba9 Mon Sep 17 00:00:00 2001 From: Jan Bouwhuis Date: Mon, 6 May 2024 14:32:37 +0200 Subject: [PATCH 043/114] Fix initial mqtt subcribe cooldown timeout (#116904) --- homeassistant/components/mqtt/client.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/mqtt/client.py b/homeassistant/components/mqtt/client.py index 4fa9f4a1d49..4b05442d71b 100644 --- a/homeassistant/components/mqtt/client.py +++ b/homeassistant/components/mqtt/client.py @@ -84,7 +84,7 @@ if TYPE_CHECKING: _LOGGER = logging.getLogger(__name__) DISCOVERY_COOLDOWN = 5 -INITIAL_SUBSCRIBE_COOLDOWN = 1.0 +INITIAL_SUBSCRIBE_COOLDOWN = 3.0 SUBSCRIBE_COOLDOWN = 0.1 UNSUBSCRIBE_COOLDOWN = 0.1 TIMEOUT_ACK = 10 @@ -891,6 +891,7 @@ class MQTT: qos=birth_message.qos, retain=birth_message.retain, ) + _LOGGER.info("MQTT client initialized, birth message sent") @callback def _async_mqtt_on_connect( @@ -950,6 +951,7 @@ class MQTT: name="mqtt re-subscribe", ) self._subscribe_debouncer.set_timeout(SUBSCRIBE_COOLDOWN) + _LOGGER.info("MQTT client initialized") self._async_connection_result(True) From 6b93f8d997f2b3986916f676917bfb29a3444a88 Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Mon, 6 May 2024 17:19:04 +0200 Subject: [PATCH 044/114] Bump version to 2024.5.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 31dc771d966..e9e1231712e 100644 --- a/homeassistant/const.py +++ b/homeassistant/const.py @@ -23,7 +23,7 @@ if TYPE_CHECKING: APPLICATION_NAME: Final = "HomeAssistant" MAJOR_VERSION: Final = 2024 MINOR_VERSION: Final = 5 -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, 12, 0) diff --git a/pyproject.toml b/pyproject.toml index 51023a501e6..887083304cf 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "homeassistant" -version = "2024.5.1" +version = "2024.5.2" license = {text = "Apache-2.0"} description = "Open-source home automation platform running on Python 3." readme = "README.rst" From eb6ccea8aa65ebf33711fbdd649832c084d26249 Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Mon, 6 May 2024 18:40:01 +0200 Subject: [PATCH 045/114] Update frontend to 20240501.1 (#116939) --- 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 6abe8df1d7c..1c4245d93b6 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==20240501.0"] + "requirements": ["home-assistant-frontend==20240501.1"] } diff --git a/homeassistant/package_constraints.txt b/homeassistant/package_constraints.txt index 4fd8ebccd7e..0f69f7d63c9 100644 --- a/homeassistant/package_constraints.txt +++ b/homeassistant/package_constraints.txt @@ -32,7 +32,7 @@ habluetooth==2.8.1 hass-nabucasa==0.78.0 hassil==1.6.1 home-assistant-bluetooth==1.12.0 -home-assistant-frontend==20240501.0 +home-assistant-frontend==20240501.1 home-assistant-intents==2024.4.24 httpx==0.27.0 ifaddr==0.2.0 diff --git a/requirements_all.txt b/requirements_all.txt index bf73d7792e7..e4c84b11ab8 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1078,7 +1078,7 @@ hole==0.8.0 holidays==0.47 # homeassistant.components.frontend -home-assistant-frontend==20240501.0 +home-assistant-frontend==20240501.1 # homeassistant.components.conversation home-assistant-intents==2024.4.24 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 77802e3d5c7..e9dc44b3765 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -880,7 +880,7 @@ hole==0.8.0 holidays==0.47 # homeassistant.components.frontend -home-assistant-frontend==20240501.0 +home-assistant-frontend==20240501.1 # homeassistant.components.conversation home-assistant-intents==2024.4.24 From 624baebbaa2c4de2f636f61d17597c6f7270f1fc Mon Sep 17 00:00:00 2001 From: Pete Sage <76050312+PeteRager@users.noreply.github.com> Date: Tue, 7 May 2024 04:08:12 -0400 Subject: [PATCH 046/114] Fix Sonos select_source timeout error (#115640) --- .../components/sonos/media_player.py | 12 +- homeassistant/components/sonos/strings.json | 5 + tests/components/sonos/conftest.py | 15 +- .../sonos/fixtures/sonos_favorites.json | 38 +++++ tests/components/sonos/test_media_player.py | 159 +++++++++++++++++- 5 files changed, 222 insertions(+), 7 deletions(-) create mode 100644 tests/components/sonos/fixtures/sonos_favorites.json diff --git a/homeassistant/components/sonos/media_player.py b/homeassistant/components/sonos/media_player.py index 35c6be3fa6b..e9fbb152b7a 100644 --- a/homeassistant/components/sonos/media_player.py +++ b/homeassistant/components/sonos/media_player.py @@ -39,7 +39,7 @@ from homeassistant.components.plex.services import process_plex_payload from homeassistant.config_entries import ConfigEntry from homeassistant.const import ATTR_TIME from homeassistant.core import HomeAssistant, ServiceCall, callback -from homeassistant.exceptions import HomeAssistantError +from homeassistant.exceptions import HomeAssistantError, ServiceValidationError from homeassistant.helpers import config_validation as cv, entity_platform, service from homeassistant.helpers.dispatcher import async_dispatcher_connect from homeassistant.helpers.entity_platform import AddEntitiesCallback @@ -432,7 +432,13 @@ class SonosMediaPlayerEntity(SonosEntity, MediaPlayerEntity): fav = [fav for fav in self.speaker.favorites if fav.title == name] if len(fav) != 1: - return + raise ServiceValidationError( + translation_domain=SONOS_DOMAIN, + translation_key="invalid_favorite", + translation_placeholders={ + "name": name, + }, + ) src = fav.pop() self._play_favorite(src) @@ -445,7 +451,7 @@ class SonosMediaPlayerEntity(SonosEntity, MediaPlayerEntity): MUSIC_SRC_RADIO, MUSIC_SRC_LINE_IN, ]: - soco.play_uri(uri, title=favorite.title) + soco.play_uri(uri, title=favorite.title, timeout=LONG_SERVICE_TIMEOUT) else: soco.clear_queue() soco.add_to_queue(favorite.reference, timeout=LONG_SERVICE_TIMEOUT) diff --git a/homeassistant/components/sonos/strings.json b/homeassistant/components/sonos/strings.json index 6f45195c46b..6521302b007 100644 --- a/homeassistant/components/sonos/strings.json +++ b/homeassistant/components/sonos/strings.json @@ -173,5 +173,10 @@ } } } + }, + "exceptions": { + "invalid_favorite": { + "message": "Could not find a Sonos favorite: {name}" + } } } diff --git a/tests/components/sonos/conftest.py b/tests/components/sonos/conftest.py index 0eb9b497fbd..15f371f272c 100644 --- a/tests/components/sonos/conftest.py +++ b/tests/components/sonos/conftest.py @@ -9,6 +9,7 @@ from unittest.mock import AsyncMock, MagicMock, Mock, patch import pytest from soco import SoCo from soco.alarms import Alarms +from soco.data_structures import DidlFavorite, SearchResult from soco.events_base import Event as SonosEvent from homeassistant.components import ssdp, zeroconf @@ -17,7 +18,7 @@ from homeassistant.components.sonos import DOMAIN from homeassistant.const import CONF_HOSTS from homeassistant.core import HomeAssistant -from tests.common import MockConfigEntry, load_fixture +from tests.common import MockConfigEntry, load_fixture, load_json_value_fixture class SonosMockEventListener: @@ -304,6 +305,14 @@ def config_fixture(): return {DOMAIN: {MP_DOMAIN: {CONF_HOSTS: ["192.168.42.2"]}}} +@pytest.fixture(name="sonos_favorites") +def sonos_favorites_fixture() -> SearchResult: + """Create sonos favorites fixture.""" + favorites = load_json_value_fixture("sonos_favorites.json", "sonos") + favorite_list = [DidlFavorite.from_dict(fav) for fav in favorites] + return SearchResult(favorite_list, "favorites", 3, 3, 1) + + class MockMusicServiceItem: """Mocks a Soco MusicServiceItem.""" @@ -408,10 +417,10 @@ def mock_get_music_library_information( @pytest.fixture(name="music_library") -def music_library_fixture(): +def music_library_fixture(sonos_favorites: SearchResult) -> Mock: """Create music_library fixture.""" music_library = MagicMock() - music_library.get_sonos_favorites.return_value.update_id = 1 + music_library.get_sonos_favorites.return_value = sonos_favorites music_library.browse_by_idstring = mock_browse_by_idstring music_library.get_music_library_information = mock_get_music_library_information return music_library diff --git a/tests/components/sonos/fixtures/sonos_favorites.json b/tests/components/sonos/fixtures/sonos_favorites.json new file mode 100644 index 00000000000..21ee68f4872 --- /dev/null +++ b/tests/components/sonos/fixtures/sonos_favorites.json @@ -0,0 +1,38 @@ +[ + { + "title": "66 - Watercolors", + "parent_id": "FV:2", + "item_id": "FV:2/4", + "resource_meta_data": "66 - Watercolorsobject.item.audioItem.audioBroadcastSA_RINCON9479_X_#Svc9479-99999999-Token", + "resources": [ + { + "uri": "x-sonosapi-hls:Api%3atune%3aliveAudio%3ajazzcafe%3aetc", + "protocol_info": "a:b:c:d" + } + ] + }, + { + "title": "James Taylor Radio", + "parent_id": "FV:2", + "item_id": "FV:2/13", + "resource_meta_data": "James Taylor Radioobject.item.audioItem.audioBroadcast.#stationSA_RINCON60423_X_#Svc60423-99999999-Token", + "resources": [ + { + "uri": "x-sonosapi-radio:ST%3aetc", + "protocol_info": "a:b:c:d" + } + ] + }, + { + "title": "1984", + "parent_id": "FV:2", + "item_id": "FV:2/8", + "resource_meta_data": "1984object.container.album.musicAlbumRINCON_AssociatedZPUDN", + "resources": [ + { + "uri": "x-rincon-playlist:RINCON_test#A:ALBUMARTIST/Aerosmith/1984", + "protocol_info": "a:b:c:d" + } + ] + } +] diff --git a/tests/components/sonos/test_media_player.py b/tests/components/sonos/test_media_player.py index 976d3480429..9fb8444a696 100644 --- a/tests/components/sonos/test_media_player.py +++ b/tests/components/sonos/test_media_player.py @@ -1,6 +1,7 @@ """Tests for the Sonos Media Player platform.""" import logging +from typing import Any import pytest @@ -9,10 +10,15 @@ from homeassistant.components.media_player import ( SERVICE_PLAY_MEDIA, MediaPlayerEnqueue, ) -from homeassistant.components.media_player.const import ATTR_MEDIA_ENQUEUE +from homeassistant.components.media_player.const import ( + ATTR_MEDIA_ENQUEUE, + SERVICE_SELECT_SOURCE, +) +from homeassistant.components.sonos.const import SOURCE_LINEIN, SOURCE_TV from homeassistant.components.sonos.media_player import LONG_SERVICE_TIMEOUT from homeassistant.const import STATE_IDLE from homeassistant.core import HomeAssistant +from homeassistant.exceptions import ServiceValidationError from homeassistant.helpers.device_registry import ( CONNECTION_NETWORK_MAC, CONNECTION_UPNP, @@ -272,3 +278,154 @@ async def test_play_media_music_library_playlist_dne( assert soco_mock.play_uri.call_count == 0 assert media_content_id in caplog.text assert "playlist" in caplog.text + + +@pytest.mark.parametrize( + ("source", "result"), + [ + ( + SOURCE_LINEIN, + { + "switch_to_line_in": 1, + }, + ), + ( + SOURCE_TV, + { + "switch_to_tv": 1, + }, + ), + ], +) +async def test_select_source_line_in_tv( + hass: HomeAssistant, + soco_factory: SoCoMockFactory, + async_autosetup_sonos, + source: str, + result: dict[str, Any], +) -> None: + """Test the select_source method with a variety of inputs.""" + soco_mock = soco_factory.mock_list.get("192.168.42.2") + await hass.services.async_call( + MP_DOMAIN, + SERVICE_SELECT_SOURCE, + { + "entity_id": "media_player.zone_a", + "source": source, + }, + blocking=True, + ) + assert soco_mock.switch_to_line_in.call_count == result.get("switch_to_line_in", 0) + assert soco_mock.switch_to_tv.call_count == result.get("switch_to_tv", 0) + + +@pytest.mark.parametrize( + ("source", "result"), + [ + ( + "James Taylor Radio", + { + "play_uri": 1, + "play_uri_uri": "x-sonosapi-radio:ST%3aetc", + "play_uri_title": "James Taylor Radio", + }, + ), + ( + "66 - Watercolors", + { + "play_uri": 1, + "play_uri_uri": "x-sonosapi-hls:Api%3atune%3aliveAudio%3ajazzcafe%3aetc", + "play_uri_title": "66 - Watercolors", + }, + ), + ], +) +async def test_select_source_play_uri( + hass: HomeAssistant, + soco_factory: SoCoMockFactory, + async_autosetup_sonos, + source: str, + result: dict[str, Any], +) -> None: + """Test the select_source method with a variety of inputs.""" + soco_mock = soco_factory.mock_list.get("192.168.42.2") + await hass.services.async_call( + MP_DOMAIN, + SERVICE_SELECT_SOURCE, + { + "entity_id": "media_player.zone_a", + "source": source, + }, + blocking=True, + ) + assert soco_mock.play_uri.call_count == result.get("play_uri") + soco_mock.play_uri.assert_called_with( + result.get("play_uri_uri"), + title=result.get("play_uri_title"), + timeout=LONG_SERVICE_TIMEOUT, + ) + + +@pytest.mark.parametrize( + ("source", "result"), + [ + ( + "1984", + { + "add_to_queue": 1, + "add_to_queue_item_id": "A:ALBUMARTIST/Aerosmith/1984", + "clear_queue": 1, + "play_from_queue": 1, + }, + ), + ], +) +async def test_select_source_play_queue( + hass: HomeAssistant, + soco_factory: SoCoMockFactory, + async_autosetup_sonos, + source: str, + result: dict[str, Any], +) -> None: + """Test the select_source method with a variety of inputs.""" + soco_mock = soco_factory.mock_list.get("192.168.42.2") + await hass.services.async_call( + MP_DOMAIN, + SERVICE_SELECT_SOURCE, + { + "entity_id": "media_player.zone_a", + "source": source, + }, + blocking=True, + ) + assert soco_mock.clear_queue.call_count == result.get("clear_queue") + assert soco_mock.add_to_queue.call_count == result.get("add_to_queue") + assert soco_mock.add_to_queue.call_args_list[0].args[0].item_id == result.get( + "add_to_queue_item_id" + ) + assert ( + soco_mock.add_to_queue.call_args_list[0].kwargs["timeout"] + == LONG_SERVICE_TIMEOUT + ) + assert soco_mock.play_from_queue.call_count == result.get("play_from_queue") + soco_mock.play_from_queue.assert_called_with(0) + + +async def test_select_source_error( + hass: HomeAssistant, + soco_factory: SoCoMockFactory, + async_autosetup_sonos, +) -> None: + """Test the select_source method with a variety of inputs.""" + with pytest.raises(ServiceValidationError) as sve: + await hass.services.async_call( + MP_DOMAIN, + SERVICE_SELECT_SOURCE, + { + "entity_id": "media_player.zone_a", + "source": "invalid_source", + }, + blocking=True, + ) + assert "invalid_source" in str(sve.value) + assert "Could not find a Sonos favorite" in str(sve.value) From 57861dc091ec3c5aab0096f53a8461f25d7ada1e Mon Sep 17 00:00:00 2001 From: "Mr. Bubbles" Date: Tue, 7 May 2024 21:10:04 +0200 Subject: [PATCH 047/114] Update strings for Bring notification service (#116181) update translations --- homeassistant/components/bring/strings.json | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/homeassistant/components/bring/strings.json b/homeassistant/components/bring/strings.json index e6df885cbbc..5deb0759c17 100644 --- a/homeassistant/components/bring/strings.json +++ b/homeassistant/components/bring/strings.json @@ -60,8 +60,8 @@ "description": "Type of push notification to send to list members." }, "item": { - "name": "Item (Required if message type `Breaking news` selected)", - "description": "Item name to include in a breaking news message e.g. `Breaking news - Please get cilantro!`" + "name": "Article (Required if message type `Urgent Message` selected)", + "description": "Article name to include in an urgent message e.g. `Urgent Message - Please buy Cilantro urgently`" } } } @@ -69,10 +69,10 @@ "selector": { "notification_type_selector": { "options": { - "going_shopping": "I'm going shopping! - Last chance for adjustments", - "changed_list": "List changed - Check it out", - "shopping_done": "Shopping done - you can relax", - "urgent_message": "Breaking news - Please get `item`!" + "going_shopping": "I'm going shopping! - Last chance to make changes", + "changed_list": "List updated - Take a look at the articles", + "shopping_done": "Shopping done - The fridge is well stocked", + "urgent_message": "Urgent Message - Please buy `Article name` urgently" } } } From fdc59547e0f02a7799657dffecd12df969f44d01 Mon Sep 17 00:00:00 2001 From: Matrix Date: Tue, 7 May 2024 13:51:10 +0800 Subject: [PATCH 048/114] Bump Yolink api to 0.4.4 (#116967) --- homeassistant/components/yolink/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/yolink/manifest.json b/homeassistant/components/yolink/manifest.json index b7bd1d4784f..5353d5d5b8c 100644 --- a/homeassistant/components/yolink/manifest.json +++ b/homeassistant/components/yolink/manifest.json @@ -6,5 +6,5 @@ "dependencies": ["auth", "application_credentials"], "documentation": "https://www.home-assistant.io/integrations/yolink", "iot_class": "cloud_push", - "requirements": ["yolink-api==0.4.3"] + "requirements": ["yolink-api==0.4.4"] } diff --git a/requirements_all.txt b/requirements_all.txt index e4c84b11ab8..f188c7ea248 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2914,7 +2914,7 @@ yeelight==0.7.14 yeelightsunflower==0.0.10 # homeassistant.components.yolink -yolink-api==0.4.3 +yolink-api==0.4.4 # homeassistant.components.youless youless-api==1.0.1 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index e9dc44b3765..9bec4e50de4 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -2264,7 +2264,7 @@ yalexs==3.0.1 yeelight==0.7.14 # homeassistant.components.yolink -yolink-api==0.4.3 +yolink-api==0.4.4 # homeassistant.components.youless youless-api==1.0.1 From bee518dc78cb7c1588e9c54df71bdcae18b86d82 Mon Sep 17 00:00:00 2001 From: Marc Mueller <30130371+cdce8p@users.noreply.github.com> Date: Tue, 7 May 2024 13:56:11 +0200 Subject: [PATCH 049/114] Update jinja2 to 3.1.4 (#116986) --- homeassistant/package_constraints.txt | 2 +- pyproject.toml | 2 +- requirements.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/package_constraints.txt b/homeassistant/package_constraints.txt index 0f69f7d63c9..13ac6119f66 100644 --- a/homeassistant/package_constraints.txt +++ b/homeassistant/package_constraints.txt @@ -36,7 +36,7 @@ home-assistant-frontend==20240501.1 home-assistant-intents==2024.4.24 httpx==0.27.0 ifaddr==0.2.0 -Jinja2==3.1.3 +Jinja2==3.1.4 lru-dict==1.3.0 mutagen==1.47.0 orjson==3.9.15 diff --git a/pyproject.toml b/pyproject.toml index 887083304cf..8fb7839c628 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -46,7 +46,7 @@ dependencies = [ "httpx==0.27.0", "home-assistant-bluetooth==1.12.0", "ifaddr==0.2.0", - "Jinja2==3.1.3", + "Jinja2==3.1.4", "lru-dict==1.3.0", "PyJWT==2.8.0", # PyJWT has loose dependency. We want the latest one. diff --git a/requirements.txt b/requirements.txt index df001251a04..9d0cd618b2e 100644 --- a/requirements.txt +++ b/requirements.txt @@ -22,7 +22,7 @@ hass-nabucasa==0.78.0 httpx==0.27.0 home-assistant-bluetooth==1.12.0 ifaddr==0.2.0 -Jinja2==3.1.3 +Jinja2==3.1.4 lru-dict==1.3.0 PyJWT==2.8.0 cryptography==42.0.5 From 1a13e1d024aca7bdf1ca95ce9356217310896300 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Tue, 7 May 2024 14:41:31 -0500 Subject: [PATCH 050/114] Simplify MQTT subscribe debouncer execution (#117006) --- homeassistant/components/mqtt/client.py | 19 +++++++------------ tests/components/mqtt/test_init.py | 22 +++++++++++----------- tests/components/mqtt/test_mixins.py | 3 +++ 3 files changed, 21 insertions(+), 23 deletions(-) diff --git a/homeassistant/components/mqtt/client.py b/homeassistant/components/mqtt/client.py index 4b05442d71b..2ca17f012e4 100644 --- a/homeassistant/components/mqtt/client.py +++ b/homeassistant/components/mqtt/client.py @@ -317,7 +317,7 @@ class EnsureJobAfterCooldown: self._loop = asyncio.get_running_loop() self._timeout = timeout self._callback = callback_job - self._task: asyncio.Future | None = None + self._task: asyncio.Task | None = None self._timer: asyncio.TimerHandle | None = None def set_timeout(self, timeout: float) -> None: @@ -332,28 +332,23 @@ class EnsureJobAfterCooldown: _LOGGER.error("%s", ha_error) @callback - def _async_task_done(self, task: asyncio.Future) -> None: + def _async_task_done(self, task: asyncio.Task) -> None: """Handle task done.""" self._task = None @callback - def _async_execute(self) -> None: + def async_execute(self) -> asyncio.Task: """Execute the job.""" if self._task: # Task already running, # so we schedule another run self.async_schedule() - return + return self._task self._async_cancel_timer() self._task = create_eager_task(self._async_job()) self._task.add_done_callback(self._async_task_done) - - async def async_fire(self) -> None: - """Execute the job immediately.""" - if self._task: - await self._task - self._async_execute() + return self._task @callback def _async_cancel_timer(self) -> None: @@ -368,7 +363,7 @@ class EnsureJobAfterCooldown: # We want to reschedule the timer in the future # every time this is called. self._async_cancel_timer() - self._timer = self._loop.call_later(self._timeout, self._async_execute) + self._timer = self._loop.call_later(self._timeout, self.async_execute) async def async_cleanup(self) -> None: """Cleanup any pending task.""" @@ -883,7 +878,7 @@ class MQTT: await self._discovery_cooldown() # Wait for MQTT discovery to cool down # Update subscribe cooldown period to a shorter time # and make sure we flush the debouncer - await self._subscribe_debouncer.async_fire() + await self._subscribe_debouncer.async_execute() self._subscribe_debouncer.set_timeout(SUBSCRIBE_COOLDOWN) await self.async_publish( topic=birth_message.topic, diff --git a/tests/components/mqtt/test_init.py b/tests/components/mqtt/test_init.py index a1264b52739..b7998274aa0 100644 --- a/tests/components/mqtt/test_init.py +++ b/tests/components/mqtt/test_init.py @@ -2589,19 +2589,19 @@ async def test_subscription_done_when_birth_message_is_sent( mqtt_client_mock.on_connect(None, None, 0, 0) await hass.async_block_till_done() hass.bus.async_fire(EVENT_HOMEASSISTANT_STARTED) + await mqtt.async_subscribe(hass, "topic/test", record_calls) # We wait until we receive a birth message await asyncio.wait_for(birth.wait(), 1) - # Assert we already have subscribed at the client - # for new config payloads at the time we the birth message is received - assert ("homeassistant/+/+/config", 0) in help_all_subscribe_calls( - mqtt_client_mock - ) - assert ("homeassistant/+/+/+/config", 0) in help_all_subscribe_calls( - mqtt_client_mock - ) - mqtt_client_mock.publish.assert_called_with( - "homeassistant/status", "online", 0, False - ) + + # Assert we already have subscribed at the client + # for new config payloads at the time we the birth message is received + subscribe_calls = help_all_subscribe_calls(mqtt_client_mock) + assert ("homeassistant/+/+/config", 0) in subscribe_calls + assert ("homeassistant/+/+/+/config", 0) in subscribe_calls + mqtt_client_mock.publish.assert_called_with( + "homeassistant/status", "online", 0, False + ) + assert ("topic/test", 0) in subscribe_calls @pytest.mark.parametrize( diff --git a/tests/components/mqtt/test_mixins.py b/tests/components/mqtt/test_mixins.py index 2bcd663c243..e46f0b56c15 100644 --- a/tests/components/mqtt/test_mixins.py +++ b/tests/components/mqtt/test_mixins.py @@ -335,6 +335,9 @@ async def test_default_entity_and_device_name( # Assert that no issues ware registered assert len(events) == 0 + await hass.async_block_till_done() + # Assert that no issues ware registered + assert len(events) == 0 async def test_name_attribute_is_set_or_not( From f34a0dc5ce760164aa00046ace3899162a9bc995 Mon Sep 17 00:00:00 2001 From: Jan Bouwhuis Date: Tue, 7 May 2024 21:19:46 +0200 Subject: [PATCH 051/114] Log an exception mqtt client call back throws (#117028) * Log an exception mqtt client call back throws * Supress exceptions and add test --- homeassistant/components/mqtt/client.py | 22 +++++++++++--- tests/components/mqtt/test_init.py | 39 ++++++++++++++++++++++++- 2 files changed, 56 insertions(+), 5 deletions(-) diff --git a/homeassistant/components/mqtt/client.py b/homeassistant/components/mqtt/client.py index 2ca17f012e4..589113d3a9e 100644 --- a/homeassistant/components/mqtt/client.py +++ b/homeassistant/components/mqtt/client.py @@ -492,6 +492,9 @@ class MQTT: mqttc.on_subscribe = self._async_mqtt_on_callback mqttc.on_unsubscribe = self._async_mqtt_on_callback + # suppress exceptions at callback + mqttc.suppress_exceptions = True + if will := self.conf.get(CONF_WILL_MESSAGE, DEFAULT_WILL): will_message = PublishMessage(**will) mqttc.will_set( @@ -988,10 +991,21 @@ class MQTT: def _async_mqtt_on_message( self, _mqttc: mqtt.Client, _userdata: None, msg: mqtt.MQTTMessage ) -> None: - topic = msg.topic - # msg.topic is a property that decodes the topic to a string - # every time it is accessed. Save the result to avoid - # decoding the same topic multiple times. + try: + # msg.topic is a property that decodes the topic to a string + # every time it is accessed. Save the result to avoid + # decoding the same topic multiple times. + topic = msg.topic + except UnicodeDecodeError: + bare_topic: bytes = getattr(msg, "_topic") + _LOGGER.warning( + "Skipping received%s message on invalid topic %s (qos=%s): %s", + " retained" if msg.retain else "", + bare_topic, + msg.qos, + msg.payload[0:8192], + ) + return _LOGGER.debug( "Received%s message on %s (qos=%s): %s", " retained" if msg.retain else "", diff --git a/tests/components/mqtt/test_init.py b/tests/components/mqtt/test_init.py index b7998274aa0..ec7968ae46b 100644 --- a/tests/components/mqtt/test_init.py +++ b/tests/components/mqtt/test_init.py @@ -6,8 +6,9 @@ from datetime import datetime, timedelta import json import socket import ssl +import time from typing import Any, TypedDict -from unittest.mock import ANY, MagicMock, call, mock_open, patch +from unittest.mock import ANY, MagicMock, Mock, call, mock_open, patch from freezegun.api import FrozenDateTimeFactory import paho.mqtt.client as paho_mqtt @@ -938,6 +939,42 @@ async def test_receiving_non_utf8_message_gets_logged( ) +async def test_receiving_message_with_non_utf8_topic_gets_logged( + hass: HomeAssistant, + mqtt_mock_entry: MqttMockHAClientGenerator, + record_calls: MessageCallbackType, + caplog: pytest.LogCaptureFixture, +) -> None: + """Test receiving a non utf8 encoded topic.""" + await mqtt_mock_entry() + await mqtt.async_subscribe(hass, "test-topic", record_calls) + + # Local import to avoid processing MQTT modules when running a testcase + # which does not use MQTT. + + # pylint: disable-next=import-outside-toplevel + from paho.mqtt.client import MQTTMessage + + # pylint: disable-next=import-outside-toplevel + from homeassistant.components.mqtt.models import MqttData + + msg = MQTTMessage(topic=b"tasmota/discovery/18FE34E0B760\xcc\x02") + msg.payload = b"Payload" + msg.qos = 2 + msg.retain = True + msg.timestamp = time.monotonic() + + mqtt_data: MqttData = hass.data["mqtt"] + assert mqtt_data.client + mqtt_data.client._async_mqtt_on_message(Mock(), None, msg) + + assert ( + "Skipping received retained message on invalid " + "topic b'tasmota/discovery/18FE34E0B760\\xcc\\x02' " + "(qos=2): b'Payload'" in caplog.text + ) + + async def test_all_subscriptions_run_when_decode_fails( hass: HomeAssistant, mqtt_mock_entry: MqttMockHAClientGenerator, From 08ba5304feb801211c26f68c6f3e98da43b22b18 Mon Sep 17 00:00:00 2001 From: Chris Talkington Date: Wed, 8 May 2024 08:38:44 -0500 Subject: [PATCH 052/114] Bump rokuecp to 0.19.3 (#117059) --- homeassistant/components/roku/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/roku/manifest.json b/homeassistant/components/roku/manifest.json index ce4513fb316..fa9823de172 100644 --- a/homeassistant/components/roku/manifest.json +++ b/homeassistant/components/roku/manifest.json @@ -11,7 +11,7 @@ "iot_class": "local_polling", "loggers": ["rokuecp"], "quality_scale": "silver", - "requirements": ["rokuecp==0.19.2"], + "requirements": ["rokuecp==0.19.3"], "ssdp": [ { "st": "roku:ecp", diff --git a/requirements_all.txt b/requirements_all.txt index f188c7ea248..b7147c8f8ec 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2460,7 +2460,7 @@ rjpl==0.3.6 rocketchat-API==0.6.1 # homeassistant.components.roku -rokuecp==0.19.2 +rokuecp==0.19.3 # homeassistant.components.romy romy==0.0.10 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 9bec4e50de4..2f84692c081 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1906,7 +1906,7 @@ rflink==0.0.66 ring-doorbell[listen]==0.8.11 # homeassistant.components.roku -rokuecp==0.19.2 +rokuecp==0.19.3 # homeassistant.components.romy romy==0.0.10 From 9e7e839f03acce12c687a0701be9feac617eb847 Mon Sep 17 00:00:00 2001 From: Arie Catsman <120491684+catsmanac@users.noreply.github.com> Date: Wed, 8 May 2024 14:02:49 +0200 Subject: [PATCH 053/114] Bump pyenphase to 1.20.3 (#117061) --- homeassistant/components/enphase_envoy/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/enphase_envoy/manifest.json b/homeassistant/components/enphase_envoy/manifest.json index 597d326968d..b3c117556bf 100644 --- a/homeassistant/components/enphase_envoy/manifest.json +++ b/homeassistant/components/enphase_envoy/manifest.json @@ -6,7 +6,7 @@ "documentation": "https://www.home-assistant.io/integrations/enphase_envoy", "iot_class": "local_polling", "loggers": ["pyenphase"], - "requirements": ["pyenphase==1.20.1"], + "requirements": ["pyenphase==1.20.3"], "zeroconf": [ { "type": "_enphase-envoy._tcp.local." diff --git a/requirements_all.txt b/requirements_all.txt index b7147c8f8ec..e39f08d66bf 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1800,7 +1800,7 @@ pyefergy==22.1.1 pyegps==0.2.5 # homeassistant.components.enphase_envoy -pyenphase==1.20.1 +pyenphase==1.20.3 # homeassistant.components.envisalink pyenvisalink==4.6 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 2f84692c081..2939c4e843e 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1405,7 +1405,7 @@ pyefergy==22.1.1 pyegps==0.2.5 # homeassistant.components.enphase_envoy -pyenphase==1.20.1 +pyenphase==1.20.3 # homeassistant.components.everlights pyeverlights==0.1.0 From d40689024a6f389134c141a52eb3f0397040f9ad Mon Sep 17 00:00:00 2001 From: puddly <32534428+puddly@users.noreply.github.com> Date: Wed, 8 May 2024 17:57:50 -0400 Subject: [PATCH 054/114] Add a missing `addon_name` placeholder to the SkyConnect config flow (#117089) --- .../components/homeassistant_sky_connect/config_flow.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/homeassistant_sky_connect/config_flow.py b/homeassistant/components/homeassistant_sky_connect/config_flow.py index 9d0aa902cc4..a65aefe96f2 100644 --- a/homeassistant/components/homeassistant_sky_connect/config_flow.py +++ b/homeassistant/components/homeassistant_sky_connect/config_flow.py @@ -95,7 +95,10 @@ class BaseFirmwareInstallFlow(ConfigEntryBaseFlow, ABC): _LOGGER.error(err) raise AbortFlow( "addon_set_config_failed", - description_placeholders=self._get_translation_placeholders(), + description_placeholders={ + **self._get_translation_placeholders(), + "addon_name": addon_manager.addon_name, + }, ) from err async def _async_get_addon_info(self, addon_manager: AddonManager) -> AddonInfo: From 82fab7df399f2f8d9091575ade306d4fadc9b093 Mon Sep 17 00:00:00 2001 From: mletenay Date: Thu, 9 May 2024 00:08:08 +0200 Subject: [PATCH 055/114] Goodwe Increase max value of export limit to 200% (#117090) --- homeassistant/components/goodwe/number.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/goodwe/number.py b/homeassistant/components/goodwe/number.py index fc8b3864ae9..d54fb8d8d0c 100644 --- a/homeassistant/components/goodwe/number.py +++ b/homeassistant/components/goodwe/number.py @@ -63,7 +63,7 @@ NUMBERS = ( native_unit_of_measurement=PERCENTAGE, native_step=1, native_min_value=0, - native_max_value=100, + native_max_value=200, getter=lambda inv: inv.get_grid_export_limit(), setter=lambda inv, val: inv.set_grid_export_limit(val), filter=lambda inv: _get_setting_unit(inv, "grid_export_limit") == "%", From 11f86d9e0b0e54a9ef85e0ede2d212cb7c3ee677 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Wed, 8 May 2024 14:16:08 -0500 Subject: [PATCH 056/114] Improve config entry has already been setup error message (#117091) --- homeassistant/helpers/entity_component.py | 5 ++++- tests/helpers/test_entity_component.py | 9 ++++++++- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/homeassistant/helpers/entity_component.py b/homeassistant/helpers/entity_component.py index eb54d83e1dd..aae0e2058e4 100644 --- a/homeassistant/helpers/entity_component.py +++ b/homeassistant/helpers/entity_component.py @@ -182,7 +182,10 @@ class EntityComponent(Generic[_EntityT]): key = config_entry.entry_id if key in self._platforms: - raise ValueError("Config entry has already been setup!") + raise ValueError( + f"Config entry {config_entry.title} ({key}) for " + f"{platform_type}.{self.domain} has already been setup!" + ) self._platforms[key] = self._async_init_entity_platform( platform_type, diff --git a/tests/helpers/test_entity_component.py b/tests/helpers/test_entity_component.py index 60d0774b549..330876aae05 100644 --- a/tests/helpers/test_entity_component.py +++ b/tests/helpers/test_entity_component.py @@ -3,6 +3,7 @@ from collections import OrderedDict from datetime import timedelta import logging +import re from unittest.mock import AsyncMock, Mock, patch from freezegun import freeze_time @@ -365,7 +366,13 @@ async def test_setup_entry_fails_duplicate(hass: HomeAssistant) -> None: assert await component.async_setup_entry(entry) - with pytest.raises(ValueError): + with pytest.raises( + ValueError, + match=re.escape( + f"Config entry Mock Title ({entry.entry_id}) for " + "entry_domain.test_domain has already been setup!" + ), + ): await component.async_setup_entry(entry) From b9ed2dab5faa5b1ddf29015376379f0017bb095f Mon Sep 17 00:00:00 2001 From: MatthewFlamm <39341281+MatthewFlamm@users.noreply.github.com> Date: Wed, 8 May 2024 15:16:20 -0400 Subject: [PATCH 057/114] Fix nws blocking startup (#117094) Co-authored-by: J. Nick Koston --- homeassistant/components/nws/__init__.py | 68 ++++++++++++++++-------- 1 file changed, 47 insertions(+), 21 deletions(-) diff --git a/homeassistant/components/nws/__init__.py b/homeassistant/components/nws/__init__.py index 840d4d917f7..df8cb4c329c 100644 --- a/homeassistant/components/nws/__init__.py +++ b/homeassistant/components/nws/__init__.py @@ -2,8 +2,10 @@ from __future__ import annotations +from collections.abc import Awaitable, Callable from dataclasses import dataclass import datetime +from functools import partial import logging from pynws import SimpleNWS, call_with_retry @@ -58,36 +60,49 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: nws_data = SimpleNWS(latitude, longitude, api_key, client_session) await nws_data.set_station(station) - async def update_observation() -> None: - """Retrieve recent observations.""" - await call_with_retry( - nws_data.update_observation, - RETRY_INTERVAL, - RETRY_STOP, - start_time=utcnow() - UPDATE_TIME_PERIOD, - ) + def async_setup_update_observation( + retry_interval: datetime.timedelta | float, + retry_stop: datetime.timedelta | float, + ) -> Callable[[], Awaitable[None]]: + async def update_observation() -> None: + """Retrieve recent observations.""" + await call_with_retry( + nws_data.update_observation, + retry_interval, + retry_stop, + start_time=utcnow() - UPDATE_TIME_PERIOD, + ) - async def update_forecast() -> None: - """Retrieve twice-daily forecsat.""" - await call_with_retry( + return update_observation + + def async_setup_update_forecast( + retry_interval: datetime.timedelta | float, + retry_stop: datetime.timedelta | float, + ) -> Callable[[], Awaitable[None]]: + return partial( + call_with_retry, nws_data.update_forecast, - RETRY_INTERVAL, - RETRY_STOP, + retry_interval, + retry_stop, ) - async def update_forecast_hourly() -> None: - """Retrieve hourly forecast.""" - await call_with_retry( + def async_setup_update_forecast_hourly( + retry_interval: datetime.timedelta | float, + retry_stop: datetime.timedelta | float, + ) -> Callable[[], Awaitable[None]]: + return partial( + call_with_retry, nws_data.update_forecast_hourly, - RETRY_INTERVAL, - RETRY_STOP, + retry_interval, + retry_stop, ) + # Don't use retries in setup coordinator_observation = TimestampDataUpdateCoordinator( hass, _LOGGER, name=f"NWS observation station {station}", - update_method=update_observation, + update_method=async_setup_update_observation(0, 0), update_interval=DEFAULT_SCAN_INTERVAL, request_refresh_debouncer=debounce.Debouncer( hass, _LOGGER, cooldown=DEBOUNCE_TIME, immediate=True @@ -98,7 +113,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: hass, _LOGGER, name=f"NWS forecast station {station}", - update_method=update_forecast, + update_method=async_setup_update_forecast(0, 0), update_interval=DEFAULT_SCAN_INTERVAL, request_refresh_debouncer=debounce.Debouncer( hass, _LOGGER, cooldown=DEBOUNCE_TIME, immediate=True @@ -109,7 +124,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: hass, _LOGGER, name=f"NWS forecast hourly station {station}", - update_method=update_forecast_hourly, + update_method=async_setup_update_forecast_hourly(0, 0), update_interval=DEFAULT_SCAN_INTERVAL, request_refresh_debouncer=debounce.Debouncer( hass, _LOGGER, cooldown=DEBOUNCE_TIME, immediate=True @@ -128,6 +143,17 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: await coordinator_forecast.async_refresh() await coordinator_forecast_hourly.async_refresh() + # Use retries + coordinator_observation.update_method = async_setup_update_observation( + RETRY_INTERVAL, RETRY_STOP + ) + coordinator_forecast.update_method = async_setup_update_forecast( + RETRY_INTERVAL, RETRY_STOP + ) + coordinator_forecast_hourly.update_method = async_setup_update_forecast_hourly( + RETRY_INTERVAL, RETRY_STOP + ) + await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS) return True From c0cd76b3bfdf066e43a41b476a5381cd091ff5c7 Mon Sep 17 00:00:00 2001 From: Jan Bouwhuis Date: Wed, 8 May 2024 21:42:11 +0200 Subject: [PATCH 058/114] Make the mqtt discovery update tasks eager and fix race (#117105) * Fix mqtt discovery race for update rapidly followed on creation * Revert unrelated renaming local var --- homeassistant/components/mqtt/mixins.py | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/homeassistant/components/mqtt/mixins.py b/homeassistant/components/mqtt/mixins.py index 63df7c71c09..68173da7297 100644 --- a/homeassistant/components/mqtt/mixins.py +++ b/homeassistant/components/mqtt/mixins.py @@ -1015,8 +1015,7 @@ class MqttDiscoveryUpdate(Entity): self.hass.async_create_task( _async_process_discovery_update_and_remove( payload, self._discovery_data - ), - eager_start=False, + ) ) elif self._discovery_update: if old_payload != self._discovery_data[ATTR_DISCOVERY_PAYLOAD]: @@ -1025,8 +1024,7 @@ class MqttDiscoveryUpdate(Entity): self.hass.async_create_task( _async_process_discovery_update( payload, self._discovery_update, self._discovery_data - ), - eager_start=False, + ) ) else: # Non-empty, unchanged payload: Ignore to avoid changing states @@ -1059,6 +1057,15 @@ class MqttDiscoveryUpdate(Entity): # rediscovered after a restart await async_remove_discovery_payload(self.hass, self._discovery_data) + @final + async def add_to_platform_finish(self) -> None: + """Finish adding entity to platform.""" + await super().add_to_platform_finish() + # Only send the discovery done after the entity is fully added + # and the state is written to the state machine. + if self._discovery_data is not None: + send_discovery_done(self.hass, self._discovery_data) + @callback def add_to_platform_abort(self) -> None: """Abort adding an entity to a platform.""" @@ -1218,8 +1225,6 @@ class MqttEntity( self._prepare_subscribe_topics() await self._subscribe_topics() await self.mqtt_async_added_to_hass() - if self._discovery_data is not None: - send_discovery_done(self.hass, self._discovery_data) async def mqtt_async_added_to_hass(self) -> None: """Call before the discovery message is acknowledged. From 09490d9e0a6901ca2af822f589038a198d6ee21e Mon Sep 17 00:00:00 2001 From: mletenay Date: Thu, 9 May 2024 00:17:20 +0200 Subject: [PATCH 059/114] Bump goodwe to 0.3.5 (#117115) --- homeassistant/components/goodwe/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/goodwe/manifest.json b/homeassistant/components/goodwe/manifest.json index 59c259524c8..8506d1fd6af 100644 --- a/homeassistant/components/goodwe/manifest.json +++ b/homeassistant/components/goodwe/manifest.json @@ -6,5 +6,5 @@ "documentation": "https://www.home-assistant.io/integrations/goodwe", "iot_class": "local_polling", "loggers": ["goodwe"], - "requirements": ["goodwe==0.3.4"] + "requirements": ["goodwe==0.3.5"] } diff --git a/requirements_all.txt b/requirements_all.txt index e39f08d66bf..1ee861f25bc 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -952,7 +952,7 @@ glances-api==0.6.0 goalzero==0.2.2 # homeassistant.components.goodwe -goodwe==0.3.4 +goodwe==0.3.5 # homeassistant.components.google_mail # homeassistant.components.google_tasks diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 2939c4e843e..189322bd545 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -781,7 +781,7 @@ glances-api==0.6.0 goalzero==0.2.2 # homeassistant.components.goodwe -goodwe==0.3.4 +goodwe==0.3.5 # homeassistant.components.google_mail # homeassistant.components.google_tasks From 1b519a4610ed2b74420a46c85b973a5fe971b538 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Fri, 10 May 2024 00:47:13 -0500 Subject: [PATCH 060/114] Handle tilt position being None in HKC (#117141) --- .../components/homekit_controller/cover.py | 4 ++- .../homekit_controller/test_cover.py | 34 +++++++++++++++++++ 2 files changed, 37 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/homekit_controller/cover.py b/homeassistant/components/homekit_controller/cover.py index ca041d49e11..d0944db38f8 100644 --- a/homeassistant/components/homekit_controller/cover.py +++ b/homeassistant/components/homekit_controller/cover.py @@ -212,13 +212,15 @@ class HomeKitWindowCover(HomeKitEntity, CoverEntity): ) @property - def current_cover_tilt_position(self) -> int: + def current_cover_tilt_position(self) -> int | None: """Return current position of cover tilt.""" tilt_position = self.service.value(CharacteristicsTypes.VERTICAL_TILT_CURRENT) if not tilt_position: tilt_position = self.service.value( CharacteristicsTypes.HORIZONTAL_TILT_CURRENT ) + if tilt_position is None: + return None # Recalculate to convert from arcdegree scale to percentage scale. if self.is_vertical_tilt: scale = 0.9 diff --git a/tests/components/homekit_controller/test_cover.py b/tests/components/homekit_controller/test_cover.py index 671e9779d30..2157eb51212 100644 --- a/tests/components/homekit_controller/test_cover.py +++ b/tests/components/homekit_controller/test_cover.py @@ -3,6 +3,7 @@ from aiohomekit.model.characteristics import CharacteristicsTypes from aiohomekit.model.services import ServicesTypes +from homeassistant.const import STATE_UNAVAILABLE from homeassistant.core import HomeAssistant from homeassistant.helpers import entity_registry as er @@ -94,6 +95,24 @@ def create_window_covering_service_with_v_tilt_2(accessory): tilt_target.maxValue = 0 +def create_window_covering_service_with_none_tilt(accessory): + """Define a window-covering characteristics as per page 219 of HAP spec. + + This accessory uses None for the tilt value unexpectedly. + """ + service = create_window_covering_service(accessory) + + tilt_current = service.add_char(CharacteristicsTypes.VERTICAL_TILT_CURRENT) + tilt_current.value = None + tilt_current.minValue = -90 + tilt_current.maxValue = 0 + + tilt_target = service.add_char(CharacteristicsTypes.VERTICAL_TILT_TARGET) + tilt_target.value = None + tilt_target.minValue = -90 + tilt_target.maxValue = 0 + + async def test_change_window_cover_state(hass: HomeAssistant) -> None: """Test that we can turn a HomeKit alarm on and off again.""" helper = await setup_test_component(hass, create_window_covering_service) @@ -212,6 +231,21 @@ async def test_read_window_cover_tilt_vertical_2(hass: HomeAssistant) -> None: assert state.attributes["current_tilt_position"] == 83 +async def test_read_window_cover_tilt_missing_tilt(hass: HomeAssistant) -> None: + """Test that missing tilt is handled.""" + helper = await setup_test_component( + hass, create_window_covering_service_with_none_tilt + ) + + await helper.async_update( + ServicesTypes.WINDOW_COVERING, + {CharacteristicsTypes.OBSTRUCTION_DETECTED: True}, + ) + state = await helper.poll_and_get_state() + assert "current_tilt_position" not in state.attributes + assert state.state != STATE_UNAVAILABLE + + async def test_write_window_cover_tilt_horizontal(hass: HomeAssistant) -> None: """Test that horizontal tilt is written correctly.""" helper = await setup_test_component( From 56b38cd8427a2c131d61eddf35f559f9e7942d4c Mon Sep 17 00:00:00 2001 From: Jan Bouwhuis Date: Thu, 9 May 2024 16:31:36 +0200 Subject: [PATCH 061/114] Fix typo in xiaomi_ble translation strings (#117144) --- homeassistant/components/xiaomi_ble/strings.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/xiaomi_ble/strings.json b/homeassistant/components/xiaomi_ble/strings.json index 8ee8bac3fea..048c9bd92e2 100644 --- a/homeassistant/components/xiaomi_ble/strings.json +++ b/homeassistant/components/xiaomi_ble/strings.json @@ -83,7 +83,7 @@ "button_fan": "Button Fan \"{subtype}\"", "button_swing": "Button Swing \"{subtype}\"", "button_decrease_speed": "Button Decrease Speed \"{subtype}\"", - "button_increase_speed": "Button Inrease Speed \"{subtype}\"", + "button_increase_speed": "Button Increase Speed \"{subtype}\"", "button_stop": "Button Stop \"{subtype}\"", "button_light": "Button Light \"{subtype}\"", "button_wind_speed": "Button Wind Speed \"{subtype}\"", From f07c00a05b24f1808a7e30d1361fff2f11d167cd Mon Sep 17 00:00:00 2001 From: Diogo Gomes Date: Fri, 10 May 2024 18:59:28 +0100 Subject: [PATCH 062/114] Bump pytrydan to 0.6.0 (#117162) --- homeassistant/components/v2c/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/v2c/manifest.json b/homeassistant/components/v2c/manifest.json index ce0e9d7b847..fb234d726e8 100644 --- a/homeassistant/components/v2c/manifest.json +++ b/homeassistant/components/v2c/manifest.json @@ -5,5 +5,5 @@ "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/v2c", "iot_class": "local_polling", - "requirements": ["pytrydan==0.4.0"] + "requirements": ["pytrydan==0.6.0"] } diff --git a/requirements_all.txt b/requirements_all.txt index 1ee861f25bc..e0cc726f3e0 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2337,7 +2337,7 @@ pytradfri[async]==9.0.1 pytrafikverket==0.3.10 # homeassistant.components.v2c -pytrydan==0.4.0 +pytrydan==0.6.0 # homeassistant.components.usb pyudev==0.24.1 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 189322bd545..6b9ce0504f5 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1816,7 +1816,7 @@ pytradfri[async]==9.0.1 pytrafikverket==0.3.10 # homeassistant.components.v2c -pytrydan==0.4.0 +pytrydan==0.6.0 # homeassistant.components.usb pyudev==0.24.1 From 2c8b3ac8bbf76f764214b760db82990e32c915ed Mon Sep 17 00:00:00 2001 From: Robert Resch Date: Fri, 10 May 2024 13:33:18 +0200 Subject: [PATCH 063/114] Bump deebot-client to 7.2.0 (#117189) --- homeassistant/components/ecovacs/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/ecovacs/manifest.json b/homeassistant/components/ecovacs/manifest.json index aad04d9ec87..e6bd59e3d12 100644 --- a/homeassistant/components/ecovacs/manifest.json +++ b/homeassistant/components/ecovacs/manifest.json @@ -6,5 +6,5 @@ "documentation": "https://www.home-assistant.io/integrations/ecovacs", "iot_class": "cloud_push", "loggers": ["sleekxmppfs", "sucks", "deebot_client"], - "requirements": ["py-sucks==0.9.9", "deebot-client==7.1.0"] + "requirements": ["py-sucks==0.9.9", "deebot-client==7.2.0"] } diff --git a/requirements_all.txt b/requirements_all.txt index e0cc726f3e0..f0acc214f78 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -697,7 +697,7 @@ debugpy==1.8.1 # decora==0.6 # homeassistant.components.ecovacs -deebot-client==7.1.0 +deebot-client==7.2.0 # homeassistant.components.ihc # homeassistant.components.namecheapdns diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 6b9ce0504f5..47f4f1baf51 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -575,7 +575,7 @@ dbus-fast==2.21.1 debugpy==1.8.1 # homeassistant.components.ecovacs -deebot-client==7.1.0 +deebot-client==7.2.0 # homeassistant.components.ihc # homeassistant.components.namecheapdns From e2da28fbdb3c226f624dce7c18eedc62abcd8c87 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Fri, 10 May 2024 18:14:24 +0000 Subject: [PATCH 064/114] Bump version to 2024.5.3 --- 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 e9e1231712e..4bab6d0f127 100644 --- a/homeassistant/const.py +++ b/homeassistant/const.py @@ -23,7 +23,7 @@ if TYPE_CHECKING: APPLICATION_NAME: Final = "HomeAssistant" MAJOR_VERSION: Final = 2024 MINOR_VERSION: Final = 5 -PATCH_VERSION: Final = "2" +PATCH_VERSION: Final = "3" __short_version__: Final = f"{MAJOR_VERSION}.{MINOR_VERSION}" __version__: Final = f"{__short_version__}.{PATCH_VERSION}" REQUIRED_PYTHON_VER: Final[tuple[int, int, int]] = (3, 12, 0) diff --git a/pyproject.toml b/pyproject.toml index 8fb7839c628..5c24c020e82 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "homeassistant" -version = "2024.5.2" +version = "2024.5.3" license = {text = "Apache-2.0"} description = "Open-source home automation platform running on Python 3." readme = "README.rst" From 52147e519622cf79b4b6f19851f937ad8a22af39 Mon Sep 17 00:00:00 2001 From: amura11 Date: Wed, 15 May 2024 07:01:55 -0600 Subject: [PATCH 065/114] Fix Fully Kiosk set config service (#112840) * Fixed a bug that prevented setting Fully Kiosk config values using a template * Added test to cover change * Fixed issue identified by Ruff * Update services.py --------- Co-authored-by: Erik Montnemery --- .../components/fully_kiosk/services.py | 23 +++++++++++-------- tests/components/fully_kiosk/test_services.py | 16 +++++++++++++ 2 files changed, 29 insertions(+), 10 deletions(-) diff --git a/homeassistant/components/fully_kiosk/services.py b/homeassistant/components/fully_kiosk/services.py index c1e0d89f7a1..b9369198940 100644 --- a/homeassistant/components/fully_kiosk/services.py +++ b/homeassistant/components/fully_kiosk/services.py @@ -69,18 +69,21 @@ async def async_setup_services(hass: HomeAssistant) -> None: async def async_set_config(call: ServiceCall) -> None: """Set a Fully Kiosk Browser config value on the device.""" for coordinator in await collect_coordinators(call.data[ATTR_DEVICE_ID]): + key = call.data[ATTR_KEY] + value = call.data[ATTR_VALUE] + # Fully API has different methods for setting string and bool values. # check if call.data[ATTR_VALUE] is a bool - if isinstance(call.data[ATTR_VALUE], bool) or call.data[ - ATTR_VALUE - ].lower() in ("true", "false"): - await coordinator.fully.setConfigurationBool( - call.data[ATTR_KEY], call.data[ATTR_VALUE] - ) + if isinstance(value, bool) or ( + isinstance(value, str) and value.lower() in ("true", "false") + ): + await coordinator.fully.setConfigurationBool(key, value) else: - await coordinator.fully.setConfigurationString( - call.data[ATTR_KEY], call.data[ATTR_VALUE] - ) + # Convert any int values to string + if isinstance(value, int): + value = str(value) + + await coordinator.fully.setConfigurationString(key, value) # Register all the above services service_mapping = [ @@ -111,7 +114,7 @@ async def async_setup_services(hass: HomeAssistant) -> None: { vol.Required(ATTR_DEVICE_ID): cv.ensure_list, vol.Required(ATTR_KEY): cv.string, - vol.Required(ATTR_VALUE): vol.Any(str, bool), + vol.Required(ATTR_VALUE): vol.Any(str, bool, int), } ) ), diff --git a/tests/components/fully_kiosk/test_services.py b/tests/components/fully_kiosk/test_services.py index eaf00d74a91..ecc81d0f090 100644 --- a/tests/components/fully_kiosk/test_services.py +++ b/tests/components/fully_kiosk/test_services.py @@ -71,6 +71,22 @@ async def test_services( mock_fully_kiosk.setConfigurationString.assert_called_once_with(key, value) + key = "test_key" + value = 1234 + + await hass.services.async_call( + DOMAIN, + SERVICE_SET_CONFIG, + { + ATTR_DEVICE_ID: [device_entry.id], + ATTR_KEY: key, + ATTR_VALUE: value, + }, + blocking=True, + ) + + mock_fully_kiosk.setConfigurationString.assert_called_with(key, str(value)) + key = "test_key" value = "true" await hass.services.async_call( From 4501658a169c9c68329e1873b1c8bac8b5f51460 Mon Sep 17 00:00:00 2001 From: Maikel Punie Date: Wed, 15 May 2024 15:12:47 +0200 Subject: [PATCH 066/114] Mark Duotecno entities unavailable when tcp goes down (#114325) When the tcp connection to the duotecno smartbox goes down, mark all entities as unavailable. --- homeassistant/components/duotecno/entity.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/homeassistant/components/duotecno/entity.py b/homeassistant/components/duotecno/entity.py index 86f61c8a73c..7661080f231 100644 --- a/homeassistant/components/duotecno/entity.py +++ b/homeassistant/components/duotecno/entity.py @@ -41,6 +41,11 @@ class DuotecnoEntity(Entity): """When a unit has an update.""" self.async_write_ha_state() + @property + def available(self) -> bool: + """Available state for the unit.""" + return self._unit.is_available() + _T = TypeVar("_T", bound="DuotecnoEntity") _P = ParamSpec("_P") From afb5e622cda6dae91e92949dea8737ad7254c673 Mon Sep 17 00:00:00 2001 From: Thomas55555 <59625598+Thomas55555@users.noreply.github.com> Date: Thu, 9 May 2024 16:56:26 +0200 Subject: [PATCH 067/114] Catch auth exception in husqvarna automower (#115365) * Catch AuthException in Husqvarna Automower * don't use getattr * raise ConfigEntryAuthFailed --- .../husqvarna_automower/coordinator.py | 9 ++++++- .../husqvarna_automower/test_init.py | 24 +++++++++++++------ 2 files changed, 25 insertions(+), 8 deletions(-) diff --git a/homeassistant/components/husqvarna_automower/coordinator.py b/homeassistant/components/husqvarna_automower/coordinator.py index 8d9588db5b7..817789727ca 100644 --- a/homeassistant/components/husqvarna_automower/coordinator.py +++ b/homeassistant/components/husqvarna_automower/coordinator.py @@ -4,12 +4,17 @@ import asyncio from datetime import timedelta import logging -from aioautomower.exceptions import ApiException, HusqvarnaWSServerHandshakeError +from aioautomower.exceptions import ( + ApiException, + AuthException, + HusqvarnaWSServerHandshakeError, +) from aioautomower.model import MowerAttributes from aioautomower.session import AutomowerSession from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant, callback +from homeassistant.exceptions import ConfigEntryAuthFailed from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed from .const import DOMAIN @@ -46,6 +51,8 @@ class AutomowerDataUpdateCoordinator(DataUpdateCoordinator[dict[str, MowerAttrib return await self.api.get_status() except ApiException as err: raise UpdateFailed(err) from err + except AuthException as err: + raise ConfigEntryAuthFailed(err) from err @callback def callback(self, ws_data: dict[str, MowerAttributes]) -> None: diff --git a/tests/components/husqvarna_automower/test_init.py b/tests/components/husqvarna_automower/test_init.py index dbf1d429eee..387c90cec38 100644 --- a/tests/components/husqvarna_automower/test_init.py +++ b/tests/components/husqvarna_automower/test_init.py @@ -5,7 +5,11 @@ import http import time from unittest.mock import AsyncMock -from aioautomower.exceptions import ApiException, HusqvarnaWSServerHandshakeError +from aioautomower.exceptions import ( + ApiException, + AuthException, + HusqvarnaWSServerHandshakeError, +) from freezegun.api import FrozenDateTimeFactory import pytest from syrupy.assertion import SnapshotAssertion @@ -75,19 +79,25 @@ async def test_expired_token_refresh_failure( assert mock_config_entry.state is expected_state +@pytest.mark.parametrize( + ("exception", "entry_state"), + [ + (ApiException, ConfigEntryState.SETUP_RETRY), + (AuthException, ConfigEntryState.SETUP_ERROR), + ], +) async def test_update_failed( hass: HomeAssistant, mock_automower_client: AsyncMock, mock_config_entry: MockConfigEntry, + exception: Exception, + entry_state: ConfigEntryState, ) -> None: - """Test load and unload entry.""" - getattr(mock_automower_client, "get_status").side_effect = ApiException( - "Test error" - ) + """Test update failed.""" + mock_automower_client.get_status.side_effect = exception("Test error") await setup_integration(hass, mock_config_entry) entry = hass.config_entries.async_entries(DOMAIN)[0] - - assert entry.state is ConfigEntryState.SETUP_RETRY + assert entry.state is entry_state async def test_websocket_not_available( From 652ee1b90dd286dc7c199343fca18230375bb4e1 Mon Sep 17 00:00:00 2001 From: tronikos Date: Mon, 6 May 2024 01:22:22 -0700 Subject: [PATCH 068/114] Avoid exceptions when Gemini responses are blocked (#116847) * Bump google-generativeai to v0.5.2 * Avoid exceptions when Gemini responses are blocked * pytest --snapshot-update * set error response * add test * ruff --- .../__init__.py | 13 ++++--- .../test_init.py | 36 +++++++++++++++++-- 2 files changed, 42 insertions(+), 7 deletions(-) diff --git a/homeassistant/components/google_generative_ai_conversation/__init__.py b/homeassistant/components/google_generative_ai_conversation/__init__.py index e956c288b53..96be366a658 100644 --- a/homeassistant/components/google_generative_ai_conversation/__init__.py +++ b/homeassistant/components/google_generative_ai_conversation/__init__.py @@ -182,11 +182,11 @@ class GoogleGenerativeAIAgent(conversation.AbstractConversationAgent): conversation_id = ulid.ulid_now() messages = [{}, {}] + intent_response = intent.IntentResponse(language=user_input.language) try: prompt = self._async_generate_prompt(raw_prompt) except TemplateError as err: _LOGGER.error("Error rendering prompt: %s", err) - intent_response = intent.IntentResponse(language=user_input.language) intent_response.async_set_error( intent.IntentResponseErrorCode.UNKNOWN, f"Sorry, I had a problem with my template: {err}", @@ -210,7 +210,6 @@ class GoogleGenerativeAIAgent(conversation.AbstractConversationAgent): genai_types.StopCandidateException, ) as err: _LOGGER.error("Error sending message: %s", err) - intent_response = intent.IntentResponse(language=user_input.language) intent_response.async_set_error( intent.IntentResponseErrorCode.UNKNOWN, f"Sorry, I had a problem talking to Google Generative AI: {err}", @@ -220,9 +219,15 @@ class GoogleGenerativeAIAgent(conversation.AbstractConversationAgent): ) _LOGGER.debug("Response: %s", chat_response.parts) + if not chat_response.parts: + intent_response.async_set_error( + intent.IntentResponseErrorCode.UNKNOWN, + "Sorry, I had a problem talking to Google Generative AI. Likely blocked", + ) + return conversation.ConversationResult( + response=intent_response, conversation_id=conversation_id + ) self.history[conversation_id] = chat.history - - intent_response = intent.IntentResponse(language=user_input.language) intent_response.async_set_speech(chat_response.text) return conversation.ConversationResult( response=intent_response, conversation_id=conversation_id diff --git a/tests/components/google_generative_ai_conversation/test_init.py b/tests/components/google_generative_ai_conversation/test_init.py index 07254be9e3f..bdf796b8c44 100644 --- a/tests/components/google_generative_ai_conversation/test_init.py +++ b/tests/components/google_generative_ai_conversation/test_init.py @@ -95,29 +95,59 @@ async def test_default_prompt( suggested_area="Test Area 2", ) with patch("google.generativeai.GenerativeModel") as mock_model: - mock_model.return_value.start_chat.return_value = AsyncMock() + mock_chat = AsyncMock() + mock_model.return_value.start_chat.return_value = mock_chat + chat_response = MagicMock() + mock_chat.send_message_async.return_value = chat_response + chat_response.parts = ["Hi there!"] + chat_response.text = "Hi there!" result = await conversation.async_converse( hass, "hello", None, Context(), agent_id=mock_config_entry.entry_id ) assert result.response.response_type == intent.IntentResponseType.ACTION_DONE + assert result.response.as_dict()["speech"]["plain"]["speech"] == "Hi there!" assert [tuple(mock_call) for mock_call in mock_model.mock_calls] == snapshot async def test_error_handling( hass: HomeAssistant, mock_config_entry: MockConfigEntry, mock_init_component ) -> None: - """Test that the default prompt works.""" + """Test that client errors are caught.""" with patch("google.generativeai.GenerativeModel") as mock_model: mock_chat = AsyncMock() mock_model.return_value.start_chat.return_value = mock_chat - mock_chat.send_message_async.side_effect = ClientError("") + mock_chat.send_message_async.side_effect = ClientError("some error") result = await conversation.async_converse( hass, "hello", None, Context(), agent_id=mock_config_entry.entry_id ) assert result.response.response_type == intent.IntentResponseType.ERROR, result assert result.response.error_code == "unknown", result + assert result.response.as_dict()["speech"]["plain"]["speech"] == ( + "Sorry, I had a problem talking to Google Generative AI: None some error" + ) + + +async def test_blocked_response( + hass: HomeAssistant, mock_config_entry: MockConfigEntry, mock_init_component +) -> None: + """Test response was blocked.""" + with patch("google.generativeai.GenerativeModel") as mock_model: + mock_chat = AsyncMock() + mock_model.return_value.start_chat.return_value = mock_chat + chat_response = MagicMock() + mock_chat.send_message_async.return_value = chat_response + chat_response.parts = [] + result = await conversation.async_converse( + hass, "hello", None, Context(), agent_id=mock_config_entry.entry_id + ) + + assert result.response.response_type == intent.IntentResponseType.ERROR, result + assert result.response.error_code == "unknown", result + assert result.response.as_dict()["speech"]["plain"]["speech"] == ( + "Sorry, I had a problem talking to Google Generative AI. Likely blocked" + ) async def test_template_error( From 9d25d228ab8a315e4fff301d7a3121c36d713e9c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michal=20=C4=8Ciha=C5=99?= Date: Tue, 7 May 2024 19:55:03 +0200 Subject: [PATCH 069/114] Reduce update interval in Ondilo Ico (#116989) Ondilo: reduce update interval The API seems to have sticter rate-limiting and frequent requests fail with HTTP 400. Fixes #116593 --- homeassistant/components/ondilo_ico/coordinator.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/ondilo_ico/coordinator.py b/homeassistant/components/ondilo_ico/coordinator.py index d3e9b4a4e11..9b22cf334f3 100644 --- a/homeassistant/components/ondilo_ico/coordinator.py +++ b/homeassistant/components/ondilo_ico/coordinator.py @@ -24,7 +24,7 @@ class OndiloIcoCoordinator(DataUpdateCoordinator[list[dict[str, Any]]]): hass, logger=_LOGGER, name=DOMAIN, - update_interval=timedelta(minutes=5), + update_interval=timedelta(minutes=20), ) self.api = api From a53b8cc0e2402cc9f6e4bca018825504006dae14 Mon Sep 17 00:00:00 2001 From: Thomas55555 <59625598+Thomas55555@users.noreply.github.com> Date: Mon, 13 May 2024 23:00:51 +0200 Subject: [PATCH 070/114] Add reauth for missing token scope in Husqvarna Automower (#117098) * Add repair for wrong token scope to Husqvarna Automower * avoid new installations with missing scope * tweaks * just reauth * texts * Add link to correct account * Update homeassistant/components/husqvarna_automower/strings.json Co-authored-by: Martin Hjelmare * Update homeassistant/components/husqvarna_automower/strings.json Co-authored-by: Martin Hjelmare * Update homeassistant/components/husqvarna_automower/strings.json Co-authored-by: Martin Hjelmare * Add comment * directly assert mock_missing_scope_config_entry.state is loaded * assert that a flow is started * pass complete url to strings and simplify texts * shorten long line * address review * simplify tests * grammar * remove obsolete fixture * fix test * Update tests/components/husqvarna_automower/test_init.py Co-authored-by: Martin Hjelmare * test if reauth flow has started --------- Co-authored-by: Martin Hjelmare --- .../husqvarna_automower/__init__.py | 5 ++ .../husqvarna_automower/config_flow.py | 27 ++++++++++ .../husqvarna_automower/strings.json | 7 ++- .../husqvarna_automower/conftest.py | 12 +++-- .../husqvarna_automower/test_config_flow.py | 51 +++++++++++++++---- .../husqvarna_automower/test_init.py | 20 ++++++++ 6 files changed, 108 insertions(+), 14 deletions(-) diff --git a/homeassistant/components/husqvarna_automower/__init__.py b/homeassistant/components/husqvarna_automower/__init__.py index fe6f6978014..e4211e1078e 100644 --- a/homeassistant/components/husqvarna_automower/__init__.py +++ b/homeassistant/components/husqvarna_automower/__init__.py @@ -57,6 +57,11 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: hass.data.setdefault(DOMAIN, {})[entry.entry_id] = coordinator + if "amc:api" not in entry.data["token"]["scope"]: + # We raise ConfigEntryAuthFailed here because the websocket can't be used + # without the scope. So only polling would be possible. + raise ConfigEntryAuthFailed + await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS) return True diff --git a/homeassistant/components/husqvarna_automower/config_flow.py b/homeassistant/components/husqvarna_automower/config_flow.py index b25a185c75f..c848f823b13 100644 --- a/homeassistant/components/husqvarna_automower/config_flow.py +++ b/homeassistant/components/husqvarna_automower/config_flow.py @@ -13,7 +13,9 @@ from homeassistant.helpers import config_entry_oauth2_flow from .const import DOMAIN, NAME _LOGGER = logging.getLogger(__name__) + CONF_USER_ID = "user_id" +HUSQVARNA_DEV_PORTAL_URL = "https://developer.husqvarnagroup.cloud/applications" class HusqvarnaConfigFlowHandler( @@ -29,8 +31,14 @@ class HusqvarnaConfigFlowHandler( async def async_oauth_create_entry(self, data: dict[str, Any]) -> ConfigFlowResult: """Create an entry for the flow.""" token = data[CONF_TOKEN] + if "amc:api" not in token["scope"] and not self.reauth_entry: + return self.async_abort(reason="missing_amc_scope") user_id = token[CONF_USER_ID] if self.reauth_entry: + if "amc:api" not in token["scope"]: + return self.async_update_reload_and_abort( + self.reauth_entry, data=data, reason="missing_amc_scope" + ) if self.reauth_entry.unique_id != user_id: return self.async_abort(reason="wrong_account") return self.async_update_reload_and_abort(self.reauth_entry, data=data) @@ -56,6 +64,9 @@ class HusqvarnaConfigFlowHandler( self.reauth_entry = self.hass.config_entries.async_get_entry( self.context["entry_id"] ) + if self.reauth_entry is not None: + if "amc:api" not in self.reauth_entry.data["token"]["scope"]: + return await self.async_step_missing_scope() return await self.async_step_reauth_confirm() async def async_step_reauth_confirm( @@ -65,3 +76,19 @@ class HusqvarnaConfigFlowHandler( if user_input is None: return self.async_show_form(step_id="reauth_confirm") return await self.async_step_user() + + async def async_step_missing_scope( + self, user_input: dict[str, Any] | None = None + ) -> ConfigFlowResult: + """Confirm reauth for missing scope.""" + if user_input is None and self.reauth_entry is not None: + token_structured = structure_token( + self.reauth_entry.data["token"]["access_token"] + ) + return self.async_show_form( + step_id="missing_scope", + description_placeholders={ + "application_url": f"{HUSQVARNA_DEV_PORTAL_URL}/{token_structured.client_id}" + }, + ) + return await self.async_step_user() diff --git a/homeassistant/components/husqvarna_automower/strings.json b/homeassistant/components/husqvarna_automower/strings.json index b4c1c97cd68..ea9a76fc319 100644 --- a/homeassistant/components/husqvarna_automower/strings.json +++ b/homeassistant/components/husqvarna_automower/strings.json @@ -5,6 +5,10 @@ "title": "[%key:common::config_flow::title::reauth%]", "description": "The Husqvarna Automower integration needs to re-authenticate your account" }, + "missing_scope": { + "title": "Your account is missing some API connections", + "description": "For the best experience with this integration both the `Authentication API` and the `Automower Connect API` should be connected. Please make sure that both of them are connected to your account in the [Husqvarna Developer Portal]({application_url})." + }, "pick_implementation": { "title": "[%key:common::config_flow::title::oauth2_pick_implementation%]" } @@ -22,7 +26,8 @@ "oauth_unauthorized": "[%key:common::config_flow::abort::oauth2_unauthorized%]", "oauth_failed": "[%key:common::config_flow::abort::oauth2_failed%]", "reauth_successful": "[%key:common::config_flow::abort::reauth_successful%]", - "wrong_account": "You can only reauthenticate this entry with the same Husqvarna account." + "wrong_account": "You can only reauthenticate this entry with the same Husqvarna account.", + "missing_amc_scope": "The `Authentication API` and the `Automower Connect API` are not connected to your application in the Husqvarna Developer Portal." }, "create_entry": { "default": "[%key:common::config_flow::create_entry::authenticated%]" diff --git a/tests/components/husqvarna_automower/conftest.py b/tests/components/husqvarna_automower/conftest.py index 5d7cb43698b..bf7cced2bca 100644 --- a/tests/components/husqvarna_automower/conftest.py +++ b/tests/components/husqvarna_automower/conftest.py @@ -22,7 +22,7 @@ from tests.common import MockConfigEntry, load_fixture, load_json_value_fixture @pytest.fixture(name="jwt") -def load_jwt_fixture(): +def load_jwt_fixture() -> str: """Load Fixture data.""" return load_fixture("jwt", DOMAIN) @@ -33,8 +33,14 @@ def mock_expires_at() -> float: return time.time() + 3600 +@pytest.fixture(name="scope") +def mock_scope() -> str: + """Fixture to set correct scope for the token.""" + return "iam:read amc:api" + + @pytest.fixture -def mock_config_entry(jwt, expires_at: int) -> MockConfigEntry: +def mock_config_entry(jwt: str, expires_at: int, scope: str) -> MockConfigEntry: """Return the default mocked config entry.""" return MockConfigEntry( version=1, @@ -44,7 +50,7 @@ def mock_config_entry(jwt, expires_at: int) -> MockConfigEntry: "auth_implementation": DOMAIN, "token": { "access_token": jwt, - "scope": "iam:read amc:api", + "scope": scope, "expires_in": 86399, "refresh_token": "3012bc9f-7a65-4240-b817-9154ffdcc30f", "provider": "husqvarna", diff --git a/tests/components/husqvarna_automower/test_config_flow.py b/tests/components/husqvarna_automower/test_config_flow.py index 0a345eed627..bb97a88d44f 100644 --- a/tests/components/husqvarna_automower/test_config_flow.py +++ b/tests/components/husqvarna_automower/test_config_flow.py @@ -2,6 +2,8 @@ from unittest.mock import AsyncMock, patch +import pytest + from homeassistant import config_entries from homeassistant.components.husqvarna_automower.const import ( DOMAIN, @@ -21,12 +23,21 @@ from tests.test_util.aiohttp import AiohttpClientMocker from tests.typing import ClientSessionGenerator +@pytest.mark.parametrize( + ("new_scope", "amount"), + [ + ("iam:read amc:api", 1), + ("iam:read", 0), + ], +) async def test_full_flow( hass: HomeAssistant, hass_client_no_auth, aioclient_mock: AiohttpClientMocker, current_request_with_host, - jwt, + jwt: str, + new_scope: str, + amount: int, ) -> None: """Check full flow.""" result = await hass.config_entries.flow.async_init( @@ -56,7 +67,7 @@ async def test_full_flow( OAUTH2_TOKEN, json={ "access_token": jwt, - "scope": "iam:read amc:api", + "scope": new_scope, "expires_in": 86399, "refresh_token": "mock-refresh-token", "provider": "husqvarna", @@ -72,8 +83,8 @@ async def test_full_flow( ) as mock_setup: await hass.config_entries.flow.async_configure(result["flow_id"]) - assert len(hass.config_entries.async_entries(DOMAIN)) == 1 - assert len(mock_setup.mock_calls) == 1 + assert len(hass.config_entries.async_entries(DOMAIN)) == amount + assert len(mock_setup.mock_calls) == amount async def test_config_non_unique_profile( @@ -129,6 +140,14 @@ async def test_config_non_unique_profile( assert result["reason"] == "already_configured" +@pytest.mark.parametrize( + ("scope", "step_id", "reason", "new_scope"), + [ + ("iam:read amc:api", "reauth_confirm", "reauth_successful", "iam:read amc:api"), + ("iam:read", "missing_scope", "reauth_successful", "iam:read amc:api"), + ("iam:read", "missing_scope", "missing_amc_scope", "iam:read"), + ], +) async def test_reauth( hass: HomeAssistant, hass_client_no_auth: ClientSessionGenerator, @@ -136,7 +155,10 @@ async def test_reauth( mock_config_entry: MockConfigEntry, current_request_with_host: None, mock_automower_client: AsyncMock, - jwt, + jwt: str, + step_id: str, + new_scope: str, + reason: str, ) -> None: """Test the reauthentication case updates the existing config entry.""" @@ -148,7 +170,7 @@ async def test_reauth( flows = hass.config_entries.flow.async_progress() assert len(flows) == 1 result = flows[0] - assert result["step_id"] == "reauth_confirm" + assert result["step_id"] == step_id result = await hass.config_entries.flow.async_configure(result["flow_id"], {}) state = config_entry_oauth2_flow._encode_jwt( @@ -172,7 +194,7 @@ async def test_reauth( OAUTH2_TOKEN, json={ "access_token": "mock-updated-token", - "scope": "iam:read amc:api", + "scope": new_scope, "expires_in": 86399, "refresh_token": "mock-refresh-token", "provider": "husqvarna", @@ -191,7 +213,7 @@ async def test_reauth( assert len(hass.config_entries.async_entries(DOMAIN)) == 1 assert result.get("type") is FlowResultType.ABORT - assert result.get("reason") == "reauth_successful" + assert result.get("reason") == reason assert mock_config_entry.unique_id == USER_ID assert "token" in mock_config_entry.data @@ -200,6 +222,12 @@ async def test_reauth( assert mock_config_entry.data["token"].get("refresh_token") == "mock-refresh-token" +@pytest.mark.parametrize( + ("user_id", "reason"), + [ + ("wrong_user_id", "wrong_account"), + ], +) async def test_reauth_wrong_account( hass: HomeAssistant, hass_client_no_auth: ClientSessionGenerator, @@ -208,6 +236,9 @@ async def test_reauth_wrong_account( current_request_with_host: None, mock_automower_client: AsyncMock, jwt, + user_id: str, + reason: str, + scope: str, ) -> None: """Test the reauthentication aborts, if user tries to reauthenticate with another account.""" @@ -247,7 +278,7 @@ async def test_reauth_wrong_account( "expires_in": 86399, "refresh_token": "mock-refresh-token", "provider": "husqvarna", - "user_id": "wrong-user-id", + "user_id": user_id, "token_type": "Bearer", "expires_at": 1697753347, }, @@ -262,7 +293,7 @@ async def test_reauth_wrong_account( assert len(hass.config_entries.async_entries(DOMAIN)) == 1 assert result.get("type") is FlowResultType.ABORT - assert result.get("reason") == "wrong_account" + assert result.get("reason") == reason assert mock_config_entry.unique_id == USER_ID assert "token" in mock_config_entry.data diff --git a/tests/components/husqvarna_automower/test_init.py b/tests/components/husqvarna_automower/test_init.py index 387c90cec38..84fe1b9e891 100644 --- a/tests/components/husqvarna_automower/test_init.py +++ b/tests/components/husqvarna_automower/test_init.py @@ -43,6 +43,26 @@ async def test_load_unload_entry( assert entry.state is ConfigEntryState.NOT_LOADED +@pytest.mark.parametrize( + ("scope"), + [ + ("iam:read"), + ], +) +async def test_load_missing_scope( + hass: HomeAssistant, + mock_automower_client: AsyncMock, + mock_config_entry: MockConfigEntry, +) -> None: + """Test if the entry starts a reauth with the missing token scope.""" + await setup_integration(hass, mock_config_entry) + assert mock_config_entry.state is ConfigEntryState.SETUP_ERROR + flows = hass.config_entries.flow.async_progress() + assert len(flows) == 1 + result = flows[0] + assert result["step_id"] == "missing_scope" + + @pytest.mark.parametrize( ("expires_at", "status", "expected_state"), [ From 5941cf05e4f1053d323bc2bccd8c4e106455cbee Mon Sep 17 00:00:00 2001 From: Tom Harris Date: Thu, 16 May 2024 21:45:03 -0400 Subject: [PATCH 071/114] Fix issue changing Insteon Hub configuration (#117204) Add Hub version to config schema --- homeassistant/components/insteon/schemas.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/homeassistant/components/insteon/schemas.py b/homeassistant/components/insteon/schemas.py index 837c6224014..4cf8d49d170 100644 --- a/homeassistant/components/insteon/schemas.py +++ b/homeassistant/components/insteon/schemas.py @@ -22,6 +22,7 @@ from .const import ( CONF_CAT, CONF_DIM_STEPS, CONF_HOUSECODE, + CONF_HUB_VERSION, CONF_SUBCAT, CONF_UNITCODE, HOUSECODES, @@ -143,6 +144,7 @@ def build_hub_schema( schema = { vol.Required(CONF_HOST, default=host): str, vol.Required(CONF_PORT, default=port): int, + vol.Required(CONF_HUB_VERSION, default=hub_version): int, } if hub_version == 2: schema[vol.Required(CONF_USERNAME, default=username)] = str From 17c6a49ff82743c72de62fc85ac8b3f9e7f670e3 Mon Sep 17 00:00:00 2001 From: jjlawren Date: Fri, 10 May 2024 20:38:38 -0500 Subject: [PATCH 072/114] Bump SoCo to 0.30.4 (#117212) --- homeassistant/components/sonos/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/sonos/manifest.json b/homeassistant/components/sonos/manifest.json index ec5ef90a0c1..d6c5eb298d8 100644 --- a/homeassistant/components/sonos/manifest.json +++ b/homeassistant/components/sonos/manifest.json @@ -8,7 +8,7 @@ "documentation": "https://www.home-assistant.io/integrations/sonos", "iot_class": "local_push", "loggers": ["soco"], - "requirements": ["soco==0.30.3", "sonos-websocket==0.1.3"], + "requirements": ["soco==0.30.4", "sonos-websocket==0.1.3"], "ssdp": [ { "st": "urn:schemas-upnp-org:device:ZonePlayer:1" diff --git a/requirements_all.txt b/requirements_all.txt index f0acc214f78..d867cd826bc 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2572,7 +2572,7 @@ smhi-pkg==1.0.16 snapcast==2.3.6 # homeassistant.components.sonos -soco==0.30.3 +soco==0.30.4 # homeassistant.components.solaredge_local solaredge-local==0.2.3 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 47f4f1baf51..640c4cfcfd1 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1991,7 +1991,7 @@ smhi-pkg==1.0.16 snapcast==2.3.6 # homeassistant.components.sonos -soco==0.30.3 +soco==0.30.4 # homeassistant.components.solax solax==3.1.0 From 57cf91a8d4b2d189a3d6c69aa83ff4a139294384 Mon Sep 17 00:00:00 2001 From: Raman Gupta <7243222+raman325@users.noreply.github.com> Date: Sat, 11 May 2024 11:41:03 -0400 Subject: [PATCH 073/114] Fix zwave_js discovery logic for node device class (#117232) * Fix zwave_js discovery logic for node device class * simplify check --- .../components/zwave_js/discovery.py | 29 +- tests/components/zwave_js/conftest.py | 14 + .../light_device_class_is_null_state.json | 10611 ++++++++++++++++ tests/components/zwave_js/test_discovery.py | 12 + 4 files changed, 10649 insertions(+), 17 deletions(-) create mode 100644 tests/components/zwave_js/fixtures/light_device_class_is_null_state.json diff --git a/homeassistant/components/zwave_js/discovery.py b/homeassistant/components/zwave_js/discovery.py index 272f6e3ddc0..4e2b59109e8 100644 --- a/homeassistant/components/zwave_js/discovery.py +++ b/homeassistant/components/zwave_js/discovery.py @@ -41,7 +41,6 @@ from zwave_js_server.const.command_class.thermostat import ( THERMOSTAT_SETPOINT_PROPERTY, ) from zwave_js_server.exceptions import UnknownValueData -from zwave_js_server.model.device_class import DeviceClassItem from zwave_js_server.model.node import Node as ZwaveNode from zwave_js_server.model.value import ( ConfigurationValue, @@ -1180,14 +1179,22 @@ def async_discover_single_value( continue # check device_class_generic - if value.node.device_class and not check_device_class( - value.node.device_class.generic, schema.device_class_generic + if schema.device_class_generic and ( + not value.node.device_class + or not any( + value.node.device_class.generic.label == val + for val in schema.device_class_generic + ) ): continue # check device_class_specific - if value.node.device_class and not check_device_class( - value.node.device_class.specific, schema.device_class_specific + if schema.device_class_specific and ( + not value.node.device_class + or not any( + value.node.device_class.specific.label == val + for val in schema.device_class_specific + ) ): continue @@ -1379,15 +1386,3 @@ def check_value(value: ZwaveValue, schema: ZWaveValueDiscoverySchema) -> bool: if schema.stateful is not None and value.metadata.stateful != schema.stateful: return False return True - - -@callback -def check_device_class( - device_class: DeviceClassItem, required_value: set[str] | None -) -> bool: - """Check if device class id or label matches.""" - if required_value is None: - return True - if any(device_class.label == val for val in required_value): - return True - return False diff --git a/tests/components/zwave_js/conftest.py b/tests/components/zwave_js/conftest.py index dbf7357d4a0..f6497492b8b 100644 --- a/tests/components/zwave_js/conftest.py +++ b/tests/components/zwave_js/conftest.py @@ -675,6 +675,12 @@ def central_scene_node_state_fixture(): return json.loads(load_fixture("zwave_js/central_scene_node_state.json")) +@pytest.fixture(name="light_device_class_is_null_state", scope="package") +def light_device_class_is_null_state_fixture(): + """Load node with device class is None state fixture data.""" + return json.loads(load_fixture("zwave_js/light_device_class_is_null_state.json")) + + # model fixtures @@ -1325,3 +1331,11 @@ def central_scene_node_fixture(client, central_scene_node_state): node = Node(client, copy.deepcopy(central_scene_node_state)) client.driver.controller.nodes[node.node_id] = node return node + + +@pytest.fixture(name="light_device_class_is_null") +def light_device_class_is_null_fixture(client, light_device_class_is_null_state): + """Mock a node when device class is null.""" + node = Node(client, copy.deepcopy(light_device_class_is_null_state)) + client.driver.controller.nodes[node.node_id] = node + return node diff --git a/tests/components/zwave_js/fixtures/light_device_class_is_null_state.json b/tests/components/zwave_js/fixtures/light_device_class_is_null_state.json new file mode 100644 index 00000000000..e736c432062 --- /dev/null +++ b/tests/components/zwave_js/fixtures/light_device_class_is_null_state.json @@ -0,0 +1,10611 @@ +{ + "nodeId": 45, + "index": 0, + "installerIcon": 1536, + "userIcon": 1536, + "status": 4, + "ready": true, + "isListening": true, + "isRouting": true, + "isSecure": false, + "manufacturerId": 29, + "productId": 1, + "productType": 12801, + "firmwareVersion": "1.20", + "zwavePlusVersion": 1, + "name": "Bar Display Cases", + "location": "**REDACTED**", + "deviceConfig": { + "filename": "/Users/spike/zwavestore/.config-db/devices/0x001d/dz6hd.json", + "isEmbedded": true, + "manufacturer": "Leviton", + "manufacturerId": 29, + "label": "DZ6HD", + "description": "In-Wall 600W Dimmer", + "devices": [ + { + "productType": 12801, + "productId": 1 + } + ], + "firmwareVersion": { + "min": "0.0", + "max": "255.255" + }, + "preferred": false, + "paramInformation": { + "_map": {} + }, + "metadata": { + "inclusion": "Enter programming mode by holding down the top of the paddle for 7 seconds, the LED will blink Amber. Tap the top of the paddle one time. The LED will flash green. Upon successful addition to network, the LED will blink 3 times.", + "exclusion": "Enter programming mode by holding down the top of the paddle for 7 seconds, the LED will blink Amber. Tap the top of the paddle one time. The LED will flash green. Upon successful removal from network, the LED will blink 3 times.", + "reset": "Hold the top of the paddle down for 14 seconds. Upon successful reset, the LED with blink red/amber.", + "manual": "https://www.leviton.com/fr/docs/DI-000-DZ6HD-02A-W.pdf" + } + }, + "label": "DZ6HD", + "interviewAttempts": 0, + "isFrequentListening": false, + "maxDataRate": 100000, + "supportedDataRates": [40000, 100000], + "protocolVersion": 3, + "supportsBeaming": true, + "supportsSecurity": false, + "nodeType": 1, + "zwavePlusNodeType": 0, + "zwavePlusRoleType": 5, + "deviceClass": null, + "interviewStage": "Complete", + "deviceDatabaseUrl": "https://devices.zwave-js.io/?jumpTo=0x001d:0x3201:0x0001:1.20", + "statistics": { + "commandsTX": 1, + "commandsRX": 0, + "commandsDroppedRX": 0, + "commandsDroppedTX": 0, + "timeoutResponse": 0, + "rtt": 31.5, + "lastSeen": "2024-05-10T21:42:42.472Z", + "lwr": { + "repeaters": [], + "protocolDataRate": 3 + } + }, + "highestSecurityClass": -1, + "isControllerNode": false, + "keepAwake": false, + "lastSeen": "2024-05-10T21:42:42.472Z", + "values": [ + { + "endpoint": 0, + "commandClass": 38, + "commandClassName": "Multilevel Switch", + "property": "targetValue", + "propertyName": "targetValue", + "ccVersion": 4, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "label": "Target value", + "valueChangeOptions": ["transitionDuration"], + "min": 0, + "max": 99, + "stateful": true, + "secret": false + }, + "value": 0 + }, + { + "endpoint": 0, + "commandClass": 38, + "commandClassName": "Multilevel Switch", + "property": "duration", + "propertyName": "duration", + "ccVersion": 4, + "metadata": { + "type": "duration", + "readable": true, + "writeable": false, + "label": "Remaining duration", + "stateful": true, + "secret": false + }, + "value": { + "value": 1, + "unit": "seconds" + } + }, + { + "endpoint": 0, + "commandClass": 38, + "commandClassName": "Multilevel Switch", + "property": "currentValue", + "propertyName": "currentValue", + "ccVersion": 4, + "metadata": { + "type": "number", + "readable": true, + "writeable": false, + "label": "Current value", + "min": 0, + "max": 99, + "stateful": true, + "secret": false + }, + "value": 0 + }, + { + "endpoint": 0, + "commandClass": 38, + "commandClassName": "Multilevel Switch", + "property": "Up", + "propertyName": "Up", + "ccVersion": 4, + "metadata": { + "type": "boolean", + "readable": false, + "writeable": true, + "label": "Perform a level change (Up)", + "ccSpecific": { + "switchType": 2 + }, + "valueChangeOptions": ["transitionDuration"], + "states": { + "true": "Start", + "false": "Stop" + }, + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 38, + "commandClassName": "Multilevel Switch", + "property": "Down", + "propertyName": "Down", + "ccVersion": 4, + "metadata": { + "type": "boolean", + "readable": false, + "writeable": true, + "label": "Perform a level change (Down)", + "ccSpecific": { + "switchType": 2 + }, + "valueChangeOptions": ["transitionDuration"], + "states": { + "true": "Start", + "false": "Stop" + }, + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 38, + "commandClassName": "Multilevel Switch", + "property": "restorePrevious", + "propertyName": "restorePrevious", + "ccVersion": 4, + "metadata": { + "type": "boolean", + "readable": false, + "writeable": true, + "label": "Restore previous value", + "states": { + "true": "Restore" + }, + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 43, + "commandClassName": "Scene Activation", + "property": "sceneId", + "propertyName": "sceneId", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "label": "Scene ID", + "valueChangeOptions": ["transitionDuration"], + "min": 1, + "max": 255, + "stateful": false, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 43, + "commandClassName": "Scene Activation", + "property": "dimmingDuration", + "propertyName": "dimmingDuration", + "ccVersion": 1, + "metadata": { + "type": "duration", + "readable": true, + "writeable": true, + "label": "Dimming duration", + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "level", + "propertyKey": 1, + "propertyName": "level", + "propertyKeyName": "1", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "label": "Level (1)", + "valueChangeOptions": ["transitionDuration"], + "min": 0, + "max": 255, + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "dimmingDuration", + "propertyKey": 1, + "propertyName": "dimmingDuration", + "propertyKeyName": "1", + "ccVersion": 1, + "metadata": { + "type": "duration", + "readable": true, + "writeable": true, + "label": "Dimming duration (1)", + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "level", + "propertyKey": 2, + "propertyName": "level", + "propertyKeyName": "2", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "label": "Level (2)", + "valueChangeOptions": ["transitionDuration"], + "min": 0, + "max": 255, + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "dimmingDuration", + "propertyKey": 2, + "propertyName": "dimmingDuration", + "propertyKeyName": "2", + "ccVersion": 1, + "metadata": { + "type": "duration", + "readable": true, + "writeable": true, + "label": "Dimming duration (2)", + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "level", + "propertyKey": 3, + "propertyName": "level", + "propertyKeyName": "3", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "label": "Level (3)", + "valueChangeOptions": ["transitionDuration"], + "min": 0, + "max": 255, + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "dimmingDuration", + "propertyKey": 3, + "propertyName": "dimmingDuration", + "propertyKeyName": "3", + "ccVersion": 1, + "metadata": { + "type": "duration", + "readable": true, + "writeable": true, + "label": "Dimming duration (3)", + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "level", + "propertyKey": 4, + "propertyName": "level", + "propertyKeyName": "4", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "label": "Level (4)", + "valueChangeOptions": ["transitionDuration"], + "min": 0, + "max": 255, + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "dimmingDuration", + "propertyKey": 4, + "propertyName": "dimmingDuration", + "propertyKeyName": "4", + "ccVersion": 1, + "metadata": { + "type": "duration", + "readable": true, + "writeable": true, + "label": "Dimming duration (4)", + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "level", + "propertyKey": 5, + "propertyName": "level", + "propertyKeyName": "5", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "label": "Level (5)", + "valueChangeOptions": ["transitionDuration"], + "min": 0, + "max": 255, + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "dimmingDuration", + "propertyKey": 5, + "propertyName": "dimmingDuration", + "propertyKeyName": "5", + "ccVersion": 1, + "metadata": { + "type": "duration", + "readable": true, + "writeable": true, + "label": "Dimming duration (5)", + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "level", + "propertyKey": 6, + "propertyName": "level", + "propertyKeyName": "6", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "label": "Level (6)", + "valueChangeOptions": ["transitionDuration"], + "min": 0, + "max": 255, + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "dimmingDuration", + "propertyKey": 6, + "propertyName": "dimmingDuration", + "propertyKeyName": "6", + "ccVersion": 1, + "metadata": { + "type": "duration", + "readable": true, + "writeable": true, + "label": "Dimming duration (6)", + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "level", + "propertyKey": 7, + "propertyName": "level", + "propertyKeyName": "7", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "label": "Level (7)", + "valueChangeOptions": ["transitionDuration"], + "min": 0, + "max": 255, + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "dimmingDuration", + "propertyKey": 7, + "propertyName": "dimmingDuration", + "propertyKeyName": "7", + "ccVersion": 1, + "metadata": { + "type": "duration", + "readable": true, + "writeable": true, + "label": "Dimming duration (7)", + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "level", + "propertyKey": 8, + "propertyName": "level", + "propertyKeyName": "8", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "label": "Level (8)", + "valueChangeOptions": ["transitionDuration"], + "min": 0, + "max": 255, + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "dimmingDuration", + "propertyKey": 8, + "propertyName": "dimmingDuration", + "propertyKeyName": "8", + "ccVersion": 1, + "metadata": { + "type": "duration", + "readable": true, + "writeable": true, + "label": "Dimming duration (8)", + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "level", + "propertyKey": 9, + "propertyName": "level", + "propertyKeyName": "9", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "label": "Level (9)", + "valueChangeOptions": ["transitionDuration"], + "min": 0, + "max": 255, + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "dimmingDuration", + "propertyKey": 9, + "propertyName": "dimmingDuration", + "propertyKeyName": "9", + "ccVersion": 1, + "metadata": { + "type": "duration", + "readable": true, + "writeable": true, + "label": "Dimming duration (9)", + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "level", + "propertyKey": 10, + "propertyName": "level", + "propertyKeyName": "10", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "label": "Level (10)", + "valueChangeOptions": ["transitionDuration"], + "min": 0, + "max": 255, + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "dimmingDuration", + "propertyKey": 10, + "propertyName": "dimmingDuration", + "propertyKeyName": "10", + "ccVersion": 1, + "metadata": { + "type": "duration", + "readable": true, + "writeable": true, + "label": "Dimming duration (10)", + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "level", + "propertyKey": 11, + "propertyName": "level", + "propertyKeyName": "11", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "label": "Level (11)", + "valueChangeOptions": ["transitionDuration"], + "min": 0, + "max": 255, + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "dimmingDuration", + "propertyKey": 11, + "propertyName": "dimmingDuration", + "propertyKeyName": "11", + "ccVersion": 1, + "metadata": { + "type": "duration", + "readable": true, + "writeable": true, + "label": "Dimming duration (11)", + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "level", + "propertyKey": 12, + "propertyName": "level", + "propertyKeyName": "12", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "label": "Level (12)", + "valueChangeOptions": ["transitionDuration"], + "min": 0, + "max": 255, + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "dimmingDuration", + "propertyKey": 12, + "propertyName": "dimmingDuration", + "propertyKeyName": "12", + "ccVersion": 1, + "metadata": { + "type": "duration", + "readable": true, + "writeable": true, + "label": "Dimming duration (12)", + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "level", + "propertyKey": 13, + "propertyName": "level", + "propertyKeyName": "13", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "label": "Level (13)", + "valueChangeOptions": ["transitionDuration"], + "min": 0, + "max": 255, + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "dimmingDuration", + "propertyKey": 13, + "propertyName": "dimmingDuration", + "propertyKeyName": "13", + "ccVersion": 1, + "metadata": { + "type": "duration", + "readable": true, + "writeable": true, + "label": "Dimming duration (13)", + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "level", + "propertyKey": 14, + "propertyName": "level", + "propertyKeyName": "14", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "label": "Level (14)", + "valueChangeOptions": ["transitionDuration"], + "min": 0, + "max": 255, + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "dimmingDuration", + "propertyKey": 14, + "propertyName": "dimmingDuration", + "propertyKeyName": "14", + "ccVersion": 1, + "metadata": { + "type": "duration", + "readable": true, + "writeable": true, + "label": "Dimming duration (14)", + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "level", + "propertyKey": 15, + "propertyName": "level", + "propertyKeyName": "15", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "label": "Level (15)", + "valueChangeOptions": ["transitionDuration"], + "min": 0, + "max": 255, + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "dimmingDuration", + "propertyKey": 15, + "propertyName": "dimmingDuration", + "propertyKeyName": "15", + "ccVersion": 1, + "metadata": { + "type": "duration", + "readable": true, + "writeable": true, + "label": "Dimming duration (15)", + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "level", + "propertyKey": 16, + "propertyName": "level", + "propertyKeyName": "16", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "label": "Level (16)", + "valueChangeOptions": ["transitionDuration"], + "min": 0, + "max": 255, + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "dimmingDuration", + "propertyKey": 16, + "propertyName": "dimmingDuration", + "propertyKeyName": "16", + "ccVersion": 1, + "metadata": { + "type": "duration", + "readable": true, + "writeable": true, + "label": "Dimming duration (16)", + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "level", + "propertyKey": 17, + "propertyName": "level", + "propertyKeyName": "17", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "label": "Level (17)", + "valueChangeOptions": ["transitionDuration"], + "min": 0, + "max": 255, + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "dimmingDuration", + "propertyKey": 17, + "propertyName": "dimmingDuration", + "propertyKeyName": "17", + "ccVersion": 1, + "metadata": { + "type": "duration", + "readable": true, + "writeable": true, + "label": "Dimming duration (17)", + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "level", + "propertyKey": 18, + "propertyName": "level", + "propertyKeyName": "18", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "label": "Level (18)", + "valueChangeOptions": ["transitionDuration"], + "min": 0, + "max": 255, + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "dimmingDuration", + "propertyKey": 18, + "propertyName": "dimmingDuration", + "propertyKeyName": "18", + "ccVersion": 1, + "metadata": { + "type": "duration", + "readable": true, + "writeable": true, + "label": "Dimming duration (18)", + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "level", + "propertyKey": 19, + "propertyName": "level", + "propertyKeyName": "19", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "label": "Level (19)", + "valueChangeOptions": ["transitionDuration"], + "min": 0, + "max": 255, + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "dimmingDuration", + "propertyKey": 19, + "propertyName": "dimmingDuration", + "propertyKeyName": "19", + "ccVersion": 1, + "metadata": { + "type": "duration", + "readable": true, + "writeable": true, + "label": "Dimming duration (19)", + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "level", + "propertyKey": 20, + "propertyName": "level", + "propertyKeyName": "20", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "label": "Level (20)", + "valueChangeOptions": ["transitionDuration"], + "min": 0, + "max": 255, + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "dimmingDuration", + "propertyKey": 20, + "propertyName": "dimmingDuration", + "propertyKeyName": "20", + "ccVersion": 1, + "metadata": { + "type": "duration", + "readable": true, + "writeable": true, + "label": "Dimming duration (20)", + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "level", + "propertyKey": 21, + "propertyName": "level", + "propertyKeyName": "21", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "label": "Level (21)", + "valueChangeOptions": ["transitionDuration"], + "min": 0, + "max": 255, + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "dimmingDuration", + "propertyKey": 21, + "propertyName": "dimmingDuration", + "propertyKeyName": "21", + "ccVersion": 1, + "metadata": { + "type": "duration", + "readable": true, + "writeable": true, + "label": "Dimming duration (21)", + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "level", + "propertyKey": 22, + "propertyName": "level", + "propertyKeyName": "22", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "label": "Level (22)", + "valueChangeOptions": ["transitionDuration"], + "min": 0, + "max": 255, + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "dimmingDuration", + "propertyKey": 22, + "propertyName": "dimmingDuration", + "propertyKeyName": "22", + "ccVersion": 1, + "metadata": { + "type": "duration", + "readable": true, + "writeable": true, + "label": "Dimming duration (22)", + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "level", + "propertyKey": 23, + "propertyName": "level", + "propertyKeyName": "23", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "label": "Level (23)", + "valueChangeOptions": ["transitionDuration"], + "min": 0, + "max": 255, + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "dimmingDuration", + "propertyKey": 23, + "propertyName": "dimmingDuration", + "propertyKeyName": "23", + "ccVersion": 1, + "metadata": { + "type": "duration", + "readable": true, + "writeable": true, + "label": "Dimming duration (23)", + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "level", + "propertyKey": 24, + "propertyName": "level", + "propertyKeyName": "24", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "label": "Level (24)", + "valueChangeOptions": ["transitionDuration"], + "min": 0, + "max": 255, + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "dimmingDuration", + "propertyKey": 24, + "propertyName": "dimmingDuration", + "propertyKeyName": "24", + "ccVersion": 1, + "metadata": { + "type": "duration", + "readable": true, + "writeable": true, + "label": "Dimming duration (24)", + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "level", + "propertyKey": 25, + "propertyName": "level", + "propertyKeyName": "25", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "label": "Level (25)", + "valueChangeOptions": ["transitionDuration"], + "min": 0, + "max": 255, + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "dimmingDuration", + "propertyKey": 25, + "propertyName": "dimmingDuration", + "propertyKeyName": "25", + "ccVersion": 1, + "metadata": { + "type": "duration", + "readable": true, + "writeable": true, + "label": "Dimming duration (25)", + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "level", + "propertyKey": 26, + "propertyName": "level", + "propertyKeyName": "26", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "label": "Level (26)", + "valueChangeOptions": ["transitionDuration"], + "min": 0, + "max": 255, + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "dimmingDuration", + "propertyKey": 26, + "propertyName": "dimmingDuration", + "propertyKeyName": "26", + "ccVersion": 1, + "metadata": { + "type": "duration", + "readable": true, + "writeable": true, + "label": "Dimming duration (26)", + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "level", + "propertyKey": 27, + "propertyName": "level", + "propertyKeyName": "27", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "label": "Level (27)", + "valueChangeOptions": ["transitionDuration"], + "min": 0, + "max": 255, + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "dimmingDuration", + "propertyKey": 27, + "propertyName": "dimmingDuration", + "propertyKeyName": "27", + "ccVersion": 1, + "metadata": { + "type": "duration", + "readable": true, + "writeable": true, + "label": "Dimming duration (27)", + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "level", + "propertyKey": 28, + "propertyName": "level", + "propertyKeyName": "28", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "label": "Level (28)", + "valueChangeOptions": ["transitionDuration"], + "min": 0, + "max": 255, + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "dimmingDuration", + "propertyKey": 28, + "propertyName": "dimmingDuration", + "propertyKeyName": "28", + "ccVersion": 1, + "metadata": { + "type": "duration", + "readable": true, + "writeable": true, + "label": "Dimming duration (28)", + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "level", + "propertyKey": 29, + "propertyName": "level", + "propertyKeyName": "29", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "label": "Level (29)", + "valueChangeOptions": ["transitionDuration"], + "min": 0, + "max": 255, + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "dimmingDuration", + "propertyKey": 29, + "propertyName": "dimmingDuration", + "propertyKeyName": "29", + "ccVersion": 1, + "metadata": { + "type": "duration", + "readable": true, + "writeable": true, + "label": "Dimming duration (29)", + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "level", + "propertyKey": 30, + "propertyName": "level", + "propertyKeyName": "30", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "label": "Level (30)", + "valueChangeOptions": ["transitionDuration"], + "min": 0, + "max": 255, + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "dimmingDuration", + "propertyKey": 30, + "propertyName": "dimmingDuration", + "propertyKeyName": "30", + "ccVersion": 1, + "metadata": { + "type": "duration", + "readable": true, + "writeable": true, + "label": "Dimming duration (30)", + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "level", + "propertyKey": 31, + "propertyName": "level", + "propertyKeyName": "31", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "label": "Level (31)", + "valueChangeOptions": ["transitionDuration"], + "min": 0, + "max": 255, + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "dimmingDuration", + "propertyKey": 31, + "propertyName": "dimmingDuration", + "propertyKeyName": "31", + "ccVersion": 1, + "metadata": { + "type": "duration", + "readable": true, + "writeable": true, + "label": "Dimming duration (31)", + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "level", + "propertyKey": 32, + "propertyName": "level", + "propertyKeyName": "32", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "label": "Level (32)", + "valueChangeOptions": ["transitionDuration"], + "min": 0, + "max": 255, + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "dimmingDuration", + "propertyKey": 32, + "propertyName": "dimmingDuration", + "propertyKeyName": "32", + "ccVersion": 1, + "metadata": { + "type": "duration", + "readable": true, + "writeable": true, + "label": "Dimming duration (32)", + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "level", + "propertyKey": 33, + "propertyName": "level", + "propertyKeyName": "33", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "label": "Level (33)", + "valueChangeOptions": ["transitionDuration"], + "min": 0, + "max": 255, + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "dimmingDuration", + "propertyKey": 33, + "propertyName": "dimmingDuration", + "propertyKeyName": "33", + "ccVersion": 1, + "metadata": { + "type": "duration", + "readable": true, + "writeable": true, + "label": "Dimming duration (33)", + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "level", + "propertyKey": 34, + "propertyName": "level", + "propertyKeyName": "34", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "label": "Level (34)", + "valueChangeOptions": ["transitionDuration"], + "min": 0, + "max": 255, + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "dimmingDuration", + "propertyKey": 34, + "propertyName": "dimmingDuration", + "propertyKeyName": "34", + "ccVersion": 1, + "metadata": { + "type": "duration", + "readable": true, + "writeable": true, + "label": "Dimming duration (34)", + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "level", + "propertyKey": 35, + "propertyName": "level", + "propertyKeyName": "35", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "label": "Level (35)", + "valueChangeOptions": ["transitionDuration"], + "min": 0, + "max": 255, + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "dimmingDuration", + "propertyKey": 35, + "propertyName": "dimmingDuration", + "propertyKeyName": "35", + "ccVersion": 1, + "metadata": { + "type": "duration", + "readable": true, + "writeable": true, + "label": "Dimming duration (35)", + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "level", + "propertyKey": 36, + "propertyName": "level", + "propertyKeyName": "36", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "label": "Level (36)", + "valueChangeOptions": ["transitionDuration"], + "min": 0, + "max": 255, + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "dimmingDuration", + "propertyKey": 36, + "propertyName": "dimmingDuration", + "propertyKeyName": "36", + "ccVersion": 1, + "metadata": { + "type": "duration", + "readable": true, + "writeable": true, + "label": "Dimming duration (36)", + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "level", + "propertyKey": 37, + "propertyName": "level", + "propertyKeyName": "37", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "label": "Level (37)", + "valueChangeOptions": ["transitionDuration"], + "min": 0, + "max": 255, + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "dimmingDuration", + "propertyKey": 37, + "propertyName": "dimmingDuration", + "propertyKeyName": "37", + "ccVersion": 1, + "metadata": { + "type": "duration", + "readable": true, + "writeable": true, + "label": "Dimming duration (37)", + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "level", + "propertyKey": 38, + "propertyName": "level", + "propertyKeyName": "38", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "label": "Level (38)", + "valueChangeOptions": ["transitionDuration"], + "min": 0, + "max": 255, + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "dimmingDuration", + "propertyKey": 38, + "propertyName": "dimmingDuration", + "propertyKeyName": "38", + "ccVersion": 1, + "metadata": { + "type": "duration", + "readable": true, + "writeable": true, + "label": "Dimming duration (38)", + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "level", + "propertyKey": 39, + "propertyName": "level", + "propertyKeyName": "39", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "label": "Level (39)", + "valueChangeOptions": ["transitionDuration"], + "min": 0, + "max": 255, + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "dimmingDuration", + "propertyKey": 39, + "propertyName": "dimmingDuration", + "propertyKeyName": "39", + "ccVersion": 1, + "metadata": { + "type": "duration", + "readable": true, + "writeable": true, + "label": "Dimming duration (39)", + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "level", + "propertyKey": 40, + "propertyName": "level", + "propertyKeyName": "40", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "label": "Level (40)", + "valueChangeOptions": ["transitionDuration"], + "min": 0, + "max": 255, + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "dimmingDuration", + "propertyKey": 40, + "propertyName": "dimmingDuration", + "propertyKeyName": "40", + "ccVersion": 1, + "metadata": { + "type": "duration", + "readable": true, + "writeable": true, + "label": "Dimming duration (40)", + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "level", + "propertyKey": 41, + "propertyName": "level", + "propertyKeyName": "41", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "label": "Level (41)", + "valueChangeOptions": ["transitionDuration"], + "min": 0, + "max": 255, + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "dimmingDuration", + "propertyKey": 41, + "propertyName": "dimmingDuration", + "propertyKeyName": "41", + "ccVersion": 1, + "metadata": { + "type": "duration", + "readable": true, + "writeable": true, + "label": "Dimming duration (41)", + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "level", + "propertyKey": 42, + "propertyName": "level", + "propertyKeyName": "42", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "label": "Level (42)", + "valueChangeOptions": ["transitionDuration"], + "min": 0, + "max": 255, + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "dimmingDuration", + "propertyKey": 42, + "propertyName": "dimmingDuration", + "propertyKeyName": "42", + "ccVersion": 1, + "metadata": { + "type": "duration", + "readable": true, + "writeable": true, + "label": "Dimming duration (42)", + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "level", + "propertyKey": 43, + "propertyName": "level", + "propertyKeyName": "43", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "label": "Level (43)", + "valueChangeOptions": ["transitionDuration"], + "min": 0, + "max": 255, + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "dimmingDuration", + "propertyKey": 43, + "propertyName": "dimmingDuration", + "propertyKeyName": "43", + "ccVersion": 1, + "metadata": { + "type": "duration", + "readable": true, + "writeable": true, + "label": "Dimming duration (43)", + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "level", + "propertyKey": 44, + "propertyName": "level", + "propertyKeyName": "44", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "label": "Level (44)", + "valueChangeOptions": ["transitionDuration"], + "min": 0, + "max": 255, + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "dimmingDuration", + "propertyKey": 44, + "propertyName": "dimmingDuration", + "propertyKeyName": "44", + "ccVersion": 1, + "metadata": { + "type": "duration", + "readable": true, + "writeable": true, + "label": "Dimming duration (44)", + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "level", + "propertyKey": 45, + "propertyName": "level", + "propertyKeyName": "45", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "label": "Level (45)", + "valueChangeOptions": ["transitionDuration"], + "min": 0, + "max": 255, + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "dimmingDuration", + "propertyKey": 45, + "propertyName": "dimmingDuration", + "propertyKeyName": "45", + "ccVersion": 1, + "metadata": { + "type": "duration", + "readable": true, + "writeable": true, + "label": "Dimming duration (45)", + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "level", + "propertyKey": 46, + "propertyName": "level", + "propertyKeyName": "46", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "label": "Level (46)", + "valueChangeOptions": ["transitionDuration"], + "min": 0, + "max": 255, + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "dimmingDuration", + "propertyKey": 46, + "propertyName": "dimmingDuration", + "propertyKeyName": "46", + "ccVersion": 1, + "metadata": { + "type": "duration", + "readable": true, + "writeable": true, + "label": "Dimming duration (46)", + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "level", + "propertyKey": 47, + "propertyName": "level", + "propertyKeyName": "47", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "label": "Level (47)", + "valueChangeOptions": ["transitionDuration"], + "min": 0, + "max": 255, + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "dimmingDuration", + "propertyKey": 47, + "propertyName": "dimmingDuration", + "propertyKeyName": "47", + "ccVersion": 1, + "metadata": { + "type": "duration", + "readable": true, + "writeable": true, + "label": "Dimming duration (47)", + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "level", + "propertyKey": 48, + "propertyName": "level", + "propertyKeyName": "48", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "label": "Level (48)", + "valueChangeOptions": ["transitionDuration"], + "min": 0, + "max": 255, + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "dimmingDuration", + "propertyKey": 48, + "propertyName": "dimmingDuration", + "propertyKeyName": "48", + "ccVersion": 1, + "metadata": { + "type": "duration", + "readable": true, + "writeable": true, + "label": "Dimming duration (48)", + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "level", + "propertyKey": 49, + "propertyName": "level", + "propertyKeyName": "49", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "label": "Level (49)", + "valueChangeOptions": ["transitionDuration"], + "min": 0, + "max": 255, + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "dimmingDuration", + "propertyKey": 49, + "propertyName": "dimmingDuration", + "propertyKeyName": "49", + "ccVersion": 1, + "metadata": { + "type": "duration", + "readable": true, + "writeable": true, + "label": "Dimming duration (49)", + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "level", + "propertyKey": 50, + "propertyName": "level", + "propertyKeyName": "50", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "label": "Level (50)", + "valueChangeOptions": ["transitionDuration"], + "min": 0, + "max": 255, + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "dimmingDuration", + "propertyKey": 50, + "propertyName": "dimmingDuration", + "propertyKeyName": "50", + "ccVersion": 1, + "metadata": { + "type": "duration", + "readable": true, + "writeable": true, + "label": "Dimming duration (50)", + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "level", + "propertyKey": 51, + "propertyName": "level", + "propertyKeyName": "51", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "label": "Level (51)", + "valueChangeOptions": ["transitionDuration"], + "min": 0, + "max": 255, + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "dimmingDuration", + "propertyKey": 51, + "propertyName": "dimmingDuration", + "propertyKeyName": "51", + "ccVersion": 1, + "metadata": { + "type": "duration", + "readable": true, + "writeable": true, + "label": "Dimming duration (51)", + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "level", + "propertyKey": 52, + "propertyName": "level", + "propertyKeyName": "52", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "label": "Level (52)", + "valueChangeOptions": ["transitionDuration"], + "min": 0, + "max": 255, + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "dimmingDuration", + "propertyKey": 52, + "propertyName": "dimmingDuration", + "propertyKeyName": "52", + "ccVersion": 1, + "metadata": { + "type": "duration", + "readable": true, + "writeable": true, + "label": "Dimming duration (52)", + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "level", + "propertyKey": 53, + "propertyName": "level", + "propertyKeyName": "53", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "label": "Level (53)", + "valueChangeOptions": ["transitionDuration"], + "min": 0, + "max": 255, + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "dimmingDuration", + "propertyKey": 53, + "propertyName": "dimmingDuration", + "propertyKeyName": "53", + "ccVersion": 1, + "metadata": { + "type": "duration", + "readable": true, + "writeable": true, + "label": "Dimming duration (53)", + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "level", + "propertyKey": 54, + "propertyName": "level", + "propertyKeyName": "54", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "label": "Level (54)", + "valueChangeOptions": ["transitionDuration"], + "min": 0, + "max": 255, + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "dimmingDuration", + "propertyKey": 54, + "propertyName": "dimmingDuration", + "propertyKeyName": "54", + "ccVersion": 1, + "metadata": { + "type": "duration", + "readable": true, + "writeable": true, + "label": "Dimming duration (54)", + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "level", + "propertyKey": 55, + "propertyName": "level", + "propertyKeyName": "55", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "label": "Level (55)", + "valueChangeOptions": ["transitionDuration"], + "min": 0, + "max": 255, + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "dimmingDuration", + "propertyKey": 55, + "propertyName": "dimmingDuration", + "propertyKeyName": "55", + "ccVersion": 1, + "metadata": { + "type": "duration", + "readable": true, + "writeable": true, + "label": "Dimming duration (55)", + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "level", + "propertyKey": 56, + "propertyName": "level", + "propertyKeyName": "56", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "label": "Level (56)", + "valueChangeOptions": ["transitionDuration"], + "min": 0, + "max": 255, + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "dimmingDuration", + "propertyKey": 56, + "propertyName": "dimmingDuration", + "propertyKeyName": "56", + "ccVersion": 1, + "metadata": { + "type": "duration", + "readable": true, + "writeable": true, + "label": "Dimming duration (56)", + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "level", + "propertyKey": 57, + "propertyName": "level", + "propertyKeyName": "57", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "label": "Level (57)", + "valueChangeOptions": ["transitionDuration"], + "min": 0, + "max": 255, + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "dimmingDuration", + "propertyKey": 57, + "propertyName": "dimmingDuration", + "propertyKeyName": "57", + "ccVersion": 1, + "metadata": { + "type": "duration", + "readable": true, + "writeable": true, + "label": "Dimming duration (57)", + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "level", + "propertyKey": 58, + "propertyName": "level", + "propertyKeyName": "58", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "label": "Level (58)", + "valueChangeOptions": ["transitionDuration"], + "min": 0, + "max": 255, + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "dimmingDuration", + "propertyKey": 58, + "propertyName": "dimmingDuration", + "propertyKeyName": "58", + "ccVersion": 1, + "metadata": { + "type": "duration", + "readable": true, + "writeable": true, + "label": "Dimming duration (58)", + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "level", + "propertyKey": 59, + "propertyName": "level", + "propertyKeyName": "59", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "label": "Level (59)", + "valueChangeOptions": ["transitionDuration"], + "min": 0, + "max": 255, + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "dimmingDuration", + "propertyKey": 59, + "propertyName": "dimmingDuration", + "propertyKeyName": "59", + "ccVersion": 1, + "metadata": { + "type": "duration", + "readable": true, + "writeable": true, + "label": "Dimming duration (59)", + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "level", + "propertyKey": 60, + "propertyName": "level", + "propertyKeyName": "60", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "label": "Level (60)", + "valueChangeOptions": ["transitionDuration"], + "min": 0, + "max": 255, + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "dimmingDuration", + "propertyKey": 60, + "propertyName": "dimmingDuration", + "propertyKeyName": "60", + "ccVersion": 1, + "metadata": { + "type": "duration", + "readable": true, + "writeable": true, + "label": "Dimming duration (60)", + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "level", + "propertyKey": 61, + "propertyName": "level", + "propertyKeyName": "61", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "label": "Level (61)", + "valueChangeOptions": ["transitionDuration"], + "min": 0, + "max": 255, + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "dimmingDuration", + "propertyKey": 61, + "propertyName": "dimmingDuration", + "propertyKeyName": "61", + "ccVersion": 1, + "metadata": { + "type": "duration", + "readable": true, + "writeable": true, + "label": "Dimming duration (61)", + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "level", + "propertyKey": 62, + "propertyName": "level", + "propertyKeyName": "62", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "label": "Level (62)", + "valueChangeOptions": ["transitionDuration"], + "min": 0, + "max": 255, + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "dimmingDuration", + "propertyKey": 62, + "propertyName": "dimmingDuration", + "propertyKeyName": "62", + "ccVersion": 1, + "metadata": { + "type": "duration", + "readable": true, + "writeable": true, + "label": "Dimming duration (62)", + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "level", + "propertyKey": 63, + "propertyName": "level", + "propertyKeyName": "63", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "label": "Level (63)", + "valueChangeOptions": ["transitionDuration"], + "min": 0, + "max": 255, + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "dimmingDuration", + "propertyKey": 63, + "propertyName": "dimmingDuration", + "propertyKeyName": "63", + "ccVersion": 1, + "metadata": { + "type": "duration", + "readable": true, + "writeable": true, + "label": "Dimming duration (63)", + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "level", + "propertyKey": 64, + "propertyName": "level", + "propertyKeyName": "64", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "label": "Level (64)", + "valueChangeOptions": ["transitionDuration"], + "min": 0, + "max": 255, + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "dimmingDuration", + "propertyKey": 64, + "propertyName": "dimmingDuration", + "propertyKeyName": "64", + "ccVersion": 1, + "metadata": { + "type": "duration", + "readable": true, + "writeable": true, + "label": "Dimming duration (64)", + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "level", + "propertyKey": 65, + "propertyName": "level", + "propertyKeyName": "65", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "label": "Level (65)", + "valueChangeOptions": ["transitionDuration"], + "min": 0, + "max": 255, + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "dimmingDuration", + "propertyKey": 65, + "propertyName": "dimmingDuration", + "propertyKeyName": "65", + "ccVersion": 1, + "metadata": { + "type": "duration", + "readable": true, + "writeable": true, + "label": "Dimming duration (65)", + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "level", + "propertyKey": 66, + "propertyName": "level", + "propertyKeyName": "66", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "label": "Level (66)", + "valueChangeOptions": ["transitionDuration"], + "min": 0, + "max": 255, + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "dimmingDuration", + "propertyKey": 66, + "propertyName": "dimmingDuration", + "propertyKeyName": "66", + "ccVersion": 1, + "metadata": { + "type": "duration", + "readable": true, + "writeable": true, + "label": "Dimming duration (66)", + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "level", + "propertyKey": 67, + "propertyName": "level", + "propertyKeyName": "67", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "label": "Level (67)", + "valueChangeOptions": ["transitionDuration"], + "min": 0, + "max": 255, + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "dimmingDuration", + "propertyKey": 67, + "propertyName": "dimmingDuration", + "propertyKeyName": "67", + "ccVersion": 1, + "metadata": { + "type": "duration", + "readable": true, + "writeable": true, + "label": "Dimming duration (67)", + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "level", + "propertyKey": 68, + "propertyName": "level", + "propertyKeyName": "68", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "label": "Level (68)", + "valueChangeOptions": ["transitionDuration"], + "min": 0, + "max": 255, + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "dimmingDuration", + "propertyKey": 68, + "propertyName": "dimmingDuration", + "propertyKeyName": "68", + "ccVersion": 1, + "metadata": { + "type": "duration", + "readable": true, + "writeable": true, + "label": "Dimming duration (68)", + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "level", + "propertyKey": 69, + "propertyName": "level", + "propertyKeyName": "69", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "label": "Level (69)", + "valueChangeOptions": ["transitionDuration"], + "min": 0, + "max": 255, + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "dimmingDuration", + "propertyKey": 69, + "propertyName": "dimmingDuration", + "propertyKeyName": "69", + "ccVersion": 1, + "metadata": { + "type": "duration", + "readable": true, + "writeable": true, + "label": "Dimming duration (69)", + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "level", + "propertyKey": 70, + "propertyName": "level", + "propertyKeyName": "70", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "label": "Level (70)", + "valueChangeOptions": ["transitionDuration"], + "min": 0, + "max": 255, + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "dimmingDuration", + "propertyKey": 70, + "propertyName": "dimmingDuration", + "propertyKeyName": "70", + "ccVersion": 1, + "metadata": { + "type": "duration", + "readable": true, + "writeable": true, + "label": "Dimming duration (70)", + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "level", + "propertyKey": 71, + "propertyName": "level", + "propertyKeyName": "71", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "label": "Level (71)", + "valueChangeOptions": ["transitionDuration"], + "min": 0, + "max": 255, + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "dimmingDuration", + "propertyKey": 71, + "propertyName": "dimmingDuration", + "propertyKeyName": "71", + "ccVersion": 1, + "metadata": { + "type": "duration", + "readable": true, + "writeable": true, + "label": "Dimming duration (71)", + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "level", + "propertyKey": 72, + "propertyName": "level", + "propertyKeyName": "72", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "label": "Level (72)", + "valueChangeOptions": ["transitionDuration"], + "min": 0, + "max": 255, + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "dimmingDuration", + "propertyKey": 72, + "propertyName": "dimmingDuration", + "propertyKeyName": "72", + "ccVersion": 1, + "metadata": { + "type": "duration", + "readable": true, + "writeable": true, + "label": "Dimming duration (72)", + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "level", + "propertyKey": 73, + "propertyName": "level", + "propertyKeyName": "73", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "label": "Level (73)", + "valueChangeOptions": ["transitionDuration"], + "min": 0, + "max": 255, + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "dimmingDuration", + "propertyKey": 73, + "propertyName": "dimmingDuration", + "propertyKeyName": "73", + "ccVersion": 1, + "metadata": { + "type": "duration", + "readable": true, + "writeable": true, + "label": "Dimming duration (73)", + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "level", + "propertyKey": 74, + "propertyName": "level", + "propertyKeyName": "74", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "label": "Level (74)", + "valueChangeOptions": ["transitionDuration"], + "min": 0, + "max": 255, + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "dimmingDuration", + "propertyKey": 74, + "propertyName": "dimmingDuration", + "propertyKeyName": "74", + "ccVersion": 1, + "metadata": { + "type": "duration", + "readable": true, + "writeable": true, + "label": "Dimming duration (74)", + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "level", + "propertyKey": 75, + "propertyName": "level", + "propertyKeyName": "75", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "label": "Level (75)", + "valueChangeOptions": ["transitionDuration"], + "min": 0, + "max": 255, + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "dimmingDuration", + "propertyKey": 75, + "propertyName": "dimmingDuration", + "propertyKeyName": "75", + "ccVersion": 1, + "metadata": { + "type": "duration", + "readable": true, + "writeable": true, + "label": "Dimming duration (75)", + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "level", + "propertyKey": 76, + "propertyName": "level", + "propertyKeyName": "76", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "label": "Level (76)", + "valueChangeOptions": ["transitionDuration"], + "min": 0, + "max": 255, + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "dimmingDuration", + "propertyKey": 76, + "propertyName": "dimmingDuration", + "propertyKeyName": "76", + "ccVersion": 1, + "metadata": { + "type": "duration", + "readable": true, + "writeable": true, + "label": "Dimming duration (76)", + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "level", + "propertyKey": 77, + "propertyName": "level", + "propertyKeyName": "77", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "label": "Level (77)", + "valueChangeOptions": ["transitionDuration"], + "min": 0, + "max": 255, + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "dimmingDuration", + "propertyKey": 77, + "propertyName": "dimmingDuration", + "propertyKeyName": "77", + "ccVersion": 1, + "metadata": { + "type": "duration", + "readable": true, + "writeable": true, + "label": "Dimming duration (77)", + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "level", + "propertyKey": 78, + "propertyName": "level", + "propertyKeyName": "78", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "label": "Level (78)", + "valueChangeOptions": ["transitionDuration"], + "min": 0, + "max": 255, + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "dimmingDuration", + "propertyKey": 78, + "propertyName": "dimmingDuration", + "propertyKeyName": "78", + "ccVersion": 1, + "metadata": { + "type": "duration", + "readable": true, + "writeable": true, + "label": "Dimming duration (78)", + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "level", + "propertyKey": 79, + "propertyName": "level", + "propertyKeyName": "79", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "label": "Level (79)", + "valueChangeOptions": ["transitionDuration"], + "min": 0, + "max": 255, + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "dimmingDuration", + "propertyKey": 79, + "propertyName": "dimmingDuration", + "propertyKeyName": "79", + "ccVersion": 1, + "metadata": { + "type": "duration", + "readable": true, + "writeable": true, + "label": "Dimming duration (79)", + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "level", + "propertyKey": 80, + "propertyName": "level", + "propertyKeyName": "80", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "label": "Level (80)", + "valueChangeOptions": ["transitionDuration"], + "min": 0, + "max": 255, + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "dimmingDuration", + "propertyKey": 80, + "propertyName": "dimmingDuration", + "propertyKeyName": "80", + "ccVersion": 1, + "metadata": { + "type": "duration", + "readable": true, + "writeable": true, + "label": "Dimming duration (80)", + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "level", + "propertyKey": 81, + "propertyName": "level", + "propertyKeyName": "81", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "label": "Level (81)", + "valueChangeOptions": ["transitionDuration"], + "min": 0, + "max": 255, + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "dimmingDuration", + "propertyKey": 81, + "propertyName": "dimmingDuration", + "propertyKeyName": "81", + "ccVersion": 1, + "metadata": { + "type": "duration", + "readable": true, + "writeable": true, + "label": "Dimming duration (81)", + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "level", + "propertyKey": 82, + "propertyName": "level", + "propertyKeyName": "82", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "label": "Level (82)", + "valueChangeOptions": ["transitionDuration"], + "min": 0, + "max": 255, + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "dimmingDuration", + "propertyKey": 82, + "propertyName": "dimmingDuration", + "propertyKeyName": "82", + "ccVersion": 1, + "metadata": { + "type": "duration", + "readable": true, + "writeable": true, + "label": "Dimming duration (82)", + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "level", + "propertyKey": 83, + "propertyName": "level", + "propertyKeyName": "83", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "label": "Level (83)", + "valueChangeOptions": ["transitionDuration"], + "min": 0, + "max": 255, + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "dimmingDuration", + "propertyKey": 83, + "propertyName": "dimmingDuration", + "propertyKeyName": "83", + "ccVersion": 1, + "metadata": { + "type": "duration", + "readable": true, + "writeable": true, + "label": "Dimming duration (83)", + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "level", + "propertyKey": 84, + "propertyName": "level", + "propertyKeyName": "84", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "label": "Level (84)", + "valueChangeOptions": ["transitionDuration"], + "min": 0, + "max": 255, + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "dimmingDuration", + "propertyKey": 84, + "propertyName": "dimmingDuration", + "propertyKeyName": "84", + "ccVersion": 1, + "metadata": { + "type": "duration", + "readable": true, + "writeable": true, + "label": "Dimming duration (84)", + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "level", + "propertyKey": 85, + "propertyName": "level", + "propertyKeyName": "85", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "label": "Level (85)", + "valueChangeOptions": ["transitionDuration"], + "min": 0, + "max": 255, + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "dimmingDuration", + "propertyKey": 85, + "propertyName": "dimmingDuration", + "propertyKeyName": "85", + "ccVersion": 1, + "metadata": { + "type": "duration", + "readable": true, + "writeable": true, + "label": "Dimming duration (85)", + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "level", + "propertyKey": 86, + "propertyName": "level", + "propertyKeyName": "86", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "label": "Level (86)", + "valueChangeOptions": ["transitionDuration"], + "min": 0, + "max": 255, + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "dimmingDuration", + "propertyKey": 86, + "propertyName": "dimmingDuration", + "propertyKeyName": "86", + "ccVersion": 1, + "metadata": { + "type": "duration", + "readable": true, + "writeable": true, + "label": "Dimming duration (86)", + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "level", + "propertyKey": 87, + "propertyName": "level", + "propertyKeyName": "87", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "label": "Level (87)", + "valueChangeOptions": ["transitionDuration"], + "min": 0, + "max": 255, + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "dimmingDuration", + "propertyKey": 87, + "propertyName": "dimmingDuration", + "propertyKeyName": "87", + "ccVersion": 1, + "metadata": { + "type": "duration", + "readable": true, + "writeable": true, + "label": "Dimming duration (87)", + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "level", + "propertyKey": 88, + "propertyName": "level", + "propertyKeyName": "88", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "label": "Level (88)", + "valueChangeOptions": ["transitionDuration"], + "min": 0, + "max": 255, + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "dimmingDuration", + "propertyKey": 88, + "propertyName": "dimmingDuration", + "propertyKeyName": "88", + "ccVersion": 1, + "metadata": { + "type": "duration", + "readable": true, + "writeable": true, + "label": "Dimming duration (88)", + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "level", + "propertyKey": 89, + "propertyName": "level", + "propertyKeyName": "89", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "label": "Level (89)", + "valueChangeOptions": ["transitionDuration"], + "min": 0, + "max": 255, + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "dimmingDuration", + "propertyKey": 89, + "propertyName": "dimmingDuration", + "propertyKeyName": "89", + "ccVersion": 1, + "metadata": { + "type": "duration", + "readable": true, + "writeable": true, + "label": "Dimming duration (89)", + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "level", + "propertyKey": 90, + "propertyName": "level", + "propertyKeyName": "90", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "label": "Level (90)", + "valueChangeOptions": ["transitionDuration"], + "min": 0, + "max": 255, + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "dimmingDuration", + "propertyKey": 90, + "propertyName": "dimmingDuration", + "propertyKeyName": "90", + "ccVersion": 1, + "metadata": { + "type": "duration", + "readable": true, + "writeable": true, + "label": "Dimming duration (90)", + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "level", + "propertyKey": 91, + "propertyName": "level", + "propertyKeyName": "91", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "label": "Level (91)", + "valueChangeOptions": ["transitionDuration"], + "min": 0, + "max": 255, + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "dimmingDuration", + "propertyKey": 91, + "propertyName": "dimmingDuration", + "propertyKeyName": "91", + "ccVersion": 1, + "metadata": { + "type": "duration", + "readable": true, + "writeable": true, + "label": "Dimming duration (91)", + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "level", + "propertyKey": 92, + "propertyName": "level", + "propertyKeyName": "92", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "label": "Level (92)", + "valueChangeOptions": ["transitionDuration"], + "min": 0, + "max": 255, + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "dimmingDuration", + "propertyKey": 92, + "propertyName": "dimmingDuration", + "propertyKeyName": "92", + "ccVersion": 1, + "metadata": { + "type": "duration", + "readable": true, + "writeable": true, + "label": "Dimming duration (92)", + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "level", + "propertyKey": 93, + "propertyName": "level", + "propertyKeyName": "93", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "label": "Level (93)", + "valueChangeOptions": ["transitionDuration"], + "min": 0, + "max": 255, + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "dimmingDuration", + "propertyKey": 93, + "propertyName": "dimmingDuration", + "propertyKeyName": "93", + "ccVersion": 1, + "metadata": { + "type": "duration", + "readable": true, + "writeable": true, + "label": "Dimming duration (93)", + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "level", + "propertyKey": 94, + "propertyName": "level", + "propertyKeyName": "94", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "label": "Level (94)", + "valueChangeOptions": ["transitionDuration"], + "min": 0, + "max": 255, + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "dimmingDuration", + "propertyKey": 94, + "propertyName": "dimmingDuration", + "propertyKeyName": "94", + "ccVersion": 1, + "metadata": { + "type": "duration", + "readable": true, + "writeable": true, + "label": "Dimming duration (94)", + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "level", + "propertyKey": 95, + "propertyName": "level", + "propertyKeyName": "95", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "label": "Level (95)", + "valueChangeOptions": ["transitionDuration"], + "min": 0, + "max": 255, + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "dimmingDuration", + "propertyKey": 95, + "propertyName": "dimmingDuration", + "propertyKeyName": "95", + "ccVersion": 1, + "metadata": { + "type": "duration", + "readable": true, + "writeable": true, + "label": "Dimming duration (95)", + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "level", + "propertyKey": 96, + "propertyName": "level", + "propertyKeyName": "96", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "label": "Level (96)", + "valueChangeOptions": ["transitionDuration"], + "min": 0, + "max": 255, + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "dimmingDuration", + "propertyKey": 96, + "propertyName": "dimmingDuration", + "propertyKeyName": "96", + "ccVersion": 1, + "metadata": { + "type": "duration", + "readable": true, + "writeable": true, + "label": "Dimming duration (96)", + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "level", + "propertyKey": 97, + "propertyName": "level", + "propertyKeyName": "97", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "label": "Level (97)", + "valueChangeOptions": ["transitionDuration"], + "min": 0, + "max": 255, + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "dimmingDuration", + "propertyKey": 97, + "propertyName": "dimmingDuration", + "propertyKeyName": "97", + "ccVersion": 1, + "metadata": { + "type": "duration", + "readable": true, + "writeable": true, + "label": "Dimming duration (97)", + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "level", + "propertyKey": 98, + "propertyName": "level", + "propertyKeyName": "98", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "label": "Level (98)", + "valueChangeOptions": ["transitionDuration"], + "min": 0, + "max": 255, + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "dimmingDuration", + "propertyKey": 98, + "propertyName": "dimmingDuration", + "propertyKeyName": "98", + "ccVersion": 1, + "metadata": { + "type": "duration", + "readable": true, + "writeable": true, + "label": "Dimming duration (98)", + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "level", + "propertyKey": 99, + "propertyName": "level", + "propertyKeyName": "99", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "label": "Level (99)", + "valueChangeOptions": ["transitionDuration"], + "min": 0, + "max": 255, + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "dimmingDuration", + "propertyKey": 99, + "propertyName": "dimmingDuration", + "propertyKeyName": "99", + "ccVersion": 1, + "metadata": { + "type": "duration", + "readable": true, + "writeable": true, + "label": "Dimming duration (99)", + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "level", + "propertyKey": 100, + "propertyName": "level", + "propertyKeyName": "100", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "label": "Level (100)", + "valueChangeOptions": ["transitionDuration"], + "min": 0, + "max": 255, + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "dimmingDuration", + "propertyKey": 100, + "propertyName": "dimmingDuration", + "propertyKeyName": "100", + "ccVersion": 1, + "metadata": { + "type": "duration", + "readable": true, + "writeable": true, + "label": "Dimming duration (100)", + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "level", + "propertyKey": 101, + "propertyName": "level", + "propertyKeyName": "101", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "label": "Level (101)", + "valueChangeOptions": ["transitionDuration"], + "min": 0, + "max": 255, + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "dimmingDuration", + "propertyKey": 101, + "propertyName": "dimmingDuration", + "propertyKeyName": "101", + "ccVersion": 1, + "metadata": { + "type": "duration", + "readable": true, + "writeable": true, + "label": "Dimming duration (101)", + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "level", + "propertyKey": 102, + "propertyName": "level", + "propertyKeyName": "102", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "label": "Level (102)", + "valueChangeOptions": ["transitionDuration"], + "min": 0, + "max": 255, + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "dimmingDuration", + "propertyKey": 102, + "propertyName": "dimmingDuration", + "propertyKeyName": "102", + "ccVersion": 1, + "metadata": { + "type": "duration", + "readable": true, + "writeable": true, + "label": "Dimming duration (102)", + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "level", + "propertyKey": 103, + "propertyName": "level", + "propertyKeyName": "103", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "label": "Level (103)", + "valueChangeOptions": ["transitionDuration"], + "min": 0, + "max": 255, + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "dimmingDuration", + "propertyKey": 103, + "propertyName": "dimmingDuration", + "propertyKeyName": "103", + "ccVersion": 1, + "metadata": { + "type": "duration", + "readable": true, + "writeable": true, + "label": "Dimming duration (103)", + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "level", + "propertyKey": 104, + "propertyName": "level", + "propertyKeyName": "104", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "label": "Level (104)", + "valueChangeOptions": ["transitionDuration"], + "min": 0, + "max": 255, + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "dimmingDuration", + "propertyKey": 104, + "propertyName": "dimmingDuration", + "propertyKeyName": "104", + "ccVersion": 1, + "metadata": { + "type": "duration", + "readable": true, + "writeable": true, + "label": "Dimming duration (104)", + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "level", + "propertyKey": 105, + "propertyName": "level", + "propertyKeyName": "105", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "label": "Level (105)", + "valueChangeOptions": ["transitionDuration"], + "min": 0, + "max": 255, + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "dimmingDuration", + "propertyKey": 105, + "propertyName": "dimmingDuration", + "propertyKeyName": "105", + "ccVersion": 1, + "metadata": { + "type": "duration", + "readable": true, + "writeable": true, + "label": "Dimming duration (105)", + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "level", + "propertyKey": 106, + "propertyName": "level", + "propertyKeyName": "106", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "label": "Level (106)", + "valueChangeOptions": ["transitionDuration"], + "min": 0, + "max": 255, + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "dimmingDuration", + "propertyKey": 106, + "propertyName": "dimmingDuration", + "propertyKeyName": "106", + "ccVersion": 1, + "metadata": { + "type": "duration", + "readable": true, + "writeable": true, + "label": "Dimming duration (106)", + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "level", + "propertyKey": 107, + "propertyName": "level", + "propertyKeyName": "107", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "label": "Level (107)", + "valueChangeOptions": ["transitionDuration"], + "min": 0, + "max": 255, + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "dimmingDuration", + "propertyKey": 107, + "propertyName": "dimmingDuration", + "propertyKeyName": "107", + "ccVersion": 1, + "metadata": { + "type": "duration", + "readable": true, + "writeable": true, + "label": "Dimming duration (107)", + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "level", + "propertyKey": 108, + "propertyName": "level", + "propertyKeyName": "108", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "label": "Level (108)", + "valueChangeOptions": ["transitionDuration"], + "min": 0, + "max": 255, + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "dimmingDuration", + "propertyKey": 108, + "propertyName": "dimmingDuration", + "propertyKeyName": "108", + "ccVersion": 1, + "metadata": { + "type": "duration", + "readable": true, + "writeable": true, + "label": "Dimming duration (108)", + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "level", + "propertyKey": 109, + "propertyName": "level", + "propertyKeyName": "109", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "label": "Level (109)", + "valueChangeOptions": ["transitionDuration"], + "min": 0, + "max": 255, + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "dimmingDuration", + "propertyKey": 109, + "propertyName": "dimmingDuration", + "propertyKeyName": "109", + "ccVersion": 1, + "metadata": { + "type": "duration", + "readable": true, + "writeable": true, + "label": "Dimming duration (109)", + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "level", + "propertyKey": 110, + "propertyName": "level", + "propertyKeyName": "110", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "label": "Level (110)", + "valueChangeOptions": ["transitionDuration"], + "min": 0, + "max": 255, + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "dimmingDuration", + "propertyKey": 110, + "propertyName": "dimmingDuration", + "propertyKeyName": "110", + "ccVersion": 1, + "metadata": { + "type": "duration", + "readable": true, + "writeable": true, + "label": "Dimming duration (110)", + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "level", + "propertyKey": 111, + "propertyName": "level", + "propertyKeyName": "111", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "label": "Level (111)", + "valueChangeOptions": ["transitionDuration"], + "min": 0, + "max": 255, + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "dimmingDuration", + "propertyKey": 111, + "propertyName": "dimmingDuration", + "propertyKeyName": "111", + "ccVersion": 1, + "metadata": { + "type": "duration", + "readable": true, + "writeable": true, + "label": "Dimming duration (111)", + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "level", + "propertyKey": 112, + "propertyName": "level", + "propertyKeyName": "112", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "label": "Level (112)", + "valueChangeOptions": ["transitionDuration"], + "min": 0, + "max": 255, + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "dimmingDuration", + "propertyKey": 112, + "propertyName": "dimmingDuration", + "propertyKeyName": "112", + "ccVersion": 1, + "metadata": { + "type": "duration", + "readable": true, + "writeable": true, + "label": "Dimming duration (112)", + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "level", + "propertyKey": 113, + "propertyName": "level", + "propertyKeyName": "113", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "label": "Level (113)", + "valueChangeOptions": ["transitionDuration"], + "min": 0, + "max": 255, + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "dimmingDuration", + "propertyKey": 113, + "propertyName": "dimmingDuration", + "propertyKeyName": "113", + "ccVersion": 1, + "metadata": { + "type": "duration", + "readable": true, + "writeable": true, + "label": "Dimming duration (113)", + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "level", + "propertyKey": 114, + "propertyName": "level", + "propertyKeyName": "114", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "label": "Level (114)", + "valueChangeOptions": ["transitionDuration"], + "min": 0, + "max": 255, + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "dimmingDuration", + "propertyKey": 114, + "propertyName": "dimmingDuration", + "propertyKeyName": "114", + "ccVersion": 1, + "metadata": { + "type": "duration", + "readable": true, + "writeable": true, + "label": "Dimming duration (114)", + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "level", + "propertyKey": 115, + "propertyName": "level", + "propertyKeyName": "115", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "label": "Level (115)", + "valueChangeOptions": ["transitionDuration"], + "min": 0, + "max": 255, + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "dimmingDuration", + "propertyKey": 115, + "propertyName": "dimmingDuration", + "propertyKeyName": "115", + "ccVersion": 1, + "metadata": { + "type": "duration", + "readable": true, + "writeable": true, + "label": "Dimming duration (115)", + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "level", + "propertyKey": 116, + "propertyName": "level", + "propertyKeyName": "116", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "label": "Level (116)", + "valueChangeOptions": ["transitionDuration"], + "min": 0, + "max": 255, + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "dimmingDuration", + "propertyKey": 116, + "propertyName": "dimmingDuration", + "propertyKeyName": "116", + "ccVersion": 1, + "metadata": { + "type": "duration", + "readable": true, + "writeable": true, + "label": "Dimming duration (116)", + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "level", + "propertyKey": 117, + "propertyName": "level", + "propertyKeyName": "117", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "label": "Level (117)", + "valueChangeOptions": ["transitionDuration"], + "min": 0, + "max": 255, + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "dimmingDuration", + "propertyKey": 117, + "propertyName": "dimmingDuration", + "propertyKeyName": "117", + "ccVersion": 1, + "metadata": { + "type": "duration", + "readable": true, + "writeable": true, + "label": "Dimming duration (117)", + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "level", + "propertyKey": 118, + "propertyName": "level", + "propertyKeyName": "118", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "label": "Level (118)", + "valueChangeOptions": ["transitionDuration"], + "min": 0, + "max": 255, + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "dimmingDuration", + "propertyKey": 118, + "propertyName": "dimmingDuration", + "propertyKeyName": "118", + "ccVersion": 1, + "metadata": { + "type": "duration", + "readable": true, + "writeable": true, + "label": "Dimming duration (118)", + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "level", + "propertyKey": 119, + "propertyName": "level", + "propertyKeyName": "119", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "label": "Level (119)", + "valueChangeOptions": ["transitionDuration"], + "min": 0, + "max": 255, + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "dimmingDuration", + "propertyKey": 119, + "propertyName": "dimmingDuration", + "propertyKeyName": "119", + "ccVersion": 1, + "metadata": { + "type": "duration", + "readable": true, + "writeable": true, + "label": "Dimming duration (119)", + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "level", + "propertyKey": 120, + "propertyName": "level", + "propertyKeyName": "120", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "label": "Level (120)", + "valueChangeOptions": ["transitionDuration"], + "min": 0, + "max": 255, + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "dimmingDuration", + "propertyKey": 120, + "propertyName": "dimmingDuration", + "propertyKeyName": "120", + "ccVersion": 1, + "metadata": { + "type": "duration", + "readable": true, + "writeable": true, + "label": "Dimming duration (120)", + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "level", + "propertyKey": 121, + "propertyName": "level", + "propertyKeyName": "121", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "label": "Level (121)", + "valueChangeOptions": ["transitionDuration"], + "min": 0, + "max": 255, + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "dimmingDuration", + "propertyKey": 121, + "propertyName": "dimmingDuration", + "propertyKeyName": "121", + "ccVersion": 1, + "metadata": { + "type": "duration", + "readable": true, + "writeable": true, + "label": "Dimming duration (121)", + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "level", + "propertyKey": 122, + "propertyName": "level", + "propertyKeyName": "122", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "label": "Level (122)", + "valueChangeOptions": ["transitionDuration"], + "min": 0, + "max": 255, + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "dimmingDuration", + "propertyKey": 122, + "propertyName": "dimmingDuration", + "propertyKeyName": "122", + "ccVersion": 1, + "metadata": { + "type": "duration", + "readable": true, + "writeable": true, + "label": "Dimming duration (122)", + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "level", + "propertyKey": 123, + "propertyName": "level", + "propertyKeyName": "123", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "label": "Level (123)", + "valueChangeOptions": ["transitionDuration"], + "min": 0, + "max": 255, + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "dimmingDuration", + "propertyKey": 123, + "propertyName": "dimmingDuration", + "propertyKeyName": "123", + "ccVersion": 1, + "metadata": { + "type": "duration", + "readable": true, + "writeable": true, + "label": "Dimming duration (123)", + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "level", + "propertyKey": 124, + "propertyName": "level", + "propertyKeyName": "124", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "label": "Level (124)", + "valueChangeOptions": ["transitionDuration"], + "min": 0, + "max": 255, + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "dimmingDuration", + "propertyKey": 124, + "propertyName": "dimmingDuration", + "propertyKeyName": "124", + "ccVersion": 1, + "metadata": { + "type": "duration", + "readable": true, + "writeable": true, + "label": "Dimming duration (124)", + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "level", + "propertyKey": 125, + "propertyName": "level", + "propertyKeyName": "125", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "label": "Level (125)", + "valueChangeOptions": ["transitionDuration"], + "min": 0, + "max": 255, + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "dimmingDuration", + "propertyKey": 125, + "propertyName": "dimmingDuration", + "propertyKeyName": "125", + "ccVersion": 1, + "metadata": { + "type": "duration", + "readable": true, + "writeable": true, + "label": "Dimming duration (125)", + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "level", + "propertyKey": 126, + "propertyName": "level", + "propertyKeyName": "126", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "label": "Level (126)", + "valueChangeOptions": ["transitionDuration"], + "min": 0, + "max": 255, + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "dimmingDuration", + "propertyKey": 126, + "propertyName": "dimmingDuration", + "propertyKeyName": "126", + "ccVersion": 1, + "metadata": { + "type": "duration", + "readable": true, + "writeable": true, + "label": "Dimming duration (126)", + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "level", + "propertyKey": 127, + "propertyName": "level", + "propertyKeyName": "127", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "label": "Level (127)", + "valueChangeOptions": ["transitionDuration"], + "min": 0, + "max": 255, + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "dimmingDuration", + "propertyKey": 127, + "propertyName": "dimmingDuration", + "propertyKeyName": "127", + "ccVersion": 1, + "metadata": { + "type": "duration", + "readable": true, + "writeable": true, + "label": "Dimming duration (127)", + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "level", + "propertyKey": 128, + "propertyName": "level", + "propertyKeyName": "128", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "label": "Level (128)", + "valueChangeOptions": ["transitionDuration"], + "min": 0, + "max": 255, + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "dimmingDuration", + "propertyKey": 128, + "propertyName": "dimmingDuration", + "propertyKeyName": "128", + "ccVersion": 1, + "metadata": { + "type": "duration", + "readable": true, + "writeable": true, + "label": "Dimming duration (128)", + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "level", + "propertyKey": 129, + "propertyName": "level", + "propertyKeyName": "129", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "label": "Level (129)", + "valueChangeOptions": ["transitionDuration"], + "min": 0, + "max": 255, + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "dimmingDuration", + "propertyKey": 129, + "propertyName": "dimmingDuration", + "propertyKeyName": "129", + "ccVersion": 1, + "metadata": { + "type": "duration", + "readable": true, + "writeable": true, + "label": "Dimming duration (129)", + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "level", + "propertyKey": 130, + "propertyName": "level", + "propertyKeyName": "130", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "label": "Level (130)", + "valueChangeOptions": ["transitionDuration"], + "min": 0, + "max": 255, + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "dimmingDuration", + "propertyKey": 130, + "propertyName": "dimmingDuration", + "propertyKeyName": "130", + "ccVersion": 1, + "metadata": { + "type": "duration", + "readable": true, + "writeable": true, + "label": "Dimming duration (130)", + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "level", + "propertyKey": 131, + "propertyName": "level", + "propertyKeyName": "131", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "label": "Level (131)", + "valueChangeOptions": ["transitionDuration"], + "min": 0, + "max": 255, + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "dimmingDuration", + "propertyKey": 131, + "propertyName": "dimmingDuration", + "propertyKeyName": "131", + "ccVersion": 1, + "metadata": { + "type": "duration", + "readable": true, + "writeable": true, + "label": "Dimming duration (131)", + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "level", + "propertyKey": 132, + "propertyName": "level", + "propertyKeyName": "132", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "label": "Level (132)", + "valueChangeOptions": ["transitionDuration"], + "min": 0, + "max": 255, + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "dimmingDuration", + "propertyKey": 132, + "propertyName": "dimmingDuration", + "propertyKeyName": "132", + "ccVersion": 1, + "metadata": { + "type": "duration", + "readable": true, + "writeable": true, + "label": "Dimming duration (132)", + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "level", + "propertyKey": 133, + "propertyName": "level", + "propertyKeyName": "133", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "label": "Level (133)", + "valueChangeOptions": ["transitionDuration"], + "min": 0, + "max": 255, + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "dimmingDuration", + "propertyKey": 133, + "propertyName": "dimmingDuration", + "propertyKeyName": "133", + "ccVersion": 1, + "metadata": { + "type": "duration", + "readable": true, + "writeable": true, + "label": "Dimming duration (133)", + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "level", + "propertyKey": 134, + "propertyName": "level", + "propertyKeyName": "134", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "label": "Level (134)", + "valueChangeOptions": ["transitionDuration"], + "min": 0, + "max": 255, + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "dimmingDuration", + "propertyKey": 134, + "propertyName": "dimmingDuration", + "propertyKeyName": "134", + "ccVersion": 1, + "metadata": { + "type": "duration", + "readable": true, + "writeable": true, + "label": "Dimming duration (134)", + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "level", + "propertyKey": 135, + "propertyName": "level", + "propertyKeyName": "135", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "label": "Level (135)", + "valueChangeOptions": ["transitionDuration"], + "min": 0, + "max": 255, + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "dimmingDuration", + "propertyKey": 135, + "propertyName": "dimmingDuration", + "propertyKeyName": "135", + "ccVersion": 1, + "metadata": { + "type": "duration", + "readable": true, + "writeable": true, + "label": "Dimming duration (135)", + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "level", + "propertyKey": 136, + "propertyName": "level", + "propertyKeyName": "136", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "label": "Level (136)", + "valueChangeOptions": ["transitionDuration"], + "min": 0, + "max": 255, + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "dimmingDuration", + "propertyKey": 136, + "propertyName": "dimmingDuration", + "propertyKeyName": "136", + "ccVersion": 1, + "metadata": { + "type": "duration", + "readable": true, + "writeable": true, + "label": "Dimming duration (136)", + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "level", + "propertyKey": 137, + "propertyName": "level", + "propertyKeyName": "137", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "label": "Level (137)", + "valueChangeOptions": ["transitionDuration"], + "min": 0, + "max": 255, + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "dimmingDuration", + "propertyKey": 137, + "propertyName": "dimmingDuration", + "propertyKeyName": "137", + "ccVersion": 1, + "metadata": { + "type": "duration", + "readable": true, + "writeable": true, + "label": "Dimming duration (137)", + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "level", + "propertyKey": 138, + "propertyName": "level", + "propertyKeyName": "138", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "label": "Level (138)", + "valueChangeOptions": ["transitionDuration"], + "min": 0, + "max": 255, + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "dimmingDuration", + "propertyKey": 138, + "propertyName": "dimmingDuration", + "propertyKeyName": "138", + "ccVersion": 1, + "metadata": { + "type": "duration", + "readable": true, + "writeable": true, + "label": "Dimming duration (138)", + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "level", + "propertyKey": 139, + "propertyName": "level", + "propertyKeyName": "139", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "label": "Level (139)", + "valueChangeOptions": ["transitionDuration"], + "min": 0, + "max": 255, + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "dimmingDuration", + "propertyKey": 139, + "propertyName": "dimmingDuration", + "propertyKeyName": "139", + "ccVersion": 1, + "metadata": { + "type": "duration", + "readable": true, + "writeable": true, + "label": "Dimming duration (139)", + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "level", + "propertyKey": 140, + "propertyName": "level", + "propertyKeyName": "140", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "label": "Level (140)", + "valueChangeOptions": ["transitionDuration"], + "min": 0, + "max": 255, + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "dimmingDuration", + "propertyKey": 140, + "propertyName": "dimmingDuration", + "propertyKeyName": "140", + "ccVersion": 1, + "metadata": { + "type": "duration", + "readable": true, + "writeable": true, + "label": "Dimming duration (140)", + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "level", + "propertyKey": 141, + "propertyName": "level", + "propertyKeyName": "141", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "label": "Level (141)", + "valueChangeOptions": ["transitionDuration"], + "min": 0, + "max": 255, + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "dimmingDuration", + "propertyKey": 141, + "propertyName": "dimmingDuration", + "propertyKeyName": "141", + "ccVersion": 1, + "metadata": { + "type": "duration", + "readable": true, + "writeable": true, + "label": "Dimming duration (141)", + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "level", + "propertyKey": 142, + "propertyName": "level", + "propertyKeyName": "142", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "label": "Level (142)", + "valueChangeOptions": ["transitionDuration"], + "min": 0, + "max": 255, + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "dimmingDuration", + "propertyKey": 142, + "propertyName": "dimmingDuration", + "propertyKeyName": "142", + "ccVersion": 1, + "metadata": { + "type": "duration", + "readable": true, + "writeable": true, + "label": "Dimming duration (142)", + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "level", + "propertyKey": 143, + "propertyName": "level", + "propertyKeyName": "143", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "label": "Level (143)", + "valueChangeOptions": ["transitionDuration"], + "min": 0, + "max": 255, + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "dimmingDuration", + "propertyKey": 143, + "propertyName": "dimmingDuration", + "propertyKeyName": "143", + "ccVersion": 1, + "metadata": { + "type": "duration", + "readable": true, + "writeable": true, + "label": "Dimming duration (143)", + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "level", + "propertyKey": 144, + "propertyName": "level", + "propertyKeyName": "144", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "label": "Level (144)", + "valueChangeOptions": ["transitionDuration"], + "min": 0, + "max": 255, + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "dimmingDuration", + "propertyKey": 144, + "propertyName": "dimmingDuration", + "propertyKeyName": "144", + "ccVersion": 1, + "metadata": { + "type": "duration", + "readable": true, + "writeable": true, + "label": "Dimming duration (144)", + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "level", + "propertyKey": 145, + "propertyName": "level", + "propertyKeyName": "145", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "label": "Level (145)", + "valueChangeOptions": ["transitionDuration"], + "min": 0, + "max": 255, + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "dimmingDuration", + "propertyKey": 145, + "propertyName": "dimmingDuration", + "propertyKeyName": "145", + "ccVersion": 1, + "metadata": { + "type": "duration", + "readable": true, + "writeable": true, + "label": "Dimming duration (145)", + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "level", + "propertyKey": 146, + "propertyName": "level", + "propertyKeyName": "146", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "label": "Level (146)", + "valueChangeOptions": ["transitionDuration"], + "min": 0, + "max": 255, + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "dimmingDuration", + "propertyKey": 146, + "propertyName": "dimmingDuration", + "propertyKeyName": "146", + "ccVersion": 1, + "metadata": { + "type": "duration", + "readable": true, + "writeable": true, + "label": "Dimming duration (146)", + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "level", + "propertyKey": 147, + "propertyName": "level", + "propertyKeyName": "147", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "label": "Level (147)", + "valueChangeOptions": ["transitionDuration"], + "min": 0, + "max": 255, + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "dimmingDuration", + "propertyKey": 147, + "propertyName": "dimmingDuration", + "propertyKeyName": "147", + "ccVersion": 1, + "metadata": { + "type": "duration", + "readable": true, + "writeable": true, + "label": "Dimming duration (147)", + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "level", + "propertyKey": 148, + "propertyName": "level", + "propertyKeyName": "148", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "label": "Level (148)", + "valueChangeOptions": ["transitionDuration"], + "min": 0, + "max": 255, + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "dimmingDuration", + "propertyKey": 148, + "propertyName": "dimmingDuration", + "propertyKeyName": "148", + "ccVersion": 1, + "metadata": { + "type": "duration", + "readable": true, + "writeable": true, + "label": "Dimming duration (148)", + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "level", + "propertyKey": 149, + "propertyName": "level", + "propertyKeyName": "149", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "label": "Level (149)", + "valueChangeOptions": ["transitionDuration"], + "min": 0, + "max": 255, + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "dimmingDuration", + "propertyKey": 149, + "propertyName": "dimmingDuration", + "propertyKeyName": "149", + "ccVersion": 1, + "metadata": { + "type": "duration", + "readable": true, + "writeable": true, + "label": "Dimming duration (149)", + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "level", + "propertyKey": 150, + "propertyName": "level", + "propertyKeyName": "150", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "label": "Level (150)", + "valueChangeOptions": ["transitionDuration"], + "min": 0, + "max": 255, + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "dimmingDuration", + "propertyKey": 150, + "propertyName": "dimmingDuration", + "propertyKeyName": "150", + "ccVersion": 1, + "metadata": { + "type": "duration", + "readable": true, + "writeable": true, + "label": "Dimming duration (150)", + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "level", + "propertyKey": 151, + "propertyName": "level", + "propertyKeyName": "151", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "label": "Level (151)", + "valueChangeOptions": ["transitionDuration"], + "min": 0, + "max": 255, + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "dimmingDuration", + "propertyKey": 151, + "propertyName": "dimmingDuration", + "propertyKeyName": "151", + "ccVersion": 1, + "metadata": { + "type": "duration", + "readable": true, + "writeable": true, + "label": "Dimming duration (151)", + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "level", + "propertyKey": 152, + "propertyName": "level", + "propertyKeyName": "152", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "label": "Level (152)", + "valueChangeOptions": ["transitionDuration"], + "min": 0, + "max": 255, + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "dimmingDuration", + "propertyKey": 152, + "propertyName": "dimmingDuration", + "propertyKeyName": "152", + "ccVersion": 1, + "metadata": { + "type": "duration", + "readable": true, + "writeable": true, + "label": "Dimming duration (152)", + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "level", + "propertyKey": 153, + "propertyName": "level", + "propertyKeyName": "153", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "label": "Level (153)", + "valueChangeOptions": ["transitionDuration"], + "min": 0, + "max": 255, + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "dimmingDuration", + "propertyKey": 153, + "propertyName": "dimmingDuration", + "propertyKeyName": "153", + "ccVersion": 1, + "metadata": { + "type": "duration", + "readable": true, + "writeable": true, + "label": "Dimming duration (153)", + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "level", + "propertyKey": 154, + "propertyName": "level", + "propertyKeyName": "154", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "label": "Level (154)", + "valueChangeOptions": ["transitionDuration"], + "min": 0, + "max": 255, + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "dimmingDuration", + "propertyKey": 154, + "propertyName": "dimmingDuration", + "propertyKeyName": "154", + "ccVersion": 1, + "metadata": { + "type": "duration", + "readable": true, + "writeable": true, + "label": "Dimming duration (154)", + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "level", + "propertyKey": 155, + "propertyName": "level", + "propertyKeyName": "155", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "label": "Level (155)", + "valueChangeOptions": ["transitionDuration"], + "min": 0, + "max": 255, + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "dimmingDuration", + "propertyKey": 155, + "propertyName": "dimmingDuration", + "propertyKeyName": "155", + "ccVersion": 1, + "metadata": { + "type": "duration", + "readable": true, + "writeable": true, + "label": "Dimming duration (155)", + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "level", + "propertyKey": 156, + "propertyName": "level", + "propertyKeyName": "156", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "label": "Level (156)", + "valueChangeOptions": ["transitionDuration"], + "min": 0, + "max": 255, + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "dimmingDuration", + "propertyKey": 156, + "propertyName": "dimmingDuration", + "propertyKeyName": "156", + "ccVersion": 1, + "metadata": { + "type": "duration", + "readable": true, + "writeable": true, + "label": "Dimming duration (156)", + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "level", + "propertyKey": 157, + "propertyName": "level", + "propertyKeyName": "157", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "label": "Level (157)", + "valueChangeOptions": ["transitionDuration"], + "min": 0, + "max": 255, + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "dimmingDuration", + "propertyKey": 157, + "propertyName": "dimmingDuration", + "propertyKeyName": "157", + "ccVersion": 1, + "metadata": { + "type": "duration", + "readable": true, + "writeable": true, + "label": "Dimming duration (157)", + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "level", + "propertyKey": 158, + "propertyName": "level", + "propertyKeyName": "158", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "label": "Level (158)", + "valueChangeOptions": ["transitionDuration"], + "min": 0, + "max": 255, + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "dimmingDuration", + "propertyKey": 158, + "propertyName": "dimmingDuration", + "propertyKeyName": "158", + "ccVersion": 1, + "metadata": { + "type": "duration", + "readable": true, + "writeable": true, + "label": "Dimming duration (158)", + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "level", + "propertyKey": 159, + "propertyName": "level", + "propertyKeyName": "159", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "label": "Level (159)", + "valueChangeOptions": ["transitionDuration"], + "min": 0, + "max": 255, + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "dimmingDuration", + "propertyKey": 159, + "propertyName": "dimmingDuration", + "propertyKeyName": "159", + "ccVersion": 1, + "metadata": { + "type": "duration", + "readable": true, + "writeable": true, + "label": "Dimming duration (159)", + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "level", + "propertyKey": 160, + "propertyName": "level", + "propertyKeyName": "160", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "label": "Level (160)", + "valueChangeOptions": ["transitionDuration"], + "min": 0, + "max": 255, + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "dimmingDuration", + "propertyKey": 160, + "propertyName": "dimmingDuration", + "propertyKeyName": "160", + "ccVersion": 1, + "metadata": { + "type": "duration", + "readable": true, + "writeable": true, + "label": "Dimming duration (160)", + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "level", + "propertyKey": 161, + "propertyName": "level", + "propertyKeyName": "161", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "label": "Level (161)", + "valueChangeOptions": ["transitionDuration"], + "min": 0, + "max": 255, + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "dimmingDuration", + "propertyKey": 161, + "propertyName": "dimmingDuration", + "propertyKeyName": "161", + "ccVersion": 1, + "metadata": { + "type": "duration", + "readable": true, + "writeable": true, + "label": "Dimming duration (161)", + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "level", + "propertyKey": 162, + "propertyName": "level", + "propertyKeyName": "162", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "label": "Level (162)", + "valueChangeOptions": ["transitionDuration"], + "min": 0, + "max": 255, + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "dimmingDuration", + "propertyKey": 162, + "propertyName": "dimmingDuration", + "propertyKeyName": "162", + "ccVersion": 1, + "metadata": { + "type": "duration", + "readable": true, + "writeable": true, + "label": "Dimming duration (162)", + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "level", + "propertyKey": 163, + "propertyName": "level", + "propertyKeyName": "163", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "label": "Level (163)", + "valueChangeOptions": ["transitionDuration"], + "min": 0, + "max": 255, + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "dimmingDuration", + "propertyKey": 163, + "propertyName": "dimmingDuration", + "propertyKeyName": "163", + "ccVersion": 1, + "metadata": { + "type": "duration", + "readable": true, + "writeable": true, + "label": "Dimming duration (163)", + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "level", + "propertyKey": 164, + "propertyName": "level", + "propertyKeyName": "164", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "label": "Level (164)", + "valueChangeOptions": ["transitionDuration"], + "min": 0, + "max": 255, + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "dimmingDuration", + "propertyKey": 164, + "propertyName": "dimmingDuration", + "propertyKeyName": "164", + "ccVersion": 1, + "metadata": { + "type": "duration", + "readable": true, + "writeable": true, + "label": "Dimming duration (164)", + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "level", + "propertyKey": 165, + "propertyName": "level", + "propertyKeyName": "165", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "label": "Level (165)", + "valueChangeOptions": ["transitionDuration"], + "min": 0, + "max": 255, + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "dimmingDuration", + "propertyKey": 165, + "propertyName": "dimmingDuration", + "propertyKeyName": "165", + "ccVersion": 1, + "metadata": { + "type": "duration", + "readable": true, + "writeable": true, + "label": "Dimming duration (165)", + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "level", + "propertyKey": 166, + "propertyName": "level", + "propertyKeyName": "166", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "label": "Level (166)", + "valueChangeOptions": ["transitionDuration"], + "min": 0, + "max": 255, + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "dimmingDuration", + "propertyKey": 166, + "propertyName": "dimmingDuration", + "propertyKeyName": "166", + "ccVersion": 1, + "metadata": { + "type": "duration", + "readable": true, + "writeable": true, + "label": "Dimming duration (166)", + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "level", + "propertyKey": 167, + "propertyName": "level", + "propertyKeyName": "167", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "label": "Level (167)", + "valueChangeOptions": ["transitionDuration"], + "min": 0, + "max": 255, + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "dimmingDuration", + "propertyKey": 167, + "propertyName": "dimmingDuration", + "propertyKeyName": "167", + "ccVersion": 1, + "metadata": { + "type": "duration", + "readable": true, + "writeable": true, + "label": "Dimming duration (167)", + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "level", + "propertyKey": 168, + "propertyName": "level", + "propertyKeyName": "168", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "label": "Level (168)", + "valueChangeOptions": ["transitionDuration"], + "min": 0, + "max": 255, + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "dimmingDuration", + "propertyKey": 168, + "propertyName": "dimmingDuration", + "propertyKeyName": "168", + "ccVersion": 1, + "metadata": { + "type": "duration", + "readable": true, + "writeable": true, + "label": "Dimming duration (168)", + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "level", + "propertyKey": 169, + "propertyName": "level", + "propertyKeyName": "169", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "label": "Level (169)", + "valueChangeOptions": ["transitionDuration"], + "min": 0, + "max": 255, + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "dimmingDuration", + "propertyKey": 169, + "propertyName": "dimmingDuration", + "propertyKeyName": "169", + "ccVersion": 1, + "metadata": { + "type": "duration", + "readable": true, + "writeable": true, + "label": "Dimming duration (169)", + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "level", + "propertyKey": 170, + "propertyName": "level", + "propertyKeyName": "170", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "label": "Level (170)", + "valueChangeOptions": ["transitionDuration"], + "min": 0, + "max": 255, + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "dimmingDuration", + "propertyKey": 170, + "propertyName": "dimmingDuration", + "propertyKeyName": "170", + "ccVersion": 1, + "metadata": { + "type": "duration", + "readable": true, + "writeable": true, + "label": "Dimming duration (170)", + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "level", + "propertyKey": 171, + "propertyName": "level", + "propertyKeyName": "171", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "label": "Level (171)", + "valueChangeOptions": ["transitionDuration"], + "min": 0, + "max": 255, + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "dimmingDuration", + "propertyKey": 171, + "propertyName": "dimmingDuration", + "propertyKeyName": "171", + "ccVersion": 1, + "metadata": { + "type": "duration", + "readable": true, + "writeable": true, + "label": "Dimming duration (171)", + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "level", + "propertyKey": 172, + "propertyName": "level", + "propertyKeyName": "172", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "label": "Level (172)", + "valueChangeOptions": ["transitionDuration"], + "min": 0, + "max": 255, + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "dimmingDuration", + "propertyKey": 172, + "propertyName": "dimmingDuration", + "propertyKeyName": "172", + "ccVersion": 1, + "metadata": { + "type": "duration", + "readable": true, + "writeable": true, + "label": "Dimming duration (172)", + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "level", + "propertyKey": 173, + "propertyName": "level", + "propertyKeyName": "173", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "label": "Level (173)", + "valueChangeOptions": ["transitionDuration"], + "min": 0, + "max": 255, + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "dimmingDuration", + "propertyKey": 173, + "propertyName": "dimmingDuration", + "propertyKeyName": "173", + "ccVersion": 1, + "metadata": { + "type": "duration", + "readable": true, + "writeable": true, + "label": "Dimming duration (173)", + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "level", + "propertyKey": 174, + "propertyName": "level", + "propertyKeyName": "174", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "label": "Level (174)", + "valueChangeOptions": ["transitionDuration"], + "min": 0, + "max": 255, + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "dimmingDuration", + "propertyKey": 174, + "propertyName": "dimmingDuration", + "propertyKeyName": "174", + "ccVersion": 1, + "metadata": { + "type": "duration", + "readable": true, + "writeable": true, + "label": "Dimming duration (174)", + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "level", + "propertyKey": 175, + "propertyName": "level", + "propertyKeyName": "175", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "label": "Level (175)", + "valueChangeOptions": ["transitionDuration"], + "min": 0, + "max": 255, + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "dimmingDuration", + "propertyKey": 175, + "propertyName": "dimmingDuration", + "propertyKeyName": "175", + "ccVersion": 1, + "metadata": { + "type": "duration", + "readable": true, + "writeable": true, + "label": "Dimming duration (175)", + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "level", + "propertyKey": 176, + "propertyName": "level", + "propertyKeyName": "176", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "label": "Level (176)", + "valueChangeOptions": ["transitionDuration"], + "min": 0, + "max": 255, + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "dimmingDuration", + "propertyKey": 176, + "propertyName": "dimmingDuration", + "propertyKeyName": "176", + "ccVersion": 1, + "metadata": { + "type": "duration", + "readable": true, + "writeable": true, + "label": "Dimming duration (176)", + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "level", + "propertyKey": 177, + "propertyName": "level", + "propertyKeyName": "177", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "label": "Level (177)", + "valueChangeOptions": ["transitionDuration"], + "min": 0, + "max": 255, + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "dimmingDuration", + "propertyKey": 177, + "propertyName": "dimmingDuration", + "propertyKeyName": "177", + "ccVersion": 1, + "metadata": { + "type": "duration", + "readable": true, + "writeable": true, + "label": "Dimming duration (177)", + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "level", + "propertyKey": 178, + "propertyName": "level", + "propertyKeyName": "178", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "label": "Level (178)", + "valueChangeOptions": ["transitionDuration"], + "min": 0, + "max": 255, + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "dimmingDuration", + "propertyKey": 178, + "propertyName": "dimmingDuration", + "propertyKeyName": "178", + "ccVersion": 1, + "metadata": { + "type": "duration", + "readable": true, + "writeable": true, + "label": "Dimming duration (178)", + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "level", + "propertyKey": 179, + "propertyName": "level", + "propertyKeyName": "179", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "label": "Level (179)", + "valueChangeOptions": ["transitionDuration"], + "min": 0, + "max": 255, + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "dimmingDuration", + "propertyKey": 179, + "propertyName": "dimmingDuration", + "propertyKeyName": "179", + "ccVersion": 1, + "metadata": { + "type": "duration", + "readable": true, + "writeable": true, + "label": "Dimming duration (179)", + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "level", + "propertyKey": 180, + "propertyName": "level", + "propertyKeyName": "180", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "label": "Level (180)", + "valueChangeOptions": ["transitionDuration"], + "min": 0, + "max": 255, + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "dimmingDuration", + "propertyKey": 180, + "propertyName": "dimmingDuration", + "propertyKeyName": "180", + "ccVersion": 1, + "metadata": { + "type": "duration", + "readable": true, + "writeable": true, + "label": "Dimming duration (180)", + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "level", + "propertyKey": 181, + "propertyName": "level", + "propertyKeyName": "181", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "label": "Level (181)", + "valueChangeOptions": ["transitionDuration"], + "min": 0, + "max": 255, + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "dimmingDuration", + "propertyKey": 181, + "propertyName": "dimmingDuration", + "propertyKeyName": "181", + "ccVersion": 1, + "metadata": { + "type": "duration", + "readable": true, + "writeable": true, + "label": "Dimming duration (181)", + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "level", + "propertyKey": 182, + "propertyName": "level", + "propertyKeyName": "182", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "label": "Level (182)", + "valueChangeOptions": ["transitionDuration"], + "min": 0, + "max": 255, + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "dimmingDuration", + "propertyKey": 182, + "propertyName": "dimmingDuration", + "propertyKeyName": "182", + "ccVersion": 1, + "metadata": { + "type": "duration", + "readable": true, + "writeable": true, + "label": "Dimming duration (182)", + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "level", + "propertyKey": 183, + "propertyName": "level", + "propertyKeyName": "183", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "label": "Level (183)", + "valueChangeOptions": ["transitionDuration"], + "min": 0, + "max": 255, + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "dimmingDuration", + "propertyKey": 183, + "propertyName": "dimmingDuration", + "propertyKeyName": "183", + "ccVersion": 1, + "metadata": { + "type": "duration", + "readable": true, + "writeable": true, + "label": "Dimming duration (183)", + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "level", + "propertyKey": 184, + "propertyName": "level", + "propertyKeyName": "184", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "label": "Level (184)", + "valueChangeOptions": ["transitionDuration"], + "min": 0, + "max": 255, + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "dimmingDuration", + "propertyKey": 184, + "propertyName": "dimmingDuration", + "propertyKeyName": "184", + "ccVersion": 1, + "metadata": { + "type": "duration", + "readable": true, + "writeable": true, + "label": "Dimming duration (184)", + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "level", + "propertyKey": 185, + "propertyName": "level", + "propertyKeyName": "185", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "label": "Level (185)", + "valueChangeOptions": ["transitionDuration"], + "min": 0, + "max": 255, + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "dimmingDuration", + "propertyKey": 185, + "propertyName": "dimmingDuration", + "propertyKeyName": "185", + "ccVersion": 1, + "metadata": { + "type": "duration", + "readable": true, + "writeable": true, + "label": "Dimming duration (185)", + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "level", + "propertyKey": 186, + "propertyName": "level", + "propertyKeyName": "186", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "label": "Level (186)", + "valueChangeOptions": ["transitionDuration"], + "min": 0, + "max": 255, + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "dimmingDuration", + "propertyKey": 186, + "propertyName": "dimmingDuration", + "propertyKeyName": "186", + "ccVersion": 1, + "metadata": { + "type": "duration", + "readable": true, + "writeable": true, + "label": "Dimming duration (186)", + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "level", + "propertyKey": 187, + "propertyName": "level", + "propertyKeyName": "187", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "label": "Level (187)", + "valueChangeOptions": ["transitionDuration"], + "min": 0, + "max": 255, + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "dimmingDuration", + "propertyKey": 187, + "propertyName": "dimmingDuration", + "propertyKeyName": "187", + "ccVersion": 1, + "metadata": { + "type": "duration", + "readable": true, + "writeable": true, + "label": "Dimming duration (187)", + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "level", + "propertyKey": 188, + "propertyName": "level", + "propertyKeyName": "188", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "label": "Level (188)", + "valueChangeOptions": ["transitionDuration"], + "min": 0, + "max": 255, + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "dimmingDuration", + "propertyKey": 188, + "propertyName": "dimmingDuration", + "propertyKeyName": "188", + "ccVersion": 1, + "metadata": { + "type": "duration", + "readable": true, + "writeable": true, + "label": "Dimming duration (188)", + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "level", + "propertyKey": 189, + "propertyName": "level", + "propertyKeyName": "189", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "label": "Level (189)", + "valueChangeOptions": ["transitionDuration"], + "min": 0, + "max": 255, + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "dimmingDuration", + "propertyKey": 189, + "propertyName": "dimmingDuration", + "propertyKeyName": "189", + "ccVersion": 1, + "metadata": { + "type": "duration", + "readable": true, + "writeable": true, + "label": "Dimming duration (189)", + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "level", + "propertyKey": 190, + "propertyName": "level", + "propertyKeyName": "190", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "label": "Level (190)", + "valueChangeOptions": ["transitionDuration"], + "min": 0, + "max": 255, + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "dimmingDuration", + "propertyKey": 190, + "propertyName": "dimmingDuration", + "propertyKeyName": "190", + "ccVersion": 1, + "metadata": { + "type": "duration", + "readable": true, + "writeable": true, + "label": "Dimming duration (190)", + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "level", + "propertyKey": 191, + "propertyName": "level", + "propertyKeyName": "191", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "label": "Level (191)", + "valueChangeOptions": ["transitionDuration"], + "min": 0, + "max": 255, + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "dimmingDuration", + "propertyKey": 191, + "propertyName": "dimmingDuration", + "propertyKeyName": "191", + "ccVersion": 1, + "metadata": { + "type": "duration", + "readable": true, + "writeable": true, + "label": "Dimming duration (191)", + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "level", + "propertyKey": 192, + "propertyName": "level", + "propertyKeyName": "192", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "label": "Level (192)", + "valueChangeOptions": ["transitionDuration"], + "min": 0, + "max": 255, + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "dimmingDuration", + "propertyKey": 192, + "propertyName": "dimmingDuration", + "propertyKeyName": "192", + "ccVersion": 1, + "metadata": { + "type": "duration", + "readable": true, + "writeable": true, + "label": "Dimming duration (192)", + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "level", + "propertyKey": 193, + "propertyName": "level", + "propertyKeyName": "193", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "label": "Level (193)", + "valueChangeOptions": ["transitionDuration"], + "min": 0, + "max": 255, + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "dimmingDuration", + "propertyKey": 193, + "propertyName": "dimmingDuration", + "propertyKeyName": "193", + "ccVersion": 1, + "metadata": { + "type": "duration", + "readable": true, + "writeable": true, + "label": "Dimming duration (193)", + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "level", + "propertyKey": 194, + "propertyName": "level", + "propertyKeyName": "194", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "label": "Level (194)", + "valueChangeOptions": ["transitionDuration"], + "min": 0, + "max": 255, + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "dimmingDuration", + "propertyKey": 194, + "propertyName": "dimmingDuration", + "propertyKeyName": "194", + "ccVersion": 1, + "metadata": { + "type": "duration", + "readable": true, + "writeable": true, + "label": "Dimming duration (194)", + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "level", + "propertyKey": 195, + "propertyName": "level", + "propertyKeyName": "195", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "label": "Level (195)", + "valueChangeOptions": ["transitionDuration"], + "min": 0, + "max": 255, + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "dimmingDuration", + "propertyKey": 195, + "propertyName": "dimmingDuration", + "propertyKeyName": "195", + "ccVersion": 1, + "metadata": { + "type": "duration", + "readable": true, + "writeable": true, + "label": "Dimming duration (195)", + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "level", + "propertyKey": 196, + "propertyName": "level", + "propertyKeyName": "196", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "label": "Level (196)", + "valueChangeOptions": ["transitionDuration"], + "min": 0, + "max": 255, + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "dimmingDuration", + "propertyKey": 196, + "propertyName": "dimmingDuration", + "propertyKeyName": "196", + "ccVersion": 1, + "metadata": { + "type": "duration", + "readable": true, + "writeable": true, + "label": "Dimming duration (196)", + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "level", + "propertyKey": 197, + "propertyName": "level", + "propertyKeyName": "197", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "label": "Level (197)", + "valueChangeOptions": ["transitionDuration"], + "min": 0, + "max": 255, + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "dimmingDuration", + "propertyKey": 197, + "propertyName": "dimmingDuration", + "propertyKeyName": "197", + "ccVersion": 1, + "metadata": { + "type": "duration", + "readable": true, + "writeable": true, + "label": "Dimming duration (197)", + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "level", + "propertyKey": 198, + "propertyName": "level", + "propertyKeyName": "198", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "label": "Level (198)", + "valueChangeOptions": ["transitionDuration"], + "min": 0, + "max": 255, + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "dimmingDuration", + "propertyKey": 198, + "propertyName": "dimmingDuration", + "propertyKeyName": "198", + "ccVersion": 1, + "metadata": { + "type": "duration", + "readable": true, + "writeable": true, + "label": "Dimming duration (198)", + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "level", + "propertyKey": 199, + "propertyName": "level", + "propertyKeyName": "199", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "label": "Level (199)", + "valueChangeOptions": ["transitionDuration"], + "min": 0, + "max": 255, + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "dimmingDuration", + "propertyKey": 199, + "propertyName": "dimmingDuration", + "propertyKeyName": "199", + "ccVersion": 1, + "metadata": { + "type": "duration", + "readable": true, + "writeable": true, + "label": "Dimming duration (199)", + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "level", + "propertyKey": 200, + "propertyName": "level", + "propertyKeyName": "200", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "label": "Level (200)", + "valueChangeOptions": ["transitionDuration"], + "min": 0, + "max": 255, + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "dimmingDuration", + "propertyKey": 200, + "propertyName": "dimmingDuration", + "propertyKeyName": "200", + "ccVersion": 1, + "metadata": { + "type": "duration", + "readable": true, + "writeable": true, + "label": "Dimming duration (200)", + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "level", + "propertyKey": 201, + "propertyName": "level", + "propertyKeyName": "201", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "label": "Level (201)", + "valueChangeOptions": ["transitionDuration"], + "min": 0, + "max": 255, + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "dimmingDuration", + "propertyKey": 201, + "propertyName": "dimmingDuration", + "propertyKeyName": "201", + "ccVersion": 1, + "metadata": { + "type": "duration", + "readable": true, + "writeable": true, + "label": "Dimming duration (201)", + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "level", + "propertyKey": 202, + "propertyName": "level", + "propertyKeyName": "202", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "label": "Level (202)", + "valueChangeOptions": ["transitionDuration"], + "min": 0, + "max": 255, + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "dimmingDuration", + "propertyKey": 202, + "propertyName": "dimmingDuration", + "propertyKeyName": "202", + "ccVersion": 1, + "metadata": { + "type": "duration", + "readable": true, + "writeable": true, + "label": "Dimming duration (202)", + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "level", + "propertyKey": 203, + "propertyName": "level", + "propertyKeyName": "203", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "label": "Level (203)", + "valueChangeOptions": ["transitionDuration"], + "min": 0, + "max": 255, + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "dimmingDuration", + "propertyKey": 203, + "propertyName": "dimmingDuration", + "propertyKeyName": "203", + "ccVersion": 1, + "metadata": { + "type": "duration", + "readable": true, + "writeable": true, + "label": "Dimming duration (203)", + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "level", + "propertyKey": 204, + "propertyName": "level", + "propertyKeyName": "204", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "label": "Level (204)", + "valueChangeOptions": ["transitionDuration"], + "min": 0, + "max": 255, + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "dimmingDuration", + "propertyKey": 204, + "propertyName": "dimmingDuration", + "propertyKeyName": "204", + "ccVersion": 1, + "metadata": { + "type": "duration", + "readable": true, + "writeable": true, + "label": "Dimming duration (204)", + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "level", + "propertyKey": 205, + "propertyName": "level", + "propertyKeyName": "205", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "label": "Level (205)", + "valueChangeOptions": ["transitionDuration"], + "min": 0, + "max": 255, + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "dimmingDuration", + "propertyKey": 205, + "propertyName": "dimmingDuration", + "propertyKeyName": "205", + "ccVersion": 1, + "metadata": { + "type": "duration", + "readable": true, + "writeable": true, + "label": "Dimming duration (205)", + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "level", + "propertyKey": 206, + "propertyName": "level", + "propertyKeyName": "206", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "label": "Level (206)", + "valueChangeOptions": ["transitionDuration"], + "min": 0, + "max": 255, + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "dimmingDuration", + "propertyKey": 206, + "propertyName": "dimmingDuration", + "propertyKeyName": "206", + "ccVersion": 1, + "metadata": { + "type": "duration", + "readable": true, + "writeable": true, + "label": "Dimming duration (206)", + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "level", + "propertyKey": 207, + "propertyName": "level", + "propertyKeyName": "207", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "label": "Level (207)", + "valueChangeOptions": ["transitionDuration"], + "min": 0, + "max": 255, + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "dimmingDuration", + "propertyKey": 207, + "propertyName": "dimmingDuration", + "propertyKeyName": "207", + "ccVersion": 1, + "metadata": { + "type": "duration", + "readable": true, + "writeable": true, + "label": "Dimming duration (207)", + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "level", + "propertyKey": 208, + "propertyName": "level", + "propertyKeyName": "208", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "label": "Level (208)", + "valueChangeOptions": ["transitionDuration"], + "min": 0, + "max": 255, + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "dimmingDuration", + "propertyKey": 208, + "propertyName": "dimmingDuration", + "propertyKeyName": "208", + "ccVersion": 1, + "metadata": { + "type": "duration", + "readable": true, + "writeable": true, + "label": "Dimming duration (208)", + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "level", + "propertyKey": 209, + "propertyName": "level", + "propertyKeyName": "209", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "label": "Level (209)", + "valueChangeOptions": ["transitionDuration"], + "min": 0, + "max": 255, + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "dimmingDuration", + "propertyKey": 209, + "propertyName": "dimmingDuration", + "propertyKeyName": "209", + "ccVersion": 1, + "metadata": { + "type": "duration", + "readable": true, + "writeable": true, + "label": "Dimming duration (209)", + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "level", + "propertyKey": 210, + "propertyName": "level", + "propertyKeyName": "210", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "label": "Level (210)", + "valueChangeOptions": ["transitionDuration"], + "min": 0, + "max": 255, + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "dimmingDuration", + "propertyKey": 210, + "propertyName": "dimmingDuration", + "propertyKeyName": "210", + "ccVersion": 1, + "metadata": { + "type": "duration", + "readable": true, + "writeable": true, + "label": "Dimming duration (210)", + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "level", + "propertyKey": 211, + "propertyName": "level", + "propertyKeyName": "211", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "label": "Level (211)", + "valueChangeOptions": ["transitionDuration"], + "min": 0, + "max": 255, + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "dimmingDuration", + "propertyKey": 211, + "propertyName": "dimmingDuration", + "propertyKeyName": "211", + "ccVersion": 1, + "metadata": { + "type": "duration", + "readable": true, + "writeable": true, + "label": "Dimming duration (211)", + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "level", + "propertyKey": 212, + "propertyName": "level", + "propertyKeyName": "212", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "label": "Level (212)", + "valueChangeOptions": ["transitionDuration"], + "min": 0, + "max": 255, + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "dimmingDuration", + "propertyKey": 212, + "propertyName": "dimmingDuration", + "propertyKeyName": "212", + "ccVersion": 1, + "metadata": { + "type": "duration", + "readable": true, + "writeable": true, + "label": "Dimming duration (212)", + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "level", + "propertyKey": 213, + "propertyName": "level", + "propertyKeyName": "213", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "label": "Level (213)", + "valueChangeOptions": ["transitionDuration"], + "min": 0, + "max": 255, + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "dimmingDuration", + "propertyKey": 213, + "propertyName": "dimmingDuration", + "propertyKeyName": "213", + "ccVersion": 1, + "metadata": { + "type": "duration", + "readable": true, + "writeable": true, + "label": "Dimming duration (213)", + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "level", + "propertyKey": 214, + "propertyName": "level", + "propertyKeyName": "214", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "label": "Level (214)", + "valueChangeOptions": ["transitionDuration"], + "min": 0, + "max": 255, + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "dimmingDuration", + "propertyKey": 214, + "propertyName": "dimmingDuration", + "propertyKeyName": "214", + "ccVersion": 1, + "metadata": { + "type": "duration", + "readable": true, + "writeable": true, + "label": "Dimming duration (214)", + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "level", + "propertyKey": 215, + "propertyName": "level", + "propertyKeyName": "215", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "label": "Level (215)", + "valueChangeOptions": ["transitionDuration"], + "min": 0, + "max": 255, + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "dimmingDuration", + "propertyKey": 215, + "propertyName": "dimmingDuration", + "propertyKeyName": "215", + "ccVersion": 1, + "metadata": { + "type": "duration", + "readable": true, + "writeable": true, + "label": "Dimming duration (215)", + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "level", + "propertyKey": 216, + "propertyName": "level", + "propertyKeyName": "216", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "label": "Level (216)", + "valueChangeOptions": ["transitionDuration"], + "min": 0, + "max": 255, + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "dimmingDuration", + "propertyKey": 216, + "propertyName": "dimmingDuration", + "propertyKeyName": "216", + "ccVersion": 1, + "metadata": { + "type": "duration", + "readable": true, + "writeable": true, + "label": "Dimming duration (216)", + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "level", + "propertyKey": 217, + "propertyName": "level", + "propertyKeyName": "217", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "label": "Level (217)", + "valueChangeOptions": ["transitionDuration"], + "min": 0, + "max": 255, + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "dimmingDuration", + "propertyKey": 217, + "propertyName": "dimmingDuration", + "propertyKeyName": "217", + "ccVersion": 1, + "metadata": { + "type": "duration", + "readable": true, + "writeable": true, + "label": "Dimming duration (217)", + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "level", + "propertyKey": 218, + "propertyName": "level", + "propertyKeyName": "218", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "label": "Level (218)", + "valueChangeOptions": ["transitionDuration"], + "min": 0, + "max": 255, + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "dimmingDuration", + "propertyKey": 218, + "propertyName": "dimmingDuration", + "propertyKeyName": "218", + "ccVersion": 1, + "metadata": { + "type": "duration", + "readable": true, + "writeable": true, + "label": "Dimming duration (218)", + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "level", + "propertyKey": 219, + "propertyName": "level", + "propertyKeyName": "219", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "label": "Level (219)", + "valueChangeOptions": ["transitionDuration"], + "min": 0, + "max": 255, + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "dimmingDuration", + "propertyKey": 219, + "propertyName": "dimmingDuration", + "propertyKeyName": "219", + "ccVersion": 1, + "metadata": { + "type": "duration", + "readable": true, + "writeable": true, + "label": "Dimming duration (219)", + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "level", + "propertyKey": 220, + "propertyName": "level", + "propertyKeyName": "220", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "label": "Level (220)", + "valueChangeOptions": ["transitionDuration"], + "min": 0, + "max": 255, + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "dimmingDuration", + "propertyKey": 220, + "propertyName": "dimmingDuration", + "propertyKeyName": "220", + "ccVersion": 1, + "metadata": { + "type": "duration", + "readable": true, + "writeable": true, + "label": "Dimming duration (220)", + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "level", + "propertyKey": 221, + "propertyName": "level", + "propertyKeyName": "221", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "label": "Level (221)", + "valueChangeOptions": ["transitionDuration"], + "min": 0, + "max": 255, + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "dimmingDuration", + "propertyKey": 221, + "propertyName": "dimmingDuration", + "propertyKeyName": "221", + "ccVersion": 1, + "metadata": { + "type": "duration", + "readable": true, + "writeable": true, + "label": "Dimming duration (221)", + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "level", + "propertyKey": 222, + "propertyName": "level", + "propertyKeyName": "222", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "label": "Level (222)", + "valueChangeOptions": ["transitionDuration"], + "min": 0, + "max": 255, + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "dimmingDuration", + "propertyKey": 222, + "propertyName": "dimmingDuration", + "propertyKeyName": "222", + "ccVersion": 1, + "metadata": { + "type": "duration", + "readable": true, + "writeable": true, + "label": "Dimming duration (222)", + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "level", + "propertyKey": 223, + "propertyName": "level", + "propertyKeyName": "223", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "label": "Level (223)", + "valueChangeOptions": ["transitionDuration"], + "min": 0, + "max": 255, + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "dimmingDuration", + "propertyKey": 223, + "propertyName": "dimmingDuration", + "propertyKeyName": "223", + "ccVersion": 1, + "metadata": { + "type": "duration", + "readable": true, + "writeable": true, + "label": "Dimming duration (223)", + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "level", + "propertyKey": 224, + "propertyName": "level", + "propertyKeyName": "224", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "label": "Level (224)", + "valueChangeOptions": ["transitionDuration"], + "min": 0, + "max": 255, + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "dimmingDuration", + "propertyKey": 224, + "propertyName": "dimmingDuration", + "propertyKeyName": "224", + "ccVersion": 1, + "metadata": { + "type": "duration", + "readable": true, + "writeable": true, + "label": "Dimming duration (224)", + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "level", + "propertyKey": 225, + "propertyName": "level", + "propertyKeyName": "225", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "label": "Level (225)", + "valueChangeOptions": ["transitionDuration"], + "min": 0, + "max": 255, + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "dimmingDuration", + "propertyKey": 225, + "propertyName": "dimmingDuration", + "propertyKeyName": "225", + "ccVersion": 1, + "metadata": { + "type": "duration", + "readable": true, + "writeable": true, + "label": "Dimming duration (225)", + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "level", + "propertyKey": 226, + "propertyName": "level", + "propertyKeyName": "226", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "label": "Level (226)", + "valueChangeOptions": ["transitionDuration"], + "min": 0, + "max": 255, + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "dimmingDuration", + "propertyKey": 226, + "propertyName": "dimmingDuration", + "propertyKeyName": "226", + "ccVersion": 1, + "metadata": { + "type": "duration", + "readable": true, + "writeable": true, + "label": "Dimming duration (226)", + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "level", + "propertyKey": 227, + "propertyName": "level", + "propertyKeyName": "227", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "label": "Level (227)", + "valueChangeOptions": ["transitionDuration"], + "min": 0, + "max": 255, + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "dimmingDuration", + "propertyKey": 227, + "propertyName": "dimmingDuration", + "propertyKeyName": "227", + "ccVersion": 1, + "metadata": { + "type": "duration", + "readable": true, + "writeable": true, + "label": "Dimming duration (227)", + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "level", + "propertyKey": 228, + "propertyName": "level", + "propertyKeyName": "228", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "label": "Level (228)", + "valueChangeOptions": ["transitionDuration"], + "min": 0, + "max": 255, + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "dimmingDuration", + "propertyKey": 228, + "propertyName": "dimmingDuration", + "propertyKeyName": "228", + "ccVersion": 1, + "metadata": { + "type": "duration", + "readable": true, + "writeable": true, + "label": "Dimming duration (228)", + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "level", + "propertyKey": 229, + "propertyName": "level", + "propertyKeyName": "229", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "label": "Level (229)", + "valueChangeOptions": ["transitionDuration"], + "min": 0, + "max": 255, + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "dimmingDuration", + "propertyKey": 229, + "propertyName": "dimmingDuration", + "propertyKeyName": "229", + "ccVersion": 1, + "metadata": { + "type": "duration", + "readable": true, + "writeable": true, + "label": "Dimming duration (229)", + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "level", + "propertyKey": 230, + "propertyName": "level", + "propertyKeyName": "230", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "label": "Level (230)", + "valueChangeOptions": ["transitionDuration"], + "min": 0, + "max": 255, + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "dimmingDuration", + "propertyKey": 230, + "propertyName": "dimmingDuration", + "propertyKeyName": "230", + "ccVersion": 1, + "metadata": { + "type": "duration", + "readable": true, + "writeable": true, + "label": "Dimming duration (230)", + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "level", + "propertyKey": 231, + "propertyName": "level", + "propertyKeyName": "231", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "label": "Level (231)", + "valueChangeOptions": ["transitionDuration"], + "min": 0, + "max": 255, + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "dimmingDuration", + "propertyKey": 231, + "propertyName": "dimmingDuration", + "propertyKeyName": "231", + "ccVersion": 1, + "metadata": { + "type": "duration", + "readable": true, + "writeable": true, + "label": "Dimming duration (231)", + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "level", + "propertyKey": 232, + "propertyName": "level", + "propertyKeyName": "232", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "label": "Level (232)", + "valueChangeOptions": ["transitionDuration"], + "min": 0, + "max": 255, + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "dimmingDuration", + "propertyKey": 232, + "propertyName": "dimmingDuration", + "propertyKeyName": "232", + "ccVersion": 1, + "metadata": { + "type": "duration", + "readable": true, + "writeable": true, + "label": "Dimming duration (232)", + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "level", + "propertyKey": 233, + "propertyName": "level", + "propertyKeyName": "233", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "label": "Level (233)", + "valueChangeOptions": ["transitionDuration"], + "min": 0, + "max": 255, + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "dimmingDuration", + "propertyKey": 233, + "propertyName": "dimmingDuration", + "propertyKeyName": "233", + "ccVersion": 1, + "metadata": { + "type": "duration", + "readable": true, + "writeable": true, + "label": "Dimming duration (233)", + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "level", + "propertyKey": 234, + "propertyName": "level", + "propertyKeyName": "234", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "label": "Level (234)", + "valueChangeOptions": ["transitionDuration"], + "min": 0, + "max": 255, + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "dimmingDuration", + "propertyKey": 234, + "propertyName": "dimmingDuration", + "propertyKeyName": "234", + "ccVersion": 1, + "metadata": { + "type": "duration", + "readable": true, + "writeable": true, + "label": "Dimming duration (234)", + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "level", + "propertyKey": 235, + "propertyName": "level", + "propertyKeyName": "235", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "label": "Level (235)", + "valueChangeOptions": ["transitionDuration"], + "min": 0, + "max": 255, + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "dimmingDuration", + "propertyKey": 235, + "propertyName": "dimmingDuration", + "propertyKeyName": "235", + "ccVersion": 1, + "metadata": { + "type": "duration", + "readable": true, + "writeable": true, + "label": "Dimming duration (235)", + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "level", + "propertyKey": 236, + "propertyName": "level", + "propertyKeyName": "236", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "label": "Level (236)", + "valueChangeOptions": ["transitionDuration"], + "min": 0, + "max": 255, + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "dimmingDuration", + "propertyKey": 236, + "propertyName": "dimmingDuration", + "propertyKeyName": "236", + "ccVersion": 1, + "metadata": { + "type": "duration", + "readable": true, + "writeable": true, + "label": "Dimming duration (236)", + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "level", + "propertyKey": 237, + "propertyName": "level", + "propertyKeyName": "237", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "label": "Level (237)", + "valueChangeOptions": ["transitionDuration"], + "min": 0, + "max": 255, + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "dimmingDuration", + "propertyKey": 237, + "propertyName": "dimmingDuration", + "propertyKeyName": "237", + "ccVersion": 1, + "metadata": { + "type": "duration", + "readable": true, + "writeable": true, + "label": "Dimming duration (237)", + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "level", + "propertyKey": 238, + "propertyName": "level", + "propertyKeyName": "238", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "label": "Level (238)", + "valueChangeOptions": ["transitionDuration"], + "min": 0, + "max": 255, + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "dimmingDuration", + "propertyKey": 238, + "propertyName": "dimmingDuration", + "propertyKeyName": "238", + "ccVersion": 1, + "metadata": { + "type": "duration", + "readable": true, + "writeable": true, + "label": "Dimming duration (238)", + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "level", + "propertyKey": 239, + "propertyName": "level", + "propertyKeyName": "239", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "label": "Level (239)", + "valueChangeOptions": ["transitionDuration"], + "min": 0, + "max": 255, + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "dimmingDuration", + "propertyKey": 239, + "propertyName": "dimmingDuration", + "propertyKeyName": "239", + "ccVersion": 1, + "metadata": { + "type": "duration", + "readable": true, + "writeable": true, + "label": "Dimming duration (239)", + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "level", + "propertyKey": 240, + "propertyName": "level", + "propertyKeyName": "240", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "label": "Level (240)", + "valueChangeOptions": ["transitionDuration"], + "min": 0, + "max": 255, + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "dimmingDuration", + "propertyKey": 240, + "propertyName": "dimmingDuration", + "propertyKeyName": "240", + "ccVersion": 1, + "metadata": { + "type": "duration", + "readable": true, + "writeable": true, + "label": "Dimming duration (240)", + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "level", + "propertyKey": 241, + "propertyName": "level", + "propertyKeyName": "241", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "label": "Level (241)", + "valueChangeOptions": ["transitionDuration"], + "min": 0, + "max": 255, + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "dimmingDuration", + "propertyKey": 241, + "propertyName": "dimmingDuration", + "propertyKeyName": "241", + "ccVersion": 1, + "metadata": { + "type": "duration", + "readable": true, + "writeable": true, + "label": "Dimming duration (241)", + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "level", + "propertyKey": 242, + "propertyName": "level", + "propertyKeyName": "242", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "label": "Level (242)", + "valueChangeOptions": ["transitionDuration"], + "min": 0, + "max": 255, + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "dimmingDuration", + "propertyKey": 242, + "propertyName": "dimmingDuration", + "propertyKeyName": "242", + "ccVersion": 1, + "metadata": { + "type": "duration", + "readable": true, + "writeable": true, + "label": "Dimming duration (242)", + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "level", + "propertyKey": 243, + "propertyName": "level", + "propertyKeyName": "243", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "label": "Level (243)", + "valueChangeOptions": ["transitionDuration"], + "min": 0, + "max": 255, + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "dimmingDuration", + "propertyKey": 243, + "propertyName": "dimmingDuration", + "propertyKeyName": "243", + "ccVersion": 1, + "metadata": { + "type": "duration", + "readable": true, + "writeable": true, + "label": "Dimming duration (243)", + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "level", + "propertyKey": 244, + "propertyName": "level", + "propertyKeyName": "244", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "label": "Level (244)", + "valueChangeOptions": ["transitionDuration"], + "min": 0, + "max": 255, + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "dimmingDuration", + "propertyKey": 244, + "propertyName": "dimmingDuration", + "propertyKeyName": "244", + "ccVersion": 1, + "metadata": { + "type": "duration", + "readable": true, + "writeable": true, + "label": "Dimming duration (244)", + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "level", + "propertyKey": 245, + "propertyName": "level", + "propertyKeyName": "245", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "label": "Level (245)", + "valueChangeOptions": ["transitionDuration"], + "min": 0, + "max": 255, + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "dimmingDuration", + "propertyKey": 245, + "propertyName": "dimmingDuration", + "propertyKeyName": "245", + "ccVersion": 1, + "metadata": { + "type": "duration", + "readable": true, + "writeable": true, + "label": "Dimming duration (245)", + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "level", + "propertyKey": 246, + "propertyName": "level", + "propertyKeyName": "246", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "label": "Level (246)", + "valueChangeOptions": ["transitionDuration"], + "min": 0, + "max": 255, + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "dimmingDuration", + "propertyKey": 246, + "propertyName": "dimmingDuration", + "propertyKeyName": "246", + "ccVersion": 1, + "metadata": { + "type": "duration", + "readable": true, + "writeable": true, + "label": "Dimming duration (246)", + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "level", + "propertyKey": 247, + "propertyName": "level", + "propertyKeyName": "247", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "label": "Level (247)", + "valueChangeOptions": ["transitionDuration"], + "min": 0, + "max": 255, + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "dimmingDuration", + "propertyKey": 247, + "propertyName": "dimmingDuration", + "propertyKeyName": "247", + "ccVersion": 1, + "metadata": { + "type": "duration", + "readable": true, + "writeable": true, + "label": "Dimming duration (247)", + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "level", + "propertyKey": 248, + "propertyName": "level", + "propertyKeyName": "248", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "label": "Level (248)", + "valueChangeOptions": ["transitionDuration"], + "min": 0, + "max": 255, + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "dimmingDuration", + "propertyKey": 248, + "propertyName": "dimmingDuration", + "propertyKeyName": "248", + "ccVersion": 1, + "metadata": { + "type": "duration", + "readable": true, + "writeable": true, + "label": "Dimming duration (248)", + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "level", + "propertyKey": 249, + "propertyName": "level", + "propertyKeyName": "249", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "label": "Level (249)", + "valueChangeOptions": ["transitionDuration"], + "min": 0, + "max": 255, + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "dimmingDuration", + "propertyKey": 249, + "propertyName": "dimmingDuration", + "propertyKeyName": "249", + "ccVersion": 1, + "metadata": { + "type": "duration", + "readable": true, + "writeable": true, + "label": "Dimming duration (249)", + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "level", + "propertyKey": 250, + "propertyName": "level", + "propertyKeyName": "250", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "label": "Level (250)", + "valueChangeOptions": ["transitionDuration"], + "min": 0, + "max": 255, + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "dimmingDuration", + "propertyKey": 250, + "propertyName": "dimmingDuration", + "propertyKeyName": "250", + "ccVersion": 1, + "metadata": { + "type": "duration", + "readable": true, + "writeable": true, + "label": "Dimming duration (250)", + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "level", + "propertyKey": 251, + "propertyName": "level", + "propertyKeyName": "251", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "label": "Level (251)", + "valueChangeOptions": ["transitionDuration"], + "min": 0, + "max": 255, + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "dimmingDuration", + "propertyKey": 251, + "propertyName": "dimmingDuration", + "propertyKeyName": "251", + "ccVersion": 1, + "metadata": { + "type": "duration", + "readable": true, + "writeable": true, + "label": "Dimming duration (251)", + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "level", + "propertyKey": 252, + "propertyName": "level", + "propertyKeyName": "252", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "label": "Level (252)", + "valueChangeOptions": ["transitionDuration"], + "min": 0, + "max": 255, + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "dimmingDuration", + "propertyKey": 252, + "propertyName": "dimmingDuration", + "propertyKeyName": "252", + "ccVersion": 1, + "metadata": { + "type": "duration", + "readable": true, + "writeable": true, + "label": "Dimming duration (252)", + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "level", + "propertyKey": 253, + "propertyName": "level", + "propertyKeyName": "253", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "label": "Level (253)", + "valueChangeOptions": ["transitionDuration"], + "min": 0, + "max": 255, + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "dimmingDuration", + "propertyKey": 253, + "propertyName": "dimmingDuration", + "propertyKeyName": "253", + "ccVersion": 1, + "metadata": { + "type": "duration", + "readable": true, + "writeable": true, + "label": "Dimming duration (253)", + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "level", + "propertyKey": 254, + "propertyName": "level", + "propertyKeyName": "254", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "label": "Level (254)", + "valueChangeOptions": ["transitionDuration"], + "min": 0, + "max": 255, + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "dimmingDuration", + "propertyKey": 254, + "propertyName": "dimmingDuration", + "propertyKeyName": "254", + "ccVersion": 1, + "metadata": { + "type": "duration", + "readable": true, + "writeable": true, + "label": "Dimming duration (254)", + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "level", + "propertyKey": 255, + "propertyName": "level", + "propertyKeyName": "255", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "label": "Level (255)", + "valueChangeOptions": ["transitionDuration"], + "min": 0, + "max": 255, + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 44, + "commandClassName": "Scene Actuator Configuration", + "property": "dimmingDuration", + "propertyKey": 255, + "propertyName": "dimmingDuration", + "propertyKeyName": "255", + "ccVersion": 1, + "metadata": { + "type": "duration", + "readable": true, + "writeable": true, + "label": "Dimming duration (255)", + "stateful": true, + "secret": false + } + }, + { + "endpoint": 0, + "commandClass": 112, + "commandClassName": "Configuration", + "property": 1, + "propertyName": "Fade On Time", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "description": "Values 1-127 = seconds; 128-253 = minutes (minus 127)", + "label": "Fade On Time", + "default": 2, + "min": 0, + "max": 253, + "states": { + "0": "Instant on" + }, + "valueSize": 1, + "format": 1, + "allowManualEntry": true, + "isFromConfig": true + }, + "value": 2 + }, + { + "endpoint": 0, + "commandClass": 112, + "commandClassName": "Configuration", + "property": 2, + "propertyName": "Fade Off Time", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "description": "Values 1-127 = seconds; 128-253 = minutes (minus 127)", + "label": "Fade Off Time", + "default": 2, + "min": 0, + "max": 253, + "states": { + "0": "Instant off" + }, + "valueSize": 1, + "format": 1, + "allowManualEntry": true, + "isFromConfig": true + }, + "value": 2 + }, + { + "endpoint": 0, + "commandClass": 112, + "commandClassName": "Configuration", + "property": 3, + "propertyName": "Minimum Dim Level", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "label": "Minimum Dim Level", + "default": 10, + "min": 1, + "max": 99, + "valueSize": 1, + "format": 1, + "allowManualEntry": true, + "isFromConfig": true + }, + "value": 10 + }, + { + "endpoint": 0, + "commandClass": 112, + "commandClassName": "Configuration", + "property": 4, + "propertyName": "Maximum Dim Level", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "label": "Maximum Dim Level", + "default": 100, + "min": 0, + "max": 100, + "valueSize": 1, + "format": 1, + "allowManualEntry": true, + "isFromConfig": true + }, + "value": 100 + }, + { + "endpoint": 0, + "commandClass": 112, + "commandClassName": "Configuration", + "property": 5, + "propertyName": "Initial Dim Level", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "label": "Initial Dim Level", + "default": 0, + "min": 0, + "max": 100, + "states": { + "0": "Last dim level" + }, + "unit": "%", + "valueSize": 1, + "format": 0, + "allowManualEntry": true, + "isFromConfig": true + }, + "value": 0 + }, + { + "endpoint": 0, + "commandClass": 112, + "commandClassName": "Configuration", + "property": 6, + "propertyName": "LED Dim Level Indicator Timeout", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "description": "How long the level indicators should stay illuminated after the dimming level is changed", + "label": "LED Dim Level Indicator Timeout", + "default": 3, + "min": 0, + "max": 255, + "states": { + "0": "Always Off", + "255": "Always On" + }, + "unit": "seconds", + "valueSize": 1, + "format": 1, + "allowManualEntry": true, + "isFromConfig": true + }, + "value": 3 + }, + { + "endpoint": 0, + "commandClass": 112, + "commandClassName": "Configuration", + "property": 7, + "propertyName": "Locator LED Status", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "label": "Locator LED Status", + "default": 255, + "min": 0, + "max": 255, + "states": { + "0": "LED always off", + "254": "LED on when switch is on", + "255": "LED on when switch is off" + }, + "valueSize": 1, + "format": 1, + "allowManualEntry": false, + "isFromConfig": true + }, + "value": 255 + }, + { + "endpoint": 0, + "commandClass": 112, + "commandClassName": "Configuration", + "property": 8, + "propertyName": "Load Type", + "ccVersion": 1, + "metadata": { + "type": "number", + "readable": true, + "writeable": true, + "label": "Load Type", + "default": 0, + "min": 0, + "max": 2, + "states": { + "0": "Incandescent", + "1": "LED", + "2": "CFL" + }, + "valueSize": 1, + "format": 0, + "allowManualEntry": false, + "isFromConfig": true + }, + "value": 0 + }, + { + "endpoint": 0, + "commandClass": 114, + "commandClassName": "Manufacturer Specific", + "property": "manufacturerId", + "propertyName": "manufacturerId", + "ccVersion": 2, + "metadata": { + "type": "number", + "readable": true, + "writeable": false, + "label": "Manufacturer ID", + "min": 0, + "max": 65535, + "stateful": true, + "secret": false + }, + "value": 29 + }, + { + "endpoint": 0, + "commandClass": 114, + "commandClassName": "Manufacturer Specific", + "property": "productType", + "propertyName": "productType", + "ccVersion": 2, + "metadata": { + "type": "number", + "readable": true, + "writeable": false, + "label": "Product type", + "min": 0, + "max": 65535, + "stateful": true, + "secret": false + }, + "value": 12801 + }, + { + "endpoint": 0, + "commandClass": 114, + "commandClassName": "Manufacturer Specific", + "property": "productId", + "propertyName": "productId", + "ccVersion": 2, + "metadata": { + "type": "number", + "readable": true, + "writeable": false, + "label": "Product ID", + "min": 0, + "max": 65535, + "stateful": true, + "secret": false + }, + "value": 1 + }, + { + "endpoint": 0, + "commandClass": 134, + "commandClassName": "Version", + "property": "libraryType", + "propertyName": "libraryType", + "ccVersion": 2, + "metadata": { + "type": "number", + "readable": true, + "writeable": false, + "label": "Library type", + "states": { + "0": "Unknown", + "1": "Static Controller", + "2": "Controller", + "3": "Enhanced Slave", + "4": "Slave", + "5": "Installer", + "6": "Routing Slave", + "7": "Bridge Controller", + "8": "Device under Test", + "9": "N/A", + "10": "AV Remote", + "11": "AV Device" + }, + "stateful": true, + "secret": false + }, + "value": 3 + }, + { + "endpoint": 0, + "commandClass": 134, + "commandClassName": "Version", + "property": "protocolVersion", + "propertyName": "protocolVersion", + "ccVersion": 2, + "metadata": { + "type": "string", + "readable": true, + "writeable": false, + "label": "Z-Wave protocol version", + "stateful": true, + "secret": false + }, + "value": "4.33" + }, + { + "endpoint": 0, + "commandClass": 134, + "commandClassName": "Version", + "property": "firmwareVersions", + "propertyName": "firmwareVersions", + "ccVersion": 2, + "metadata": { + "type": "string[]", + "readable": true, + "writeable": false, + "label": "Z-Wave chip firmware versions", + "stateful": true, + "secret": false + }, + "value": ["1.20"] + }, + { + "endpoint": 0, + "commandClass": 134, + "commandClassName": "Version", + "property": "hardwareVersion", + "propertyName": "hardwareVersion", + "ccVersion": 2, + "metadata": { + "type": "number", + "readable": true, + "writeable": false, + "label": "Z-Wave chip hardware version", + "stateful": true, + "secret": false + }, + "value": 255 + } + ], + "endpoints": [ + { + "nodeId": 45, + "index": 0, + "installerIcon": 1536, + "userIcon": 1536, + "deviceClass": null, + "commandClasses": [ + { + "id": 94, + "name": "Z-Wave Plus Info", + "version": 2, + "isSecure": false + }, + { + "id": 133, + "name": "Association", + "version": 2, + "isSecure": false + }, + { + "id": 89, + "name": "Association Group Information", + "version": 1, + "isSecure": false + }, + { + "id": 134, + "name": "Version", + "version": 2, + "isSecure": false + }, + { + "id": 114, + "name": "Manufacturer Specific", + "version": 2, + "isSecure": false + }, + { + "id": 112, + "name": "Configuration", + "version": 1, + "isSecure": false + }, + { + "id": 90, + "name": "Device Reset Locally", + "version": 1, + "isSecure": false + }, + { + "id": 115, + "name": "Powerlevel", + "version": 1, + "isSecure": false + }, + { + "id": 38, + "name": "Multilevel Switch", + "version": 4, + "isSecure": false + }, + { + "id": 44, + "name": "Scene Actuator Configuration", + "version": 1, + "isSecure": false + }, + { + "id": 43, + "name": "Scene Activation", + "version": 1, + "isSecure": false + }, + { + "id": 122, + "name": "Firmware Update Meta Data", + "version": 4, + "isSecure": false + } + ] + } + ] +} diff --git a/tests/components/zwave_js/test_discovery.py b/tests/components/zwave_js/test_discovery.py index fe231707629..9c926f9b19b 100644 --- a/tests/components/zwave_js/test_discovery.py +++ b/tests/components/zwave_js/test_discovery.py @@ -305,3 +305,15 @@ async def test_indicator_test( "propertyKey": "Switch", } assert args["value"] is False + + +async def test_light_device_class_is_null( + hass: HomeAssistant, client, light_device_class_is_null, integration +) -> None: + """Test that a Multilevel Switch CC value with a null device class is discovered as a light. + + Tied to #117121. + """ + node = light_device_class_is_null + assert node.device_class is None + assert hass.states.get("light.bar_display_cases") From dba4785c9b9bf36e665bc7cb398d54db3ffd1c76 Mon Sep 17 00:00:00 2001 From: Jan Bouwhuis Date: Sun, 12 May 2024 13:13:41 +0200 Subject: [PATCH 074/114] Increase MQTT broker socket buffer size (#117267) * Increase MQTT broker socket buffer size * Revert unrelated change * Try to increase buffer size * Set INITIAL_SUBSCRIBE_COOLDOWN back to 0.5 sec * Sinplify and add test * comments * comments --------- Co-authored-by: J. Nick Koston --- homeassistant/components/mqtt/client.py | 37 ++++++++++++++++++++++++- tests/components/mqtt/test_init.py | 28 +++++++++++++++++++ 2 files changed, 64 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/mqtt/client.py b/homeassistant/components/mqtt/client.py index 589113d3a9e..8245363fd85 100644 --- a/homeassistant/components/mqtt/client.py +++ b/homeassistant/components/mqtt/client.py @@ -83,8 +83,18 @@ if TYPE_CHECKING: _LOGGER = logging.getLogger(__name__) +MIN_BUFFER_SIZE = 131072 # Minimum buffer size to use if preferred size fails +PREFERRED_BUFFER_SIZE = 2097152 # Set receive buffer size to 2MB + DISCOVERY_COOLDOWN = 5 -INITIAL_SUBSCRIBE_COOLDOWN = 3.0 +# The initial subscribe cooldown controls how long to wait to group +# subscriptions together. This is to avoid making too many subscribe +# requests in a short period of time. If the number is too low, the +# system will be flooded with subscribe requests. If the number is too +# high, we risk being flooded with responses to the subscribe requests +# which can exceed the receive buffer size of the socket. To mitigate +# this, we increase the receive buffer size of the socket as well. +INITIAL_SUBSCRIBE_COOLDOWN = 0.5 SUBSCRIBE_COOLDOWN = 0.1 UNSUBSCRIBE_COOLDOWN = 0.1 TIMEOUT_ACK = 10 @@ -429,6 +439,7 @@ class MQTT: hass.bus.async_listen(EVENT_HOMEASSISTANT_STOP, self._async_ha_stop), ) ) + self._socket_buffersize: int | None = None @callback def _async_ha_started(self, _hass: HomeAssistant) -> None: @@ -529,6 +540,29 @@ class MQTT: self.hass, self._misc_loop(), name="mqtt misc loop" ) + def _increase_socket_buffer_size(self, sock: SocketType) -> None: + """Increase the socket buffer size.""" + new_buffer_size = PREFERRED_BUFFER_SIZE + while True: + try: + # Some operating systems do not allow us to set the preferred + # buffer size. In that case we try some other size options. + sock.setsockopt(socket.SOL_SOCKET, socket.SO_RCVBUF, new_buffer_size) + except OSError as err: + if new_buffer_size <= MIN_BUFFER_SIZE: + _LOGGER.warning( + "Unable to increase the socket buffer size to %s; " + "The connection may be unstable if the MQTT broker " + "sends data at volume or a large amount of subscriptions " + "need to be processed: %s", + new_buffer_size, + err, + ) + return + new_buffer_size //= 2 + else: + return + def _on_socket_open( self, client: mqtt.Client, userdata: Any, sock: SocketType ) -> None: @@ -545,6 +579,7 @@ class MQTT: fileno = sock.fileno() _LOGGER.debug("%s: connection opened %s", self.config_entry.title, fileno) if fileno > -1: + self._increase_socket_buffer_size(sock) self.loop.add_reader(sock, partial(self._async_reader_callback, client)) self._async_start_misc_loop() diff --git a/tests/components/mqtt/test_init.py b/tests/components/mqtt/test_init.py index ec7968ae46b..448d41c59cc 100644 --- a/tests/components/mqtt/test_init.py +++ b/tests/components/mqtt/test_init.py @@ -4382,6 +4382,34 @@ async def test_server_sock_connect_and_disconnect( assert len(calls) == 0 +@patch("homeassistant.components.mqtt.client.INITIAL_SUBSCRIBE_COOLDOWN", 0.0) +@patch("homeassistant.components.mqtt.client.DISCOVERY_COOLDOWN", 0.0) +@patch("homeassistant.components.mqtt.client.SUBSCRIBE_COOLDOWN", 0.0) +async def test_server_sock_buffer_size( + hass: HomeAssistant, + mqtt_client_mock: MqttMockPahoClient, + mqtt_mock_entry: MqttMockHAClientGenerator, + caplog: pytest.LogCaptureFixture, +) -> None: + """Test handling the socket buffer size fails.""" + mqtt_mock = await mqtt_mock_entry() + await hass.async_block_till_done() + assert mqtt_mock.connected is True + + mqtt_client_mock.loop_misc.return_value = paho_mqtt.MQTT_ERR_SUCCESS + + client, server = socket.socketpair( + family=socket.AF_UNIX, type=socket.SOCK_STREAM, proto=0 + ) + client.setblocking(False) + server.setblocking(False) + with patch.object(client, "setsockopt", side_effect=OSError("foo")): + mqtt_client_mock.on_socket_open(mqtt_client_mock, None, client) + mqtt_client_mock.on_socket_register_write(mqtt_client_mock, None, client) + await hass.async_block_till_done() + assert "Unable to increase the socket buffer size" in caplog.text + + @patch("homeassistant.components.mqtt.client.INITIAL_SUBSCRIBE_COOLDOWN", 0.0) @patch("homeassistant.components.mqtt.client.DISCOVERY_COOLDOWN", 0.0) @patch("homeassistant.components.mqtt.client.SUBSCRIBE_COOLDOWN", 0.0) From bca20646bba4825d46eda40b38c120925e454916 Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Sun, 12 May 2024 21:36:21 +0200 Subject: [PATCH 075/114] Fix Aurora naming (#117314) --- homeassistant/components/aurora/entity.py | 1 + 1 file changed, 1 insertion(+) diff --git a/homeassistant/components/aurora/entity.py b/homeassistant/components/aurora/entity.py index 3aa917862fb..e0dd1de3b15 100644 --- a/homeassistant/components/aurora/entity.py +++ b/homeassistant/components/aurora/entity.py @@ -15,6 +15,7 @@ class AuroraEntity(CoordinatorEntity[AuroraDataUpdateCoordinator]): """Implementation of the base Aurora Entity.""" _attr_attribution = ATTRIBUTION + _attr_has_entity_name = True def __init__( self, From 642a6b44ebe314a658794eb5d46b3fb89bbb137f Mon Sep 17 00:00:00 2001 From: tronikos Date: Sun, 12 May 2024 19:19:20 -0700 Subject: [PATCH 076/114] Call Google Assistant SDK service using async_add_executor_job (#117325) --- homeassistant/components/google_assistant_sdk/__init__.py | 4 +++- homeassistant/components/google_assistant_sdk/helpers.py | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/google_assistant_sdk/__init__.py b/homeassistant/components/google_assistant_sdk/__init__.py index 7d8653b509d..52950a82b93 100644 --- a/homeassistant/components/google_assistant_sdk/__init__.py +++ b/homeassistant/components/google_assistant_sdk/__init__.py @@ -169,7 +169,9 @@ class GoogleAssistantConversationAgent(conversation.AbstractConversationAgent): self.language = user_input.language self.assistant = TextAssistant(credentials, self.language) - resp = self.assistant.assist(user_input.text) + resp = await self.hass.async_add_executor_job( + self.assistant.assist, user_input.text + ) text_response = resp[0] or "" intent_response = intent.IntentResponse(language=user_input.language) diff --git a/homeassistant/components/google_assistant_sdk/helpers.py b/homeassistant/components/google_assistant_sdk/helpers.py index ccd0fe765ac..b6b13f92fcf 100644 --- a/homeassistant/components/google_assistant_sdk/helpers.py +++ b/homeassistant/components/google_assistant_sdk/helpers.py @@ -79,7 +79,7 @@ async def async_send_text_commands( ) as assistant: command_response_list = [] for command in commands: - resp = assistant.assist(command) + resp = await hass.async_add_executor_job(assistant.assist, command) text_response = resp[0] _LOGGER.debug("command: %s\nresponse: %s", command, text_response) audio_response = resp[2] From c90818e10cd262e342faf13511748e50beeab0c4 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 13 May 2024 11:18:52 +0900 Subject: [PATCH 077/114] Fix squeezebox blocking startup (#117331) fixes #117079 --- homeassistant/components/squeezebox/media_player.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/squeezebox/media_player.py b/homeassistant/components/squeezebox/media_player.py index a3a404fe1ae..e822fe817b9 100644 --- a/homeassistant/components/squeezebox/media_player.py +++ b/homeassistant/components/squeezebox/media_player.py @@ -92,7 +92,7 @@ SQUEEZEBOX_MODE = { } -async def start_server_discovery(hass): +async def start_server_discovery(hass: HomeAssistant) -> None: """Start a server discovery task.""" def _discovered_server(server): @@ -110,8 +110,9 @@ async def start_server_discovery(hass): hass.data.setdefault(DOMAIN, {}) if DISCOVERY_TASK not in hass.data[DOMAIN]: _LOGGER.debug("Adding server discovery task for squeezebox") - hass.data[DOMAIN][DISCOVERY_TASK] = hass.async_create_task( - async_discover(_discovered_server) + hass.data[DOMAIN][DISCOVERY_TASK] = hass.async_create_background_task( + async_discover(_discovered_server), + name="squeezebox server discovery", ) From f48f8eefe7b650bceb071c4675395eef6b710baf Mon Sep 17 00:00:00 2001 From: Jiaqi Wu Date: Mon, 13 May 2024 17:05:12 -0700 Subject: [PATCH 078/114] Fix Lutron Serena Tilt Only Wood Blinds set tilt function (#117374) --- homeassistant/components/lutron_caseta/cover.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/lutron_caseta/cover.py b/homeassistant/components/lutron_caseta/cover.py index aa5c2f4e0b9..04fbb9e54c1 100644 --- a/homeassistant/components/lutron_caseta/cover.py +++ b/homeassistant/components/lutron_caseta/cover.py @@ -96,7 +96,7 @@ class LutronCasetaTiltOnlyBlind(LutronCasetaDeviceUpdatableEntity, CoverEntity): async def async_set_cover_tilt_position(self, **kwargs: Any) -> None: """Move the blind to a specific tilt.""" - self._smartbridge.set_tilt(self.device_id, kwargs[ATTR_TILT_POSITION]) + await self._smartbridge.set_tilt(self.device_id, kwargs[ATTR_TILT_POSITION]) PYLUTRON_TYPE_TO_CLASSES = { From e7ff552de6db56d0eb70c17766d25b373b021686 Mon Sep 17 00:00:00 2001 From: mk-81 <63057155+mk-81@users.noreply.github.com> Date: Tue, 14 May 2024 21:02:17 +0200 Subject: [PATCH 079/114] Fix Kodi on/off status (#117436) * Fix Kodi Issue 104603 Fixes issue, that Kodi media player is displayed as online even when offline. The issue occurrs when using HTTP(S) only (no web Socket) integration after kodi was found online once. Issue: In async_update the connection exceptions from self._kodi.get_players are not catched and therefore self._players (and the like) are not reset. The call of self._connection.connected returns always true for HTTP(S) connections. Solution: Catch Exceptions from self._kodi.get_players und reset state in case of HTTP(S) only connection. Otherwise keep current behaviour. * Fix Kodi Issue 104603 / code style adjustments as requested --- homeassistant/components/kodi/media_player.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/kodi/media_player.py b/homeassistant/components/kodi/media_player.py index 74140ca873c..27b2d3e0199 100644 --- a/homeassistant/components/kodi/media_player.py +++ b/homeassistant/components/kodi/media_player.py @@ -480,7 +480,13 @@ class KodiEntity(MediaPlayerEntity): self._reset_state() return - self._players = await self._kodi.get_players() + try: + self._players = await self._kodi.get_players() + except (TransportError, ProtocolError): + if not self._connection.can_subscribe: + self._reset_state() + return + raise if self._kodi_is_off: self._reset_state() From 819e9860a8cd96c10de54a599b7c32a1010e0c0d Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Tue, 14 May 2024 19:17:50 +0200 Subject: [PATCH 080/114] Update wled to 0.17.1 (#117444) --- homeassistant/components/wled/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/wled/manifest.json b/homeassistant/components/wled/manifest.json index b6e14963b9e..fd15d8ef171 100644 --- a/homeassistant/components/wled/manifest.json +++ b/homeassistant/components/wled/manifest.json @@ -7,6 +7,6 @@ "integration_type": "device", "iot_class": "local_push", "quality_scale": "platinum", - "requirements": ["wled==0.17.0"], + "requirements": ["wled==0.17.1"], "zeroconf": ["_wled._tcp.local."] } diff --git a/requirements_all.txt b/requirements_all.txt index d867cd826bc..2d5c6fd4769 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2866,7 +2866,7 @@ wiffi==1.1.2 wirelesstagpy==0.8.1 # homeassistant.components.wled -wled==0.17.0 +wled==0.17.1 # homeassistant.components.wolflink wolf-comm==0.0.7 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 640c4cfcfd1..da0cf834fa3 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -2222,7 +2222,7 @@ whois==0.9.27 wiffi==1.1.2 # homeassistant.components.wled -wled==0.17.0 +wled==0.17.1 # homeassistant.components.wolflink wolf-comm==0.0.7 From 970ad8c07c2f5a723b00404f69fbd6469e1cedc0 Mon Sep 17 00:00:00 2001 From: Maikel Punie Date: Tue, 14 May 2024 19:22:13 +0200 Subject: [PATCH 081/114] Bump pyduotecno to 2024.5.0 (#117446) --- homeassistant/components/duotecno/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/duotecno/manifest.json b/homeassistant/components/duotecno/manifest.json index 0c8eab8f0a0..e74c12227db 100644 --- a/homeassistant/components/duotecno/manifest.json +++ b/homeassistant/components/duotecno/manifest.json @@ -7,5 +7,5 @@ "iot_class": "local_push", "loggers": ["pyduotecno", "pyduotecno-node", "pyduotecno-unit"], "quality_scale": "silver", - "requirements": ["pyDuotecno==2024.3.2"] + "requirements": ["pyDuotecno==2024.5.0"] } diff --git a/requirements_all.txt b/requirements_all.txt index 2d5c6fd4769..15cfc0e1394 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1649,7 +1649,7 @@ pyCEC==0.5.2 pyControl4==1.1.0 # homeassistant.components.duotecno -pyDuotecno==2024.3.2 +pyDuotecno==2024.5.0 # homeassistant.components.electrasmart pyElectra==1.2.0 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index da0cf834fa3..c69f0514cf7 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1305,7 +1305,7 @@ pyCEC==0.5.2 pyControl4==1.1.0 # homeassistant.components.duotecno -pyDuotecno==2024.3.2 +pyDuotecno==2024.5.0 # homeassistant.components.electrasmart pyElectra==1.2.0 From b86513c3a4306cb8f477ca1f752613392c04e5c7 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Wed, 15 May 2024 19:08:24 +0900 Subject: [PATCH 082/114] Fix non-thread-safe state write in tellduslive (#117487) --- homeassistant/components/tellduslive/const.py | 1 - homeassistant/components/tellduslive/cover.py | 6 +++--- homeassistant/components/tellduslive/entry.py | 18 ++++-------------- homeassistant/components/tellduslive/light.py | 2 +- homeassistant/components/tellduslive/switch.py | 4 ++-- 5 files changed, 10 insertions(+), 21 deletions(-) diff --git a/homeassistant/components/tellduslive/const.py b/homeassistant/components/tellduslive/const.py index 3a24f6b033a..eee36879ba9 100644 --- a/homeassistant/components/tellduslive/const.py +++ b/homeassistant/components/tellduslive/const.py @@ -24,7 +24,6 @@ SCAN_INTERVAL = timedelta(minutes=1) ATTR_LAST_UPDATED = "time_last_updated" -SIGNAL_UPDATE_ENTITY = "tellduslive_update" TELLDUS_DISCOVERY_NEW = "telldus_new_{}_{}" CLOUD_NAME = "Cloud API" diff --git a/homeassistant/components/tellduslive/cover.py b/homeassistant/components/tellduslive/cover.py index 57c6ae9e7eb..de962041333 100644 --- a/homeassistant/components/tellduslive/cover.py +++ b/homeassistant/components/tellduslive/cover.py @@ -46,14 +46,14 @@ class TelldusLiveCover(TelldusLiveEntity, CoverEntity): def close_cover(self, **kwargs: Any) -> None: """Close the cover.""" self.device.down() - self._update_callback() + self.schedule_update_ha_state() def open_cover(self, **kwargs: Any) -> None: """Open the cover.""" self.device.up() - self._update_callback() + self.schedule_update_ha_state() def stop_cover(self, **kwargs: Any) -> None: """Stop the cover.""" self.device.stop() - self._update_callback() + self.schedule_update_ha_state() diff --git a/homeassistant/components/tellduslive/entry.py b/homeassistant/components/tellduslive/entry.py index 77a04fabd06..a71fcb685c0 100644 --- a/homeassistant/components/tellduslive/entry.py +++ b/homeassistant/components/tellduslive/entry.py @@ -11,7 +11,6 @@ from homeassistant.const import ( ATTR_MODEL, ATTR_VIA_DEVICE, ) -from homeassistant.core import callback from homeassistant.helpers.device_registry import DeviceInfo from homeassistant.helpers.dispatcher import async_dispatcher_connect from homeassistant.helpers.entity import Entity @@ -33,25 +32,16 @@ class TelldusLiveEntity(Entity): """Initialize the entity.""" self._id = device_id self._client = client - self._async_unsub_dispatcher_connect = None async def async_added_to_hass(self): """Call when entity is added to hass.""" _LOGGER.debug("Created device %s", self) - self._async_unsub_dispatcher_connect = async_dispatcher_connect( - self.hass, SIGNAL_UPDATE_ENTITY, self._update_callback + self.async_on_remove( + async_dispatcher_connect( + self.hass, SIGNAL_UPDATE_ENTITY, self.async_write_ha_state + ) ) - async def async_will_remove_from_hass(self): - """Disconnect dispatcher listener when removed.""" - if self._async_unsub_dispatcher_connect: - self._async_unsub_dispatcher_connect() - - @callback - def _update_callback(self): - """Return the property of the device might have changed.""" - self.async_write_ha_state() - @property def device_id(self): """Return the id of the device.""" diff --git a/homeassistant/components/tellduslive/light.py b/homeassistant/components/tellduslive/light.py index 63af8a32527..101ccb0dab0 100644 --- a/homeassistant/components/tellduslive/light.py +++ b/homeassistant/components/tellduslive/light.py @@ -50,7 +50,7 @@ class TelldusLiveLight(TelldusLiveEntity, LightEntity): def changed(self): """Define a property of the device that might have changed.""" self._last_brightness = self.brightness - self._update_callback() + self.schedule_update_ha_state() @property def brightness(self): diff --git a/homeassistant/components/tellduslive/switch.py b/homeassistant/components/tellduslive/switch.py index c26a8dcf951..cd28a170442 100644 --- a/homeassistant/components/tellduslive/switch.py +++ b/homeassistant/components/tellduslive/switch.py @@ -45,9 +45,9 @@ class TelldusLiveSwitch(TelldusLiveEntity, SwitchEntity): def turn_on(self, **kwargs: Any) -> None: """Turn the switch on.""" self.device.turn_on() - self._update_callback() + self.schedule_update_ha_state() def turn_off(self, **kwargs: Any) -> None: """Turn the switch off.""" self.device.turn_off() - self._update_callback() + self.schedule_update_ha_state() From 615ae780ca1925d6f89cd6f130ce6cb8c6fbdea1 Mon Sep 17 00:00:00 2001 From: starkillerOG Date: Wed, 15 May 2024 12:04:12 +0200 Subject: [PATCH 083/114] Reolink fix not unregistering webhook during ReAuth (#117490) --- homeassistant/components/reolink/__init__.py | 1 + tests/components/reolink/test_init.py | 7 ++++++- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/reolink/__init__.py b/homeassistant/components/reolink/__init__.py index 3196dbf3ad7..22b616f9f43 100644 --- a/homeassistant/components/reolink/__init__.py +++ b/homeassistant/components/reolink/__init__.py @@ -85,6 +85,7 @@ async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> b try: await host.update_states() except CredentialsInvalidError as err: + await host.stop() raise ConfigEntryAuthFailed(err) from err except ReolinkError as err: raise UpdateFailed(str(err)) from err diff --git a/tests/components/reolink/test_init.py b/tests/components/reolink/test_init.py index 4ec02244c91..261f572bf2e 100644 --- a/tests/components/reolink/test_init.py +++ b/tests/components/reolink/test_init.py @@ -5,7 +5,7 @@ from typing import Any from unittest.mock import AsyncMock, MagicMock, Mock, patch import pytest -from reolink_aio.exceptions import ReolinkError +from reolink_aio.exceptions import CredentialsInvalidError, ReolinkError from homeassistant.components.reolink import FIRMWARE_UPDATE_INTERVAL, const from homeassistant.config import async_process_ha_core_config @@ -50,6 +50,11 @@ pytestmark = pytest.mark.usefixtures("reolink_connect", "reolink_platforms") AsyncMock(side_effect=ReolinkError("Test error")), ConfigEntryState.SETUP_RETRY, ), + ( + "get_states", + AsyncMock(side_effect=CredentialsInvalidError("Test error")), + ConfigEntryState.SETUP_ERROR, + ), ( "supported", Mock(return_value=False), From b1746faa47a6701e67536f3a0a982bce6e3a4541 Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Wed, 15 May 2024 13:39:07 +0200 Subject: [PATCH 084/114] Fix API creation for passwordless pi_hole (#117494) --- homeassistant/components/pi_hole/__init__.py | 2 +- tests/components/pi_hole/test_init.py | 35 ++++++++++++++++++-- 2 files changed, 34 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/pi_hole/__init__.py b/homeassistant/components/pi_hole/__init__.py index f892114b26c..922590a5cde 100644 --- a/homeassistant/components/pi_hole/__init__.py +++ b/homeassistant/components/pi_hole/__init__.py @@ -55,7 +55,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: use_tls = entry.data[CONF_SSL] verify_tls = entry.data[CONF_VERIFY_SSL] location = entry.data[CONF_LOCATION] - api_key = entry.data.get(CONF_API_KEY) + api_key = entry.data.get(CONF_API_KEY, "") # remove obsolet CONF_STATISTICS_ONLY from entry.data if CONF_STATISTICS_ONLY in entry.data: diff --git a/tests/components/pi_hole/test_init.py b/tests/components/pi_hole/test_init.py index a58a46680bb..b8d66286c64 100644 --- a/tests/components/pi_hole/test_init.py +++ b/tests/components/pi_hole/test_init.py @@ -1,7 +1,7 @@ """Test pi_hole component.""" import logging -from unittest.mock import AsyncMock +from unittest.mock import ANY, AsyncMock from hole.exceptions import HoleError import pytest @@ -12,12 +12,20 @@ from homeassistant.components.pi_hole.const import ( SERVICE_DISABLE, SERVICE_DISABLE_ATTR_DURATION, ) -from homeassistant.const import ATTR_ENTITY_ID, CONF_HOST, CONF_NAME +from homeassistant.const import ( + ATTR_ENTITY_ID, + CONF_HOST, + CONF_LOCATION, + CONF_NAME, + CONF_SSL, +) from homeassistant.core import HomeAssistant from . import ( + API_KEY, CONFIG_DATA, CONFIG_DATA_DEFAULTS, + CONFIG_ENTRY_WITHOUT_API_KEY, SWITCH_ENTITY_ID, _create_mocked_hole, _patch_init_hole, @@ -26,6 +34,29 @@ from . import ( from tests.common import MockConfigEntry +@pytest.mark.parametrize( + ("config_entry_data", "expected_api_token"), + [(CONFIG_DATA_DEFAULTS, API_KEY), (CONFIG_ENTRY_WITHOUT_API_KEY, "")], +) +async def test_setup_api( + hass: HomeAssistant, config_entry_data: dict, expected_api_token: str +) -> None: + """Tests the API object is created with the expected parameters.""" + mocked_hole = _create_mocked_hole() + config_entry_data = {**config_entry_data, CONF_STATISTICS_ONLY: True} + entry = MockConfigEntry(domain=pi_hole.DOMAIN, data=config_entry_data) + entry.add_to_hass(hass) + with _patch_init_hole(mocked_hole) as patched_init_hole: + assert await hass.config_entries.async_setup(entry.entry_id) + patched_init_hole.assert_called_once_with( + config_entry_data[CONF_HOST], + ANY, + api_token=expected_api_token, + location=config_entry_data[CONF_LOCATION], + tls=config_entry_data[CONF_SSL], + ) + + async def test_setup_with_defaults(hass: HomeAssistant) -> None: """Tests component setup with default config.""" mocked_hole = _create_mocked_hole() From 4548ff619c8992b7a9050e360b797baf97998ff0 Mon Sep 17 00:00:00 2001 From: starkillerOG Date: Wed, 15 May 2024 16:19:02 +0200 Subject: [PATCH 085/114] Bump reolink-aio to 0.8.10 (#117501) --- homeassistant/components/reolink/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/reolink/manifest.json b/homeassistant/components/reolink/manifest.json index 81d11e2fd0a..1cec4c90890 100644 --- a/homeassistant/components/reolink/manifest.json +++ b/homeassistant/components/reolink/manifest.json @@ -18,5 +18,5 @@ "documentation": "https://www.home-assistant.io/integrations/reolink", "iot_class": "local_push", "loggers": ["reolink_aio"], - "requirements": ["reolink-aio==0.8.9"] + "requirements": ["reolink-aio==0.8.10"] } diff --git a/requirements_all.txt b/requirements_all.txt index 15cfc0e1394..675b01a31b9 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2439,7 +2439,7 @@ renault-api==0.2.2 renson-endura-delta==1.7.1 # homeassistant.components.reolink -reolink-aio==0.8.9 +reolink-aio==0.8.10 # homeassistant.components.idteck_prox rfk101py==0.0.1 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index c69f0514cf7..c313ef952a3 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1897,7 +1897,7 @@ renault-api==0.2.2 renson-endura-delta==1.7.1 # homeassistant.components.reolink -reolink-aio==0.8.9 +reolink-aio==0.8.10 # homeassistant.components.rflink rflink==0.0.66 From ab9ed0eba4fa2e7d9134a6d639ed50a947b258b7 Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Thu, 16 May 2024 13:43:03 +0200 Subject: [PATCH 086/114] Handle uncaught exceptions in Analytics insights (#117558) --- .../analytics_insights/config_flow.py | 3 ++ .../analytics_insights/strings.json | 3 +- .../analytics_insights/test_config_flow.py | 28 ++++++++++++------- 3 files changed, 23 insertions(+), 11 deletions(-) diff --git a/homeassistant/components/analytics_insights/config_flow.py b/homeassistant/components/analytics_insights/config_flow.py index cef5ac2e9e5..909290b1035 100644 --- a/homeassistant/components/analytics_insights/config_flow.py +++ b/homeassistant/components/analytics_insights/config_flow.py @@ -82,6 +82,9 @@ class HomeassistantAnalyticsConfigFlow(ConfigFlow, domain=DOMAIN): except HomeassistantAnalyticsConnectionError: LOGGER.exception("Error connecting to Home Assistant analytics") return self.async_abort(reason="cannot_connect") + except Exception: # noqa: BLE001 + LOGGER.exception("Unexpected error") + return self.async_abort(reason="unknown") options = [ SelectOptionDict( diff --git a/homeassistant/components/analytics_insights/strings.json b/homeassistant/components/analytics_insights/strings.json index 00c9cfa4404..3b770f189a4 100644 --- a/homeassistant/components/analytics_insights/strings.json +++ b/homeassistant/components/analytics_insights/strings.json @@ -13,7 +13,8 @@ } }, "abort": { - "cannot_connect": "[%key:common::config_flow::error::cannot_connect%]" + "cannot_connect": "[%key:common::config_flow::error::cannot_connect%]", + "unknown": "[%key:common::config_flow::error::unknown%]" }, "error": { "no_integration_selected": "You must select at least one integration to track" diff --git a/tests/components/analytics_insights/test_config_flow.py b/tests/components/analytics_insights/test_config_flow.py index 77264eb2439..6bfd0e798ce 100644 --- a/tests/components/analytics_insights/test_config_flow.py +++ b/tests/components/analytics_insights/test_config_flow.py @@ -6,12 +6,12 @@ from unittest.mock import AsyncMock import pytest from python_homeassistant_analytics import HomeassistantAnalyticsConnectionError -from homeassistant import config_entries from homeassistant.components.analytics_insights.const import ( CONF_TRACKED_CUSTOM_INTEGRATIONS, CONF_TRACKED_INTEGRATIONS, DOMAIN, ) +from homeassistant.config_entries import SOURCE_USER from homeassistant.core import HomeAssistant from homeassistant.data_entry_flow import FlowResultType @@ -61,7 +61,7 @@ async def test_form( ) -> None: """Test we get the form.""" result = await hass.config_entries.flow.async_init( - DOMAIN, context={"source": config_entries.SOURCE_USER} + DOMAIN, context={"source": SOURCE_USER} ) assert result["type"] is FlowResultType.FORM @@ -96,7 +96,7 @@ async def test_submitting_empty_form( ) -> None: """Test we can't submit an empty form.""" result = await hass.config_entries.flow.async_init( - DOMAIN, context={"source": config_entries.SOURCE_USER} + DOMAIN, context={"source": SOURCE_USER} ) assert result["type"] is FlowResultType.FORM @@ -128,20 +128,28 @@ async def test_submitting_empty_form( assert len(mock_setup_entry.mock_calls) == 1 +@pytest.mark.parametrize( + ("exception", "reason"), + [ + (HomeassistantAnalyticsConnectionError, "cannot_connect"), + (Exception, "unknown"), + ], +) async def test_form_cannot_connect( - hass: HomeAssistant, mock_analytics_client: AsyncMock + hass: HomeAssistant, + mock_analytics_client: AsyncMock, + exception: Exception, + reason: str, ) -> None: """Test we handle cannot connect error.""" - mock_analytics_client.get_integrations.side_effect = ( - HomeassistantAnalyticsConnectionError - ) + mock_analytics_client.get_integrations.side_effect = exception result = await hass.config_entries.flow.async_init( - DOMAIN, context={"source": config_entries.SOURCE_USER} + DOMAIN, context={"source": SOURCE_USER} ) assert result["type"] is FlowResultType.ABORT - assert result["reason"] == "cannot_connect" + assert result["reason"] == reason async def test_form_already_configured( @@ -159,7 +167,7 @@ async def test_form_already_configured( entry.add_to_hass(hass) result = await hass.config_entries.flow.async_init( - DOMAIN, context={"source": config_entries.SOURCE_USER} + DOMAIN, context={"source": SOURCE_USER} ) assert result["type"] is FlowResultType.ABORT assert result["reason"] == "single_instance_allowed" From 5cd101d2b18239a2ea5d2c15409bfc551cfec405 Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Thu, 16 May 2024 16:42:40 +0200 Subject: [PATCH 087/114] Fix poolsense naming (#117567) --- homeassistant/components/poolsense/entity.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/poolsense/entity.py b/homeassistant/components/poolsense/entity.py index eaf2c4ab540..88abe67670a 100644 --- a/homeassistant/components/poolsense/entity.py +++ b/homeassistant/components/poolsense/entity.py @@ -1,9 +1,10 @@ """Base entity for poolsense integration.""" +from homeassistant.helpers.device_registry import DeviceInfo from homeassistant.helpers.entity import EntityDescription from homeassistant.helpers.update_coordinator import CoordinatorEntity -from .const import ATTRIBUTION +from .const import ATTRIBUTION, DOMAIN from .coordinator import PoolSenseDataUpdateCoordinator @@ -11,6 +12,7 @@ class PoolSenseEntity(CoordinatorEntity[PoolSenseDataUpdateCoordinator]): """Implements a common class elements representing the PoolSense component.""" _attr_attribution = ATTRIBUTION + _attr_has_entity_name = True def __init__( self, @@ -21,5 +23,8 @@ class PoolSenseEntity(CoordinatorEntity[PoolSenseDataUpdateCoordinator]): """Initialize poolsense sensor.""" super().__init__(coordinator) self.entity_description = description - self._attr_name = f"PoolSense {description.name}" self._attr_unique_id = f"{email}-{description.key}" + self._attr_device_info = DeviceInfo( + identifiers={(DOMAIN, email)}, + model="PoolSense", + ) From f043b2db49feafe588a89767af680b809b22bd14 Mon Sep 17 00:00:00 2001 From: Robert Svensson Date: Fri, 17 May 2024 08:44:09 +0200 Subject: [PATCH 088/114] Improve syncing light states to deCONZ groups (#117588) --- homeassistant/components/deconz/light.py | 34 ++++++++++++++++++------ tests/components/deconz/test_light.py | 2 +- 2 files changed, 27 insertions(+), 9 deletions(-) diff --git a/homeassistant/components/deconz/light.py b/homeassistant/components/deconz/light.py index fc5388d2b33..91a8bdf6110 100644 --- a/homeassistant/components/deconz/light.py +++ b/homeassistant/components/deconz/light.py @@ -2,13 +2,13 @@ from __future__ import annotations -from typing import Any, TypedDict, TypeVar +from typing import Any, TypedDict, TypeVar, cast from pydeconz.interfaces.groups import GroupHandler from pydeconz.interfaces.lights import LightHandler from pydeconz.models import ResourceType from pydeconz.models.event import EventType -from pydeconz.models.group import Group +from pydeconz.models.group import Group, TypedGroupAction from pydeconz.models.light.light import Light, LightAlert, LightColorMode, LightEffect from homeassistant.components.light import ( @@ -105,6 +105,23 @@ class SetStateAttributes(TypedDict, total=False): xy: tuple[float, float] +def update_color_state( + group: Group, lights: list[Light], override: bool = False +) -> None: + """Sync group color state with light.""" + data = { + attribute: light_attribute + for light in lights + for attribute in ("bri", "ct", "hue", "sat", "xy", "colormode", "effect") + if (light_attribute := light.raw["state"].get(attribute)) is not None + } + + if override: + group.raw["action"] = cast(TypedGroupAction, data) + else: + group.update(cast(dict[str, dict[str, Any]], {"action": data})) + + async def async_setup_entry( hass: HomeAssistant, config_entry: ConfigEntry, @@ -148,11 +165,12 @@ async def async_setup_entry( if (group := hub.api.groups[group_id]) and not group.lights: return - first = True - for light_id in group.lights: - if (light := hub.api.lights.lights.get(light_id)) and light.reachable: - group.update_color_state(light, update_all_attributes=first) - first = False + lights = [ + light + for light_id in group.lights + if (light := hub.api.lights.lights.get(light_id)) and light.reachable + ] + update_color_state(group, lights, True) async_add_entities([DeconzGroup(group, hub)]) @@ -326,7 +344,7 @@ class DeconzLight(DeconzBaseLight[Light]): if self._device.reachable and "attr" not in self._device.changed_keys: for group in self.hub.api.groups.values(): if self._device.resource_id in group.lights: - group.update_color_state(self._device) + update_color_state(group, [self._device]) class DeconzGroup(DeconzBaseLight[Group]): diff --git a/tests/components/deconz/test_light.py b/tests/components/deconz/test_light.py index 5144f222484..d964361df57 100644 --- a/tests/components/deconz/test_light.py +++ b/tests/components/deconz/test_light.py @@ -1522,4 +1522,4 @@ async def test_verify_group_color_mode_fallback( ) group_state = hass.states.get("light.opbergruimte") assert group_state.state == STATE_ON - assert group_state.attributes[ATTR_COLOR_MODE] is ColorMode.UNKNOWN + assert group_state.attributes[ATTR_COLOR_MODE] is ColorMode.BRIGHTNESS From 8896d134e93cc73ffbb094e2a8c28f5dd8d38678 Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Fri, 17 May 2024 13:45:47 +0200 Subject: [PATCH 089/114] Bump version to 2024.5.4 --- 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 4bab6d0f127..278050b69e1 100644 --- a/homeassistant/const.py +++ b/homeassistant/const.py @@ -23,7 +23,7 @@ if TYPE_CHECKING: APPLICATION_NAME: Final = "HomeAssistant" MAJOR_VERSION: Final = 2024 MINOR_VERSION: Final = 5 -PATCH_VERSION: Final = "3" +PATCH_VERSION: Final = "4" __short_version__: Final = f"{MAJOR_VERSION}.{MINOR_VERSION}" __version__: Final = f"{__short_version__}.{PATCH_VERSION}" REQUIRED_PYTHON_VER: Final[tuple[int, int, int]] = (3, 12, 0) diff --git a/pyproject.toml b/pyproject.toml index 5c24c020e82..1805545235f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "homeassistant" -version = "2024.5.3" +version = "2024.5.4" license = {text = "Apache-2.0"} description = "Open-source home automation platform running on Python 3." readme = "README.rst" From 5c8f7fe52ae74e143afde48cc74a89cb29640a47 Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Fri, 17 May 2024 14:13:10 +0200 Subject: [PATCH 090/114] Fix rc pylint warning for Home Assistant Analytics (#117635) --- homeassistant/components/analytics_insights/config_flow.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/analytics_insights/config_flow.py b/homeassistant/components/analytics_insights/config_flow.py index 909290b1035..64d1580223e 100644 --- a/homeassistant/components/analytics_insights/config_flow.py +++ b/homeassistant/components/analytics_insights/config_flow.py @@ -82,7 +82,7 @@ class HomeassistantAnalyticsConfigFlow(ConfigFlow, domain=DOMAIN): except HomeassistantAnalyticsConnectionError: LOGGER.exception("Error connecting to Home Assistant analytics") return self.async_abort(reason="cannot_connect") - except Exception: # noqa: BLE001 + except Exception: # pylint: disable=broad-except LOGGER.exception("Unexpected error") return self.async_abort(reason="unknown") From 9dc66404e78e4460ebae18a0664bc5adad0b8df5 Mon Sep 17 00:00:00 2001 From: Pete Sage <76050312+PeteRager@users.noreply.github.com> Date: Fri, 24 May 2024 04:42:45 -0400 Subject: [PATCH 091/114] Fix Sonos album artwork performance (#116391) --- .../components/sonos/media_browser.py | 14 +- tests/components/sonos/conftest.py | 43 +++++- .../sonos/fixtures/music_library_albums.json | 23 +++ .../fixtures/music_library_categories.json | 44 ++++++ .../sonos/fixtures/music_library_tracks.json | 14 ++ .../sonos/snapshots/test_media_browser.ambr | 133 ++++++++++++++++++ tests/components/sonos/test_media_browser.py | 82 +++++++++++ 7 files changed, 344 insertions(+), 9 deletions(-) create mode 100644 tests/components/sonos/fixtures/music_library_albums.json create mode 100644 tests/components/sonos/fixtures/music_library_categories.json create mode 100644 tests/components/sonos/fixtures/music_library_tracks.json create mode 100644 tests/components/sonos/snapshots/test_media_browser.ambr diff --git a/homeassistant/components/sonos/media_browser.py b/homeassistant/components/sonos/media_browser.py index eeadd7db232..008c539581b 100644 --- a/homeassistant/components/sonos/media_browser.py +++ b/homeassistant/components/sonos/media_browser.py @@ -53,14 +53,16 @@ def get_thumbnail_url_full( media_content_type: str, media_content_id: str, media_image_id: str | None = None, + item: MusicServiceItem | None = None, ) -> str | None: """Get thumbnail URL.""" if is_internal: - item = get_media( - media.library, - media_content_id, - media_content_type, - ) + if not item: + item = get_media( + media.library, + media_content_id, + media_content_type, + ) return urllib.parse.unquote(getattr(item, "album_art_uri", "")) return urllib.parse.unquote( @@ -255,7 +257,7 @@ def item_payload(item: DidlObject, get_thumbnail_url=None) -> BrowseMedia: content_id = get_content_id(item) thumbnail = None if getattr(item, "album_art_uri", None): - thumbnail = get_thumbnail_url(media_class, content_id) + thumbnail = get_thumbnail_url(media_class, content_id, item=item) return BrowseMedia( title=item.title, diff --git a/tests/components/sonos/conftest.py b/tests/components/sonos/conftest.py index 15f371f272c..a7062b24e88 100644 --- a/tests/components/sonos/conftest.py +++ b/tests/components/sonos/conftest.py @@ -316,12 +316,35 @@ def sonos_favorites_fixture() -> SearchResult: class MockMusicServiceItem: """Mocks a Soco MusicServiceItem.""" - def __init__(self, title: str, item_id: str, parent_id: str, item_class: str): + def __init__( + self, + title: str, + item_id: str, + parent_id: str, + item_class: str, + album_art_uri: None | str = None, + ): """Initialize the mock item.""" self.title = title self.item_id = item_id self.item_class = item_class self.parent_id = parent_id + self.album_art_uri: None | str = album_art_uri + + +def list_from_json_fixture(file_name: str) -> list[MockMusicServiceItem]: + """Create a list of music service items from a json fixture file.""" + item_list = load_json_value_fixture(file_name, "sonos") + return [ + MockMusicServiceItem( + item.get("title"), + item.get("item_id"), + item.get("parent_id"), + item.get("item_class"), + item.get("album_art_uri"), + ) + for item in item_list + ] def mock_browse_by_idstring( @@ -398,6 +421,10 @@ def mock_browse_by_idstring( "object.container.album.musicAlbum", ), ] + if search_type == "tracks": + return list_from_json_fixture("music_library_tracks.json") + if search_type == "albums" and idstring == "A:ALBUM": + return list_from_json_fixture("music_library_albums.json") return [] @@ -416,13 +443,23 @@ def mock_get_music_library_information( ] +@pytest.fixture(name="music_library_browse_categories") +def music_library_browse_categories() -> list[MockMusicServiceItem]: + """Create fixture for top-level music library categories.""" + return list_from_json_fixture("music_library_categories.json") + + @pytest.fixture(name="music_library") -def music_library_fixture(sonos_favorites: SearchResult) -> Mock: +def music_library_fixture( + sonos_favorites: SearchResult, + music_library_browse_categories: list[MockMusicServiceItem], +) -> Mock: """Create music_library fixture.""" music_library = MagicMock() music_library.get_sonos_favorites.return_value = sonos_favorites - music_library.browse_by_idstring = mock_browse_by_idstring + music_library.browse_by_idstring = Mock(side_effect=mock_browse_by_idstring) music_library.get_music_library_information = mock_get_music_library_information + music_library.browse = Mock(return_value=music_library_browse_categories) return music_library diff --git a/tests/components/sonos/fixtures/music_library_albums.json b/tests/components/sonos/fixtures/music_library_albums.json new file mode 100644 index 00000000000..4941abe8ba7 --- /dev/null +++ b/tests/components/sonos/fixtures/music_library_albums.json @@ -0,0 +1,23 @@ +[ + { + "title": "A Hard Day's Night", + "item_id": "A:ALBUM/A%20Hard%20Day's%20Night", + "parent_id": "A:ALBUM", + "item_class": "object.container.album.musicAlbum", + "album_art_uri": "http://192.168.42.2:1400/getaa?u=x-file-cifs%3a%2f%2f192.168.42.100%2fmusic%2fThe%2520Beatles%2fA%2520Hard%2520Day's%2520Night%2f01%2520A%2520Hard%2520Day's%2520Night%25201.m4a&v=53" + }, + { + "title": "Abbey Road", + "item_id": "A:ALBUM/Abbey%20Road", + "parent_id": "A:ALBUM", + "item_class": "object.container.album.musicAlbum", + "album_art_uri": "http://192.168.42.2:1400/getaa?u=x-file-cifs%3a%2f%2f192.168.42.100%2fmusic%2fThe%2520Beatles%2fAbbeyA%2520Road%2f01%2520Come%2520Together.m4a&v=53" + }, + { + "title": "Between Good And Evil", + "item_id": "A:ALBUM/Between%20Good%20And%20Evil", + "parent_id": "A:ALBUM", + "item_class": "object.container.album.musicAlbum", + "album_art_uri": "http://192.168.42.2:1400/getaa?u=x-file-cifs%3a%2f%2f192.168.42.100%2fmusic%2fSantana%2fA%2520Between%2520Good%2520And%2520Evil%2f02%2520A%2520Persuasion.m4a&v=53" + } +] diff --git a/tests/components/sonos/fixtures/music_library_categories.json b/tests/components/sonos/fixtures/music_library_categories.json new file mode 100644 index 00000000000..b6d6d3bf2dd --- /dev/null +++ b/tests/components/sonos/fixtures/music_library_categories.json @@ -0,0 +1,44 @@ +[ + { + "title": "Contributing Artists", + "item_id": "A:ARTIST", + "parent_id": "A:", + "item_class": "object.container" + }, + { + "title": "Artists", + "item_id": "A:ALBUMARTIST", + "parent_id": "A:", + "item_class": "object.container" + }, + { + "title": "Albums", + "item_id": "A:ALBUM", + "parent_id": "A:", + "item_class": "object.container" + }, + { + "title": "Genres", + "item_id": "A:GENRE", + "parent_id": "A:", + "item_class": "object.container" + }, + { + "title": "Composers", + "item_id": "A:COMPOSER", + "parent_id": "A:", + "item_class": "object.container" + }, + { + "title": "Tracks", + "item_id": "A:TRACKS", + "parent_id": "A:", + "item_class": "object.container.playlistContainer" + }, + { + "title": "Playlists", + "item_id": "A:PLAYLISTS", + "parent_id": "A:", + "item_class": "object.container" + } +] diff --git a/tests/components/sonos/fixtures/music_library_tracks.json b/tests/components/sonos/fixtures/music_library_tracks.json new file mode 100644 index 00000000000..1f1fcdbc21c --- /dev/null +++ b/tests/components/sonos/fixtures/music_library_tracks.json @@ -0,0 +1,14 @@ +[ + { + "title": "A Hard Day's Night", + "item_id": "S://192.168.42.100/music/iTunes/The%20Beatles/A%20Hard%20Day%2fs%20Night/A%20Hard%20Day%2fs%20Night.mp3", + "parent_id": "A:ALBUMARTIST/Beatles/A%20Hard%20Day's%20Night", + "item_class": "object.container.album.musicTrack" + }, + { + "title": "I Should Have Known Better", + "item_id": "S://192.168.42.100/music/iTunes/The%20Beatles/A%20Hard%20Day%2fs%I%20Should%20Have%20Known%20Better.mp3", + "parent_id": "A:ALBUMARTIST/Beatles/A%20Hard%20Day's%20Night", + "item_class": "object.container.album.musicTrack" + } +] diff --git a/tests/components/sonos/snapshots/test_media_browser.ambr b/tests/components/sonos/snapshots/test_media_browser.ambr new file mode 100644 index 00000000000..b4388b148e5 --- /dev/null +++ b/tests/components/sonos/snapshots/test_media_browser.ambr @@ -0,0 +1,133 @@ +# serializer version: 1 +# name: test_browse_media_library + list([ + dict({ + 'can_expand': True, + 'can_play': False, + 'children_media_class': None, + 'media_class': 'contributing_artist', + 'media_content_id': 'A:ARTIST', + 'media_content_type': 'contributing_artist', + 'thumbnail': None, + 'title': 'Contributing Artists', + }), + dict({ + 'can_expand': True, + 'can_play': False, + 'children_media_class': None, + 'media_class': 'artist', + 'media_content_id': 'A:ALBUMARTIST', + 'media_content_type': 'artist', + 'thumbnail': None, + 'title': 'Artists', + }), + dict({ + 'can_expand': True, + 'can_play': False, + 'children_media_class': None, + 'media_class': 'album', + 'media_content_id': 'A:ALBUM', + 'media_content_type': 'album', + 'thumbnail': None, + 'title': 'Albums', + }), + dict({ + 'can_expand': True, + 'can_play': False, + 'children_media_class': None, + 'media_class': 'genre', + 'media_content_id': 'A:GENRE', + 'media_content_type': 'genre', + 'thumbnail': None, + 'title': 'Genres', + }), + dict({ + 'can_expand': True, + 'can_play': False, + 'children_media_class': None, + 'media_class': 'composer', + 'media_content_id': 'A:COMPOSER', + 'media_content_type': 'composer', + 'thumbnail': None, + 'title': 'Composers', + }), + dict({ + 'can_expand': True, + 'can_play': True, + 'children_media_class': None, + 'media_class': 'track', + 'media_content_id': 'A:TRACKS', + 'media_content_type': 'track', + 'thumbnail': None, + 'title': 'Tracks', + }), + dict({ + 'can_expand': True, + 'can_play': False, + 'children_media_class': None, + 'media_class': 'playlist', + 'media_content_id': 'A:PLAYLISTS', + 'media_content_type': 'playlist', + 'thumbnail': None, + 'title': 'Playlists', + }), + ]) +# --- +# name: test_browse_media_library_albums + list([ + dict({ + 'can_expand': True, + 'can_play': True, + 'children_media_class': None, + 'media_class': 'album', + 'media_content_id': "A:ALBUM/A%20Hard%20Day's%20Night", + 'media_content_type': 'album', + 'thumbnail': "http://192.168.42.2:1400/getaa?u=x-file-cifs://192.168.42.100/music/The%20Beatles/A%20Hard%20Day's%20Night/01%20A%20Hard%20Day's%20Night%201.m4a&v=53", + 'title': "A Hard Day's Night", + }), + dict({ + 'can_expand': True, + 'can_play': True, + 'children_media_class': None, + 'media_class': 'album', + 'media_content_id': 'A:ALBUM/Abbey%20Road', + 'media_content_type': 'album', + 'thumbnail': 'http://192.168.42.2:1400/getaa?u=x-file-cifs://192.168.42.100/music/The%20Beatles/AbbeyA%20Road/01%20Come%20Together.m4a&v=53', + 'title': 'Abbey Road', + }), + dict({ + 'can_expand': True, + 'can_play': True, + 'children_media_class': None, + 'media_class': 'album', + 'media_content_id': 'A:ALBUM/Between%20Good%20And%20Evil', + 'media_content_type': 'album', + 'thumbnail': 'http://192.168.42.2:1400/getaa?u=x-file-cifs://192.168.42.100/music/Santana/A%20Between%20Good%20And%20Evil/02%20A%20Persuasion.m4a&v=53', + 'title': 'Between Good And Evil', + }), + ]) +# --- +# name: test_browse_media_root + list([ + dict({ + 'can_expand': True, + 'can_play': False, + 'children_media_class': None, + 'media_class': 'directory', + 'media_content_id': '', + 'media_content_type': 'favorites', + 'thumbnail': 'https://brands.home-assistant.io/_/sonos/logo.png', + 'title': 'Favorites', + }), + dict({ + 'can_expand': True, + 'can_play': False, + 'children_media_class': None, + 'media_class': 'directory', + 'media_content_id': '', + 'media_content_type': 'library', + 'thumbnail': 'https://brands.home-assistant.io/_/sonos/logo.png', + 'title': 'Music Library', + }), + ]) +# --- diff --git a/tests/components/sonos/test_media_browser.py b/tests/components/sonos/test_media_browser.py index d8d0e1c3a07..4f6c2f53d8b 100644 --- a/tests/components/sonos/test_media_browser.py +++ b/tests/components/sonos/test_media_browser.py @@ -2,6 +2,8 @@ from functools import partial +from syrupy import SnapshotAssertion + from homeassistant.components.media_player.browse_media import BrowseMedia from homeassistant.components.media_player.const import MediaClass, MediaType from homeassistant.components.sonos.media_browser import ( @@ -12,6 +14,8 @@ from homeassistant.core import HomeAssistant from .conftest import SoCoMockFactory +from tests.typing import WebSocketGenerator + class MockMusicServiceItem: """Mocks a Soco MusicServiceItem.""" @@ -95,3 +99,81 @@ async def test_build_item_response( browse_item.children[1].media_content_id == "x-file-cifs://192.168.42.10/music/The%20Beatles/Abbey%20Road/03%20Something.mp3" ) + + +async def test_browse_media_root( + hass: HomeAssistant, + soco_factory: SoCoMockFactory, + async_autosetup_sonos, + soco, + discover, + hass_ws_client: WebSocketGenerator, + snapshot: SnapshotAssertion, +) -> None: + """Test the async_browse_media method.""" + + client = await hass_ws_client() + await client.send_json( + { + "id": 1, + "type": "media_player/browse_media", + "entity_id": "media_player.zone_a", + } + ) + response = await client.receive_json() + assert response["success"] + assert response["result"]["children"] == snapshot + + +async def test_browse_media_library( + hass: HomeAssistant, + soco_factory: SoCoMockFactory, + async_autosetup_sonos, + soco, + discover, + hass_ws_client: WebSocketGenerator, + snapshot: SnapshotAssertion, +) -> None: + """Test the async_browse_media method.""" + + client = await hass_ws_client() + await client.send_json( + { + "id": 1, + "type": "media_player/browse_media", + "entity_id": "media_player.zone_a", + "media_content_id": "", + "media_content_type": "library", + } + ) + response = await client.receive_json() + assert response["success"] + assert response["result"]["children"] == snapshot + + +async def test_browse_media_library_albums( + hass: HomeAssistant, + soco_factory: SoCoMockFactory, + async_autosetup_sonos, + soco, + discover, + hass_ws_client: WebSocketGenerator, + snapshot: SnapshotAssertion, +) -> None: + """Test the async_browse_media method.""" + soco_mock = soco_factory.mock_list.get("192.168.42.2") + + client = await hass_ws_client() + await client.send_json( + { + "id": 1, + "type": "media_player/browse_media", + "entity_id": "media_player.zone_a", + "media_content_id": "A:ALBUM", + "media_content_type": "album", + } + ) + response = await client.receive_json() + assert response["success"] + assert response["result"]["children"] == snapshot + assert soco_mock.music_library.browse_by_idstring.call_count == 1 From 85f0fffa5a9538eb135875ae4809aa2ff366873c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ville=20Skytt=C3=A4?= Date: Sat, 18 May 2024 14:45:42 +0300 Subject: [PATCH 092/114] Filter out HTML greater/less than entities from huawei_lte sensor values (#117209) --- homeassistant/components/huawei_lte/sensor.py | 2 +- tests/components/huawei_lte/test_sensor.py | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/huawei_lte/sensor.py b/homeassistant/components/huawei_lte/sensor.py index cef5bc5030e..5c5f7fc8b8e 100644 --- a/homeassistant/components/huawei_lte/sensor.py +++ b/homeassistant/components/huawei_lte/sensor.py @@ -54,7 +54,7 @@ def format_default(value: StateType) -> tuple[StateType, str | None]: if value is not None: # Clean up value and infer unit, e.g. -71dBm, 15 dB if match := re.match( - r"([>=<]*)(?P.+?)\s*(?P[a-zA-Z]+)\s*$", str(value) + r"((&[gl]t;|[><])=?)?(?P.+?)\s*(?P[a-zA-Z]+)\s*$", str(value) ): try: value = float(match.group("value")) diff --git a/tests/components/huawei_lte/test_sensor.py b/tests/components/huawei_lte/test_sensor.py index 4d5acaf2d31..75cdc7be1c2 100644 --- a/tests/components/huawei_lte/test_sensor.py +++ b/tests/components/huawei_lte/test_sensor.py @@ -15,6 +15,8 @@ from homeassistant.const import ( ("-71 dBm", (-71, SIGNAL_STRENGTH_DECIBELS_MILLIWATT)), ("15dB", (15, SIGNAL_STRENGTH_DECIBELS)), (">=-51dBm", (-51, SIGNAL_STRENGTH_DECIBELS_MILLIWATT)), + ("<-20dB", (-20, SIGNAL_STRENGTH_DECIBELS)), + (">=30dB", (30, SIGNAL_STRENGTH_DECIBELS)), ], ) def test_format_default(value, expected) -> None: From c6a9388aeac0e4ae493dad9c51d2a36377c90616 Mon Sep 17 00:00:00 2001 From: Bouwe Westerdijk <11290930+bouwew@users.noreply.github.com> Date: Sat, 18 May 2024 12:39:58 +0200 Subject: [PATCH 093/114] Add options-property to Plugwise Select (#117655) --- homeassistant/components/plugwise/select.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/plugwise/select.py b/homeassistant/components/plugwise/select.py index 10718a818ff..a3e2a567e85 100644 --- a/homeassistant/components/plugwise/select.py +++ b/homeassistant/components/plugwise/select.py @@ -91,13 +91,17 @@ class PlugwiseSelectEntity(PlugwiseEntity, SelectEntity): super().__init__(coordinator, device_id) self.entity_description = entity_description self._attr_unique_id = f"{device_id}-{entity_description.key}" - self._attr_options = self.device[entity_description.options_key] @property def current_option(self) -> str: """Return the selected entity option to represent the entity state.""" return self.device[self.entity_description.key] + @property + def options(self) -> list[str]: + """Return the available select-options.""" + return self.device[self.entity_description.options_key] + async def async_select_option(self, option: str) -> None: """Change to the selected entity option.""" await self.entity_description.command( From ecb587c4ca4451eee116929efd613382e2462c35 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 19 May 2024 14:09:21 -1000 Subject: [PATCH 094/114] Fix setting MQTT socket buffer size with WebsocketWrapper (#117672) --- homeassistant/components/mqtt/client.py | 8 ++++++ tests/components/mqtt/test_init.py | 37 +++++++++++++++++++++++++ 2 files changed, 45 insertions(+) diff --git a/homeassistant/components/mqtt/client.py b/homeassistant/components/mqtt/client.py index 8245363fd85..e6e4bb52049 100644 --- a/homeassistant/components/mqtt/client.py +++ b/homeassistant/components/mqtt/client.py @@ -542,6 +542,14 @@ class MQTT: def _increase_socket_buffer_size(self, sock: SocketType) -> None: """Increase the socket buffer size.""" + if not hasattr(sock, "setsockopt") and hasattr(sock, "_socket"): + # The WebsocketWrapper does not wrap setsockopt + # so we need to get the underlying socket + # Remove this once + # https://github.com/eclipse/paho.mqtt.python/pull/843 + # is available. + sock = sock._socket # noqa: SLF001 + new_buffer_size = PREFERRED_BUFFER_SIZE while True: try: diff --git a/tests/components/mqtt/test_init.py b/tests/components/mqtt/test_init.py index 448d41c59cc..6ead70e4150 100644 --- a/tests/components/mqtt/test_init.py +++ b/tests/components/mqtt/test_init.py @@ -4410,6 +4410,43 @@ async def test_server_sock_buffer_size( assert "Unable to increase the socket buffer size" in caplog.text +@patch("homeassistant.components.mqtt.client.INITIAL_SUBSCRIBE_COOLDOWN", 0.0) +@patch("homeassistant.components.mqtt.client.DISCOVERY_COOLDOWN", 0.0) +@patch("homeassistant.components.mqtt.client.SUBSCRIBE_COOLDOWN", 0.0) +async def test_server_sock_buffer_size_with_websocket( + hass: HomeAssistant, + mqtt_client_mock: MqttMockPahoClient, + mqtt_mock_entry: MqttMockHAClientGenerator, + caplog: pytest.LogCaptureFixture, +) -> None: + """Test handling the socket buffer size fails.""" + mqtt_mock = await mqtt_mock_entry() + await hass.async_block_till_done() + assert mqtt_mock.connected is True + + mqtt_client_mock.loop_misc.return_value = paho_mqtt.MQTT_ERR_SUCCESS + + client, server = socket.socketpair( + family=socket.AF_UNIX, type=socket.SOCK_STREAM, proto=0 + ) + client.setblocking(False) + server.setblocking(False) + + class FakeWebsocket(paho_mqtt.WebsocketWrapper): + def _do_handshake(self, *args, **kwargs): + pass + + wrapped_socket = FakeWebsocket(client, "127.0.01", 1, False, "/", None) + + with patch.object(client, "setsockopt", side_effect=OSError("foo")): + mqtt_client_mock.on_socket_open(mqtt_client_mock, None, wrapped_socket) + mqtt_client_mock.on_socket_register_write( + mqtt_client_mock, None, wrapped_socket + ) + await hass.async_block_till_done() + assert "Unable to increase the socket buffer size" in caplog.text + + @patch("homeassistant.components.mqtt.client.INITIAL_SUBSCRIBE_COOLDOWN", 0.0) @patch("homeassistant.components.mqtt.client.DISCOVERY_COOLDOWN", 0.0) @patch("homeassistant.components.mqtt.client.SUBSCRIBE_COOLDOWN", 0.0) From 66fccb72967992fa7ce3c39274b3e84b498f3e16 Mon Sep 17 00:00:00 2001 From: On Freund Date: Sat, 18 May 2024 12:37:24 +0300 Subject: [PATCH 095/114] Bump pyrisco to 0.6.2 (#117682) --- homeassistant/components/risco/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/risco/manifest.json b/homeassistant/components/risco/manifest.json index 22e73a10d6d..25520d1f96e 100644 --- a/homeassistant/components/risco/manifest.json +++ b/homeassistant/components/risco/manifest.json @@ -7,5 +7,5 @@ "iot_class": "local_push", "loggers": ["pyrisco"], "quality_scale": "platinum", - "requirements": ["pyrisco==0.6.1"] + "requirements": ["pyrisco==0.6.2"] } diff --git a/requirements_all.txt b/requirements_all.txt index 675b01a31b9..b5ffb75bc2b 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2093,7 +2093,7 @@ pyrecswitch==1.0.2 pyrepetierng==0.1.0 # homeassistant.components.risco -pyrisco==0.6.1 +pyrisco==0.6.2 # homeassistant.components.rituals_perfume_genie pyrituals==0.0.6 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index c313ef952a3..3f91c7fe76d 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1635,7 +1635,7 @@ pyqwikswitch==0.93 pyrainbird==4.0.2 # homeassistant.components.risco -pyrisco==0.6.1 +pyrisco==0.6.2 # homeassistant.components.rituals_perfume_genie pyrituals==0.0.6 From 66c52e144e8e1e07ebbd7837c48ba141df85fabf Mon Sep 17 00:00:00 2001 From: Michael <35783820+mib1185@users.noreply.github.com> Date: Sat, 18 May 2024 16:38:22 +0200 Subject: [PATCH 096/114] Consider only active config entries as media source in Synology DSM (#117691) consider only active config entries as media source --- homeassistant/components/synology_dsm/media_source.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/synology_dsm/media_source.py b/homeassistant/components/synology_dsm/media_source.py index 4699a1a5c20..4b0c19b2b55 100644 --- a/homeassistant/components/synology_dsm/media_source.py +++ b/homeassistant/components/synology_dsm/media_source.py @@ -27,7 +27,9 @@ from .models import SynologyDSMData async def async_get_media_source(hass: HomeAssistant) -> MediaSource: """Set up Synology media source.""" - entries = hass.config_entries.async_entries(DOMAIN) + entries = hass.config_entries.async_entries( + DOMAIN, include_disabled=False, include_ignore=False + ) hass.http.register_view(SynologyDsmMediaView(hass)) return SynologyPhotosMediaSource(hass, entries) From b44821b805ab9fdb1c1d5b1004dd880afa0fdb73 Mon Sep 17 00:00:00 2001 From: Anrijs Date: Sun, 19 May 2024 21:08:39 +0300 Subject: [PATCH 097/114] Bump aranet4 to 2.3.4 (#117738) bump aranet4 lib version to 2.3.4 --- homeassistant/components/aranet/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/aranet/manifest.json b/homeassistant/components/aranet/manifest.json index 152c56e80f3..f7f831df05c 100644 --- a/homeassistant/components/aranet/manifest.json +++ b/homeassistant/components/aranet/manifest.json @@ -19,5 +19,5 @@ "documentation": "https://www.home-assistant.io/integrations/aranet", "integration_type": "device", "iot_class": "local_push", - "requirements": ["aranet4==2.3.3"] + "requirements": ["aranet4==2.3.4"] } diff --git a/requirements_all.txt b/requirements_all.txt index b5ffb75bc2b..1aa74face86 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -461,7 +461,7 @@ aprslib==0.7.2 aqualogic==2.6 # homeassistant.components.aranet -aranet4==2.3.3 +aranet4==2.3.4 # homeassistant.components.arcam_fmj arcam-fmj==1.4.0 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 3f91c7fe76d..e8004593afb 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -422,7 +422,7 @@ apprise==1.7.4 aprslib==0.7.2 # homeassistant.components.aranet -aranet4==2.3.3 +aranet4==2.3.4 # homeassistant.components.arcam_fmj arcam-fmj==1.4.0 From 8d24f68f55c0492d48ab1190e30d4828608fbef4 Mon Sep 17 00:00:00 2001 From: Ricardo Steijn <61013287+RicArch97@users.noreply.github.com> Date: Mon, 20 May 2024 07:18:28 +0200 Subject: [PATCH 098/114] Bump crownstone-sse to 2.0.5, crownstone-cloud to 1.4.11 (#117748) --- homeassistant/components/crownstone/manifest.json | 4 ++-- requirements_all.txt | 4 ++-- requirements_test_all.txt | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/homeassistant/components/crownstone/manifest.json b/homeassistant/components/crownstone/manifest.json index 532fd859b4e..6168d483ab5 100644 --- a/homeassistant/components/crownstone/manifest.json +++ b/homeassistant/components/crownstone/manifest.json @@ -13,8 +13,8 @@ "crownstone_uart" ], "requirements": [ - "crownstone-cloud==1.4.9", - "crownstone-sse==2.0.4", + "crownstone-cloud==1.4.11", + "crownstone-sse==2.0.5", "crownstone-uart==2.1.0", "pyserial==3.5" ] diff --git a/requirements_all.txt b/requirements_all.txt index 1aa74face86..46d2b49461a 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -670,10 +670,10 @@ construct==2.10.68 croniter==2.0.2 # homeassistant.components.crownstone -crownstone-cloud==1.4.9 +crownstone-cloud==1.4.11 # homeassistant.components.crownstone -crownstone-sse==2.0.4 +crownstone-sse==2.0.5 # homeassistant.components.crownstone crownstone-uart==2.1.0 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index e8004593afb..f8d3da0fc2e 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -554,10 +554,10 @@ construct==2.10.68 croniter==2.0.2 # homeassistant.components.crownstone -crownstone-cloud==1.4.9 +crownstone-cloud==1.4.11 # homeassistant.components.crownstone -crownstone-sse==2.0.4 +crownstone-sse==2.0.5 # homeassistant.components.crownstone crownstone-uart==2.1.0 From 56b55a0df5217ae6b3dbde733b0749f3dd6bd364 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 19 May 2024 21:45:52 -1000 Subject: [PATCH 099/114] Block older versions of custom integration mydolphin_plus since they cause crashes (#117751) --- homeassistant/loader.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/homeassistant/loader.py b/homeassistant/loader.py index 89c3442be6a..b65d6f34f7b 100644 --- a/homeassistant/loader.py +++ b/homeassistant/loader.py @@ -96,6 +96,11 @@ BLOCKED_CUSTOM_INTEGRATIONS: dict[str, BlockedIntegration] = { "dreame_vacuum": BlockedIntegration( AwesomeVersion("1.0.4"), "crashes Home Assistant" ), + # Added in 2024.5.5 because of + # https://github.com/sh00t2kill/dolphin-robot/issues/185 + "mydolphin_plus": BlockedIntegration( + AwesomeVersion("1.0.13"), "crashes Home Assistant" + ), } DATA_COMPONENTS = "components" From dae4d316ae9db4813b6ab52e69551f09f2b75940 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 19 May 2024 21:47:47 -1000 Subject: [PATCH 100/114] Fix race in config entry setup (#117756) --- homeassistant/config_entries.py | 11 +++++ tests/test_config_entries.py | 83 +++++++++++++++++++++++++++++++++ 2 files changed, 94 insertions(+) diff --git a/homeassistant/config_entries.py b/homeassistant/config_entries.py index f982f63b948..9635d5cba48 100644 --- a/homeassistant/config_entries.py +++ b/homeassistant/config_entries.py @@ -709,6 +709,17 @@ class ConfigEntry: ) -> None: """Set up while holding the setup lock.""" async with self.setup_lock: + if self.state is ConfigEntryState.LOADED: + # If something loaded the config entry while + # we were waiting for the lock, we should not + # set it up again. + _LOGGER.debug( + "Not setting up %s (%s %s) again, already loaded", + self.title, + self.domain, + self.entry_id, + ) + return await self.async_setup(hass, integration=integration) @callback diff --git a/tests/test_config_entries.py b/tests/test_config_entries.py index 8d7efad8918..9c491987d79 100644 --- a/tests/test_config_entries.py +++ b/tests/test_config_entries.py @@ -90,6 +90,89 @@ async def manager(hass: HomeAssistant) -> config_entries.ConfigEntries: return manager +async def test_setup_race_only_setup_once(hass: HomeAssistant) -> None: + """Test ensure that config entries are only setup once.""" + attempts = 0 + slow_config_entry_setup_future = hass.loop.create_future() + fast_config_entry_setup_future = hass.loop.create_future() + slow_setup_future = hass.loop.create_future() + + async def async_setup(hass, config): + """Mock setup.""" + await slow_setup_future + return True + + async def async_setup_entry(hass, entry): + """Mock setup entry.""" + slow = entry.data["slow"] + if slow: + await slow_config_entry_setup_future + return True + nonlocal attempts + attempts += 1 + if attempts == 1: + raise ConfigEntryNotReady + await fast_config_entry_setup_future + return True + + async def async_unload_entry(hass, entry): + """Mock unload entry.""" + return True + + mock_integration( + hass, + MockModule( + "comp", + async_setup=async_setup, + async_setup_entry=async_setup_entry, + async_unload_entry=async_unload_entry, + ), + ) + mock_platform(hass, "comp.config_flow", None) + + entry = MockConfigEntry(domain="comp", data={"slow": False}) + entry.add_to_hass(hass) + + entry2 = MockConfigEntry(domain="comp", data={"slow": True}) + entry2.add_to_hass(hass) + await entry2.setup_lock.acquire() + + async def _async_reload_entry(entry: MockConfigEntry): + async with entry.setup_lock: + await entry.async_unload(hass) + await entry.async_setup(hass) + + hass.async_create_task(_async_reload_entry(entry2)) + + setup_task = hass.async_create_task(async_setup_component(hass, "comp", {})) + entry2.setup_lock.release() + + assert entry.state is config_entries.ConfigEntryState.NOT_LOADED + assert entry2.state is config_entries.ConfigEntryState.NOT_LOADED + + assert "comp" not in hass.config.components + slow_setup_future.set_result(None) + await asyncio.sleep(0) + assert "comp" in hass.config.components + + assert entry.state is config_entries.ConfigEntryState.SETUP_RETRY + assert entry2.state is config_entries.ConfigEntryState.SETUP_IN_PROGRESS + + fast_config_entry_setup_future.set_result(None) + # Make sure setup retry is started + async_fire_time_changed(hass, dt_util.utcnow() + timedelta(seconds=5)) + slow_config_entry_setup_future.set_result(None) + await hass.async_block_till_done() + + assert entry.state is config_entries.ConfigEntryState.LOADED + await hass.async_block_till_done() + + assert attempts == 2 + await hass.async_block_till_done() + assert setup_task.done() + assert entry2.state is config_entries.ConfigEntryState.LOADED + + async def test_call_setup_entry(hass: HomeAssistant) -> None: """Test we call .setup_entry.""" entry = MockConfigEntry(domain="comp") From db73074185fa3d69dd3392852bb19ddbffc5e0f8 Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Mon, 20 May 2024 13:49:52 +0200 Subject: [PATCH 101/114] Update wled to 0.18.0 (#117790) --- homeassistant/components/wled/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/wled/manifest.json b/homeassistant/components/wled/manifest.json index fd15d8ef171..a01bbcabdd6 100644 --- a/homeassistant/components/wled/manifest.json +++ b/homeassistant/components/wled/manifest.json @@ -7,6 +7,6 @@ "integration_type": "device", "iot_class": "local_push", "quality_scale": "platinum", - "requirements": ["wled==0.17.1"], + "requirements": ["wled==0.18.0"], "zeroconf": ["_wled._tcp.local."] } diff --git a/requirements_all.txt b/requirements_all.txt index 46d2b49461a..fc909232f2f 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2866,7 +2866,7 @@ wiffi==1.1.2 wirelesstagpy==0.8.1 # homeassistant.components.wled -wled==0.17.1 +wled==0.18.0 # homeassistant.components.wolflink wolf-comm==0.0.7 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index f8d3da0fc2e..fc12be7e292 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -2222,7 +2222,7 @@ whois==0.9.27 wiffi==1.1.2 # homeassistant.components.wled -wled==0.17.1 +wled==0.18.0 # homeassistant.components.wolflink wolf-comm==0.0.7 From 6956d0d65a6bc9d3140b99012cd0f59831446561 Mon Sep 17 00:00:00 2001 From: puddly <32534428+puddly@users.noreply.github.com> Date: Mon, 20 May 2024 21:35:57 -0400 Subject: [PATCH 102/114] Account for disabled ZHA discovery config entries when migrating SkyConnect integration (#117800) * Properly handle disabled ZHA discovery config entries * Update tests/components/homeassistant_sky_connect/test_util.py Co-authored-by: TheJulianJES --------- Co-authored-by: TheJulianJES --- .../homeassistant_sky_connect/util.py | 18 ++++++++++-------- .../homeassistant_sky_connect/test_util.py | 12 ++++++++++++ 2 files changed, 22 insertions(+), 8 deletions(-) diff --git a/homeassistant/components/homeassistant_sky_connect/util.py b/homeassistant/components/homeassistant_sky_connect/util.py index f242416fa9a..864d6bfd9dc 100644 --- a/homeassistant/components/homeassistant_sky_connect/util.py +++ b/homeassistant/components/homeassistant_sky_connect/util.py @@ -50,9 +50,9 @@ def get_hardware_variant(config_entry: ConfigEntry) -> HardwareVariant: return HardwareVariant.from_usb_product_name(config_entry.data["product"]) -def get_zha_device_path(config_entry: ConfigEntry) -> str: +def get_zha_device_path(config_entry: ConfigEntry) -> str | None: """Get the device path from a ZHA config entry.""" - return cast(str, config_entry.data["device"]["path"]) + return cast(str | None, config_entry.data.get("device", {}).get("path", None)) @singleton(OTBR_ADDON_MANAGER_DATA) @@ -94,13 +94,15 @@ async def guess_firmware_type(hass: HomeAssistant, device_path: str) -> Firmware for zha_config_entry in hass.config_entries.async_entries(ZHA_DOMAIN): zha_path = get_zha_device_path(zha_config_entry) - device_guesses[zha_path].append( - FirmwareGuess( - is_running=(zha_config_entry.state == ConfigEntryState.LOADED), - firmware_type=ApplicationType.EZSP, - source="zha", + + if zha_path is not None: + device_guesses[zha_path].append( + FirmwareGuess( + is_running=(zha_config_entry.state == ConfigEntryState.LOADED), + firmware_type=ApplicationType.EZSP, + source="zha", + ) ) - ) if is_hassio(hass): otbr_addon_manager = get_otbr_addon_manager(hass) diff --git a/tests/components/homeassistant_sky_connect/test_util.py b/tests/components/homeassistant_sky_connect/test_util.py index 12ba352eb16..b560acc65b7 100644 --- a/tests/components/homeassistant_sky_connect/test_util.py +++ b/tests/components/homeassistant_sky_connect/test_util.py @@ -94,6 +94,18 @@ def test_get_zha_device_path() -> None: ) +def test_get_zha_device_path_ignored_discovery() -> None: + """Test extracting the ZHA device path from an ignored ZHA discovery.""" + config_entry = MockConfigEntry( + domain="zha", + unique_id="some_unique_id", + data={}, + version=4, + ) + + assert get_zha_device_path(config_entry) is None + + async def test_guess_firmware_type_unknown(hass: HomeAssistant) -> None: """Test guessing the firmware type.""" From 0fb5aaf0f827e631c467345eb6a5d239f290d552 Mon Sep 17 00:00:00 2001 From: Bernardus Jansen Date: Tue, 21 May 2024 10:00:29 +0200 Subject: [PATCH 103/114] Tesla Wall Connector fix spelling error/typo (#117841) --- homeassistant/components/tesla_wall_connector/strings.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/tesla_wall_connector/strings.json b/homeassistant/components/tesla_wall_connector/strings.json index ed1878caecb..e8f73f22d20 100644 --- a/homeassistant/components/tesla_wall_connector/strings.json +++ b/homeassistant/components/tesla_wall_connector/strings.json @@ -37,7 +37,7 @@ "not_connected": "Vehicle not connected", "connected": "Vehicle connected", "ready": "Ready to charge", - "negociating": "Negociating connection", + "negotiating": "Negotiating connection", "error": "Error", "charging_finished": "Charging finished", "waiting_car": "Waiting for car", From 7d5f9b1adf52c89645a8ab7b906a6dd3bef65fac Mon Sep 17 00:00:00 2001 From: Peter Date: Wed, 22 May 2024 22:36:03 +0200 Subject: [PATCH 104/114] Prevent time pattern reschedule if cancelled during job execution (#117879) Co-authored-by: J. Nick Koston --- homeassistant/helpers/event.py | 2 +- tests/helpers/test_event.py | 34 ++++++++++++++++++++++++++++++++++ 2 files changed, 35 insertions(+), 1 deletion(-) diff --git a/homeassistant/helpers/event.py b/homeassistant/helpers/event.py index 5c026064c28..67b057463dd 100644 --- a/homeassistant/helpers/event.py +++ b/homeassistant/helpers/event.py @@ -1766,7 +1766,6 @@ class _TrackUTCTimeChange: # time when the timer was scheduled utc_now = time_tracker_utcnow() localized_now = dt_util.as_local(utc_now) if self.local else utc_now - hass.async_run_hass_job(self.job, localized_now, background=True) if TYPE_CHECKING: assert self._pattern_time_change_listener_job is not None self._cancel_callback = async_track_point_in_utc_time( @@ -1774,6 +1773,7 @@ class _TrackUTCTimeChange: self._pattern_time_change_listener_job, self._calculate_next(utc_now + timedelta(seconds=1)), ) + hass.async_run_hass_job(self.job, localized_now, background=True) @callback def async_cancel(self) -> None: diff --git a/tests/helpers/test_event.py b/tests/helpers/test_event.py index a6fad968eac..7fb02024170 100644 --- a/tests/helpers/test_event.py +++ b/tests/helpers/test_event.py @@ -4589,6 +4589,40 @@ async def test_async_track_point_in_time_cancel(hass: HomeAssistant) -> None: assert "US/Hawaii" in str(times[0].tzinfo) +async def test_async_track_point_in_time_cancel_in_job( + hass: HomeAssistant, freezer: FrozenDateTimeFactory +) -> None: + """Test cancel of async track point in time during job execution.""" + + now = dt_util.utcnow() + times = [] + + time_that_will_not_match_right_away = datetime( + now.year + 1, 5, 24, 11, 59, 55, tzinfo=dt_util.UTC + ) + freezer.move_to(time_that_will_not_match_right_away) + + @callback + def action(x: datetime): + nonlocal times + times.append(x) + unsub() + + unsub = async_track_utc_time_change(hass, action, minute=0, second="*") + + async_fire_time_changed( + hass, datetime(now.year + 1, 5, 24, 12, 0, 0, 999999, tzinfo=dt_util.UTC) + ) + await hass.async_block_till_done() + assert len(times) == 1 + + async_fire_time_changed( + hass, datetime(now.year + 1, 5, 24, 13, 0, 0, 999999, tzinfo=dt_util.UTC) + ) + await hass.async_block_till_done() + assert len(times) == 1 + + async def test_async_track_entity_registry_updated_event(hass: HomeAssistant) -> None: """Test tracking entity registry updates for an entity_id.""" From 7e18527dfb6bb31717e4ce17db5364fc2f0d56ad Mon Sep 17 00:00:00 2001 From: Joakim Plate Date: Wed, 22 May 2024 00:11:10 +0200 Subject: [PATCH 105/114] Update philips_js to 3.2.1 (#117881) * Update philips_js to 3.2.0 * Update to 3.2.1 --- homeassistant/components/philips_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/philips_js/manifest.json b/homeassistant/components/philips_js/manifest.json index 4751e85d378..b4ca9b931a7 100644 --- a/homeassistant/components/philips_js/manifest.json +++ b/homeassistant/components/philips_js/manifest.json @@ -6,5 +6,5 @@ "documentation": "https://www.home-assistant.io/integrations/philips_js", "iot_class": "local_polling", "loggers": ["haphilipsjs"], - "requirements": ["ha-philipsjs==3.1.1"] + "requirements": ["ha-philipsjs==3.2.1"] } diff --git a/requirements_all.txt b/requirements_all.txt index fc909232f2f..3b7b854a2b0 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1029,7 +1029,7 @@ ha-ffmpeg==3.2.0 ha-iotawattpy==0.1.2 # homeassistant.components.philips_js -ha-philipsjs==3.1.1 +ha-philipsjs==3.2.1 # homeassistant.components.habitica habitipy==0.2.0 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index fc12be7e292..307c3b39d3e 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -843,7 +843,7 @@ ha-ffmpeg==3.2.0 ha-iotawattpy==0.1.2 # homeassistant.components.philips_js -ha-philipsjs==3.1.1 +ha-philipsjs==3.2.1 # homeassistant.components.habitica habitipy==0.2.0 From ac97f25d6ce3433c0ef807ff6f66b3c9e2aa7911 Mon Sep 17 00:00:00 2001 From: On Freund Date: Wed, 22 May 2024 19:14:04 +0300 Subject: [PATCH 106/114] Bump pyrympro to 0.0.8 (#117919) --- homeassistant/components/rympro/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/rympro/manifest.json b/homeassistant/components/rympro/manifest.json index e14ac9af71f..046e778f05b 100644 --- a/homeassistant/components/rympro/manifest.json +++ b/homeassistant/components/rympro/manifest.json @@ -5,5 +5,5 @@ "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/rympro", "iot_class": "cloud_polling", - "requirements": ["pyrympro==0.0.7"] + "requirements": ["pyrympro==0.0.8"] } diff --git a/requirements_all.txt b/requirements_all.txt index 3b7b854a2b0..4ef01b9b52e 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2102,7 +2102,7 @@ pyrituals==0.0.6 pyroute2==0.7.5 # homeassistant.components.rympro -pyrympro==0.0.7 +pyrympro==0.0.8 # homeassistant.components.sabnzbd pysabnzbd==1.1.1 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 307c3b39d3e..9fd712fbab3 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1644,7 +1644,7 @@ pyrituals==0.0.6 pyroute2==0.7.5 # homeassistant.components.rympro -pyrympro==0.0.7 +pyrympro==0.0.8 # homeassistant.components.sabnzbd pysabnzbd==1.1.1 From 09779b5f6ee8b56f38961f63bb65f58f8105ec44 Mon Sep 17 00:00:00 2001 From: Shay Levy Date: Thu, 23 May 2024 06:15:15 +0300 Subject: [PATCH 107/114] Add Shelly debug logging for async_reconnect_soon (#117945) --- homeassistant/components/shelly/config_flow.py | 1 + 1 file changed, 1 insertion(+) diff --git a/homeassistant/components/shelly/config_flow.py b/homeassistant/components/shelly/config_flow.py index 46cea4e49a4..ccc86c564d5 100644 --- a/homeassistant/components/shelly/config_flow.py +++ b/homeassistant/components/shelly/config_flow.py @@ -256,6 +256,7 @@ class ShellyConfigFlow(ConfigFlow, domain=DOMAIN): if ( current_entry := await self.async_set_unique_id(mac) ) and current_entry.data.get(CONF_HOST) == host: + LOGGER.debug("async_reconnect_soon: host: %s, mac: %s", host, mac) await async_reconnect_soon(self.hass, current_entry) if host == INTERNAL_WIFI_AP_IP: # If the device is broadcasting the internal wifi ap ip From f4b653a7678a15e5d22945eca41f404786388184 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Thu, 23 May 2024 17:59:44 -1000 Subject: [PATCH 108/114] Update pySwitchbot to 0.46.0 to fix lock key retrieval (#118005) * Update pySwitchbot to 0.46.0 to fix lock key retrieval needs https://github.com/Danielhiversen/pySwitchbot/pull/236 * bump * fixes --- homeassistant/components/switchbot/config_flow.py | 15 +++++++++++---- homeassistant/components/switchbot/manifest.json | 2 +- homeassistant/components/switchbot/strings.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- tests/components/switchbot/test_config_flow.py | 11 ++++++----- 6 files changed, 21 insertions(+), 13 deletions(-) diff --git a/homeassistant/components/switchbot/config_flow.py b/homeassistant/components/switchbot/config_flow.py index 06b95c6f8aa..bb69da52239 100644 --- a/homeassistant/components/switchbot/config_flow.py +++ b/homeassistant/components/switchbot/config_flow.py @@ -8,6 +8,7 @@ from typing import Any from switchbot import ( SwitchbotAccountConnectionError, SwitchBotAdvertisement, + SwitchbotApiError, SwitchbotAuthenticationError, SwitchbotLock, SwitchbotModel, @@ -33,6 +34,7 @@ from homeassistant.const import ( ) from homeassistant.core import callback from homeassistant.data_entry_flow import AbortFlow +from homeassistant.helpers.aiohttp_client import async_get_clientsession from .const import ( CONF_ENCRYPTION_KEY, @@ -175,14 +177,19 @@ class SwitchbotConfigFlow(ConfigFlow, domain=DOMAIN): description_placeholders = {} if user_input is not None: try: - key_details = await self.hass.async_add_executor_job( - SwitchbotLock.retrieve_encryption_key, + key_details = await SwitchbotLock.async_retrieve_encryption_key( + async_get_clientsession(self.hass), self._discovered_adv.address, user_input[CONF_USERNAME], user_input[CONF_PASSWORD], ) - except SwitchbotAccountConnectionError as ex: - raise AbortFlow("cannot_connect") from ex + except (SwitchbotApiError, SwitchbotAccountConnectionError) as ex: + _LOGGER.debug( + "Failed to connect to SwitchBot API: %s", ex, exc_info=True + ) + raise AbortFlow( + "api_error", description_placeholders={"error_detail": str(ex)} + ) from ex except SwitchbotAuthenticationError as ex: _LOGGER.debug("Authentication failed: %s", ex, exc_info=True) errors = {"base": "auth_failed"} diff --git a/homeassistant/components/switchbot/manifest.json b/homeassistant/components/switchbot/manifest.json index 401d85e7376..ba4782c8b63 100644 --- a/homeassistant/components/switchbot/manifest.json +++ b/homeassistant/components/switchbot/manifest.json @@ -39,5 +39,5 @@ "documentation": "https://www.home-assistant.io/integrations/switchbot", "iot_class": "local_push", "loggers": ["switchbot"], - "requirements": ["PySwitchbot==0.45.0"] + "requirements": ["PySwitchbot==0.46.0"] } diff --git a/homeassistant/components/switchbot/strings.json b/homeassistant/components/switchbot/strings.json index 8eab1ec6f1a..a20b4939f8f 100644 --- a/homeassistant/components/switchbot/strings.json +++ b/homeassistant/components/switchbot/strings.json @@ -46,7 +46,7 @@ "already_configured_device": "[%key:common::config_flow::abort::already_configured_device%]", "no_devices_found": "No supported SwitchBot devices found in range; If the device is in range, ensure the scanner has active scanning enabled, as SwitchBot devices cannot be discovered with passive scans. Active scans can be disabled once the device is configured. If you need clarification on whether the device is in-range, download the diagnostics for the integration that provides your Bluetooth adapter or proxy and check if the MAC address of the SwitchBot device is present.", "unknown": "[%key:common::config_flow::error::unknown%]", - "cannot_connect": "[%key:common::config_flow::error::cannot_connect%]", + "api_error": "Error while communicating with SwitchBot API: {error_detail}", "switchbot_unsupported_type": "Unsupported Switchbot Type." } }, diff --git a/requirements_all.txt b/requirements_all.txt index 4ef01b9b52e..34cce0f7dc1 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -93,7 +93,7 @@ PyQRCode==1.2.1 PyRMVtransport==0.3.3 # homeassistant.components.switchbot -PySwitchbot==0.45.0 +PySwitchbot==0.46.0 # homeassistant.components.switchmate PySwitchmate==0.5.1 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 9fd712fbab3..26e704781e7 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -81,7 +81,7 @@ PyQRCode==1.2.1 PyRMVtransport==0.3.3 # homeassistant.components.switchbot -PySwitchbot==0.45.0 +PySwitchbot==0.46.0 # homeassistant.components.syncthru PySyncThru==0.7.10 diff --git a/tests/components/switchbot/test_config_flow.py b/tests/components/switchbot/test_config_flow.py index a62a100f55a..182e9457f22 100644 --- a/tests/components/switchbot/test_config_flow.py +++ b/tests/components/switchbot/test_config_flow.py @@ -487,7 +487,7 @@ async def test_user_setup_wolock_auth(hass: HomeAssistant) -> None: assert result["errors"] == {} with patch( - "homeassistant.components.switchbot.config_flow.SwitchbotLock.retrieve_encryption_key", + "homeassistant.components.switchbot.config_flow.SwitchbotLock.async_retrieve_encryption_key", side_effect=SwitchbotAuthenticationError("error from api"), ): result = await hass.config_entries.flow.async_configure( @@ -510,7 +510,7 @@ async def test_user_setup_wolock_auth(hass: HomeAssistant) -> None: return_value=True, ), patch( - "homeassistant.components.switchbot.config_flow.SwitchbotLock.retrieve_encryption_key", + "homeassistant.components.switchbot.config_flow.SwitchbotLock.async_retrieve_encryption_key", return_value={ CONF_KEY_ID: "ff", CONF_ENCRYPTION_KEY: "ffffffffffffffffffffffffffffffff", @@ -560,8 +560,8 @@ async def test_user_setup_wolock_auth_switchbot_api_down(hass: HomeAssistant) -> assert result["errors"] == {} with patch( - "homeassistant.components.switchbot.config_flow.SwitchbotLock.retrieve_encryption_key", - side_effect=SwitchbotAccountConnectionError, + "homeassistant.components.switchbot.config_flow.SwitchbotLock.async_retrieve_encryption_key", + side_effect=SwitchbotAccountConnectionError("Switchbot API down"), ): result = await hass.config_entries.flow.async_configure( result["flow_id"], @@ -572,7 +572,8 @@ async def test_user_setup_wolock_auth_switchbot_api_down(hass: HomeAssistant) -> ) await hass.async_block_till_done() assert result["type"] is FlowResultType.ABORT - assert result["reason"] == "cannot_connect" + assert result["reason"] == "api_error" + assert result["description_placeholders"] == {"error_detail": "Switchbot API down"} async def test_user_setup_wolock_or_bot(hass: HomeAssistant) -> None: From 3238bc83b8711ce1a72915d179ef658dc20a31c0 Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Fri, 24 May 2024 09:55:05 +0200 Subject: [PATCH 109/114] Improve async_get_issue_tracker for custom integrations (#118016) --- homeassistant/bootstrap.py | 3 +++ homeassistant/loader.py | 8 ++++++++ tests/test_loader.py | 35 +++++++++++++++++++++++++++++++++-- 3 files changed, 44 insertions(+), 2 deletions(-) diff --git a/homeassistant/bootstrap.py b/homeassistant/bootstrap.py index fc5eedffc39..f733c6f9ff1 100644 --- a/homeassistant/bootstrap.py +++ b/homeassistant/bootstrap.py @@ -414,6 +414,9 @@ async def async_from_config_dict( start = monotonic() hass.config_entries = config_entries.ConfigEntries(hass, config) + # Prime custom component cache early so we know if registry entries are tied + # to a custom integration + await loader.async_get_custom_components(hass) await async_load_base_functionality(hass) # Set up core. diff --git a/homeassistant/loader.py b/homeassistant/loader.py index b65d6f34f7b..d8b32b053db 100644 --- a/homeassistant/loader.py +++ b/homeassistant/loader.py @@ -1674,6 +1674,14 @@ def async_get_issue_tracker( # If we know nothing about the entity, suggest opening an issue on HA core return issue_tracker + if ( + not integration + and (hass and integration_domain) + and (comps_or_future := hass.data.get(DATA_CUSTOM_COMPONENTS)) + and not isinstance(comps_or_future, asyncio.Future) + ): + integration = comps_or_future.get(integration_domain) + if not integration and (hass and integration_domain): with suppress(IntegrationNotLoaded): integration = async_get_loaded_integration(hass, integration_domain) diff --git a/tests/test_loader.py b/tests/test_loader.py index 404858200bc..09afdf1504b 100644 --- a/tests/test_loader.py +++ b/tests/test_loader.py @@ -2,6 +2,7 @@ import asyncio import os +import pathlib import sys import threading from typing import Any @@ -1108,14 +1109,18 @@ CUSTOM_ISSUE_TRACKER = "https://blablabla.com" # Integration domain is not currently deduced from module (None, "homeassistant.components.hue.sensor", CORE_ISSUE_TRACKER), ("hue", "homeassistant.components.mqtt.sensor", CORE_ISSUE_TRACKER_HUE), - # Custom integration with known issue tracker + # Loaded custom integration with known issue tracker ("bla_custom", "custom_components.bla_custom.sensor", CUSTOM_ISSUE_TRACKER), ("bla_custom", None, CUSTOM_ISSUE_TRACKER), - # Custom integration without known issue tracker + # Loaded custom integration without known issue tracker (None, "custom_components.bla.sensor", None), ("bla_custom_no_tracker", "custom_components.bla_custom.sensor", None), ("bla_custom_no_tracker", None, None), ("hue", "custom_components.bla.sensor", None), + # Unloaded custom integration with known issue tracker + ("bla_custom_not_loaded", None, CUSTOM_ISSUE_TRACKER), + # Unloaded custom integration without known issue tracker + ("bla_custom_not_loaded_no_tracker", None, None), # Integration domain has priority over module ("bla_custom_no_tracker", "homeassistant.components.bla_custom.sensor", None), ], @@ -1133,6 +1138,32 @@ async def test_async_get_issue_tracker( built_in=False, ) mock_integration(hass, MockModule("bla_custom_no_tracker"), built_in=False) + + cust_unloaded_module = MockModule( + "bla_custom_not_loaded", + partial_manifest={"issue_tracker": CUSTOM_ISSUE_TRACKER}, + ) + cust_unloaded = loader.Integration( + hass, + f"{loader.PACKAGE_CUSTOM_COMPONENTS}.{cust_unloaded_module.DOMAIN}", + pathlib.Path(""), + cust_unloaded_module.mock_manifest(), + set(), + ) + + cust_unloaded_no_tracker_module = MockModule("bla_custom_not_loaded_no_tracker") + cust_unloaded_no_tracker = loader.Integration( + hass, + f"{loader.PACKAGE_CUSTOM_COMPONENTS}.{cust_unloaded_no_tracker_module.DOMAIN}", + pathlib.Path(""), + cust_unloaded_no_tracker_module.mock_manifest(), + set(), + ) + hass.data["custom_components"] = { + "bla_custom_not_loaded": cust_unloaded, + "bla_custom_not_loaded_no_tracker": cust_unloaded_no_tracker, + } + assert ( loader.async_get_issue_tracker(hass, integration_domain=domain, module=module) == issue_tracker From f5c20b3528c0c5c1cfca2260711680d3c319892e Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Thu, 23 May 2024 22:37:10 -1000 Subject: [PATCH 110/114] Bump pySwitchbot to 0.46.1 (#118025) --- homeassistant/components/switchbot/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/switchbot/manifest.json b/homeassistant/components/switchbot/manifest.json index ba4782c8b63..2388e5a98b3 100644 --- a/homeassistant/components/switchbot/manifest.json +++ b/homeassistant/components/switchbot/manifest.json @@ -39,5 +39,5 @@ "documentation": "https://www.home-assistant.io/integrations/switchbot", "iot_class": "local_push", "loggers": ["switchbot"], - "requirements": ["PySwitchbot==0.46.0"] + "requirements": ["PySwitchbot==0.46.1"] } diff --git a/requirements_all.txt b/requirements_all.txt index 34cce0f7dc1..bd747808819 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -93,7 +93,7 @@ PyQRCode==1.2.1 PyRMVtransport==0.3.3 # homeassistant.components.switchbot -PySwitchbot==0.46.0 +PySwitchbot==0.46.1 # homeassistant.components.switchmate PySwitchmate==0.5.1 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 26e704781e7..716abc3edd2 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -81,7 +81,7 @@ PyQRCode==1.2.1 PyRMVtransport==0.3.3 # homeassistant.components.switchbot -PySwitchbot==0.46.0 +PySwitchbot==0.46.1 # homeassistant.components.syncthru PySyncThru==0.7.10 From 81bf31bbb16ad570604aafaecadda6d36c9f55b0 Mon Sep 17 00:00:00 2001 From: Marcel van der Veldt Date: Fri, 24 May 2024 13:50:10 +0200 Subject: [PATCH 111/114] Extend the blocklist for Matter transitions with more models (#118038) --- homeassistant/components/matter/light.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/homeassistant/components/matter/light.py b/homeassistant/components/matter/light.py index da72798dda1..acd85884875 100644 --- a/homeassistant/components/matter/light.py +++ b/homeassistant/components/matter/light.py @@ -52,7 +52,10 @@ DEFAULT_TRANSITION = 0.2 # sw version (attributeKey 0/40/10) TRANSITION_BLOCKLIST = ( (4488, 514, "1.0", "1.0.0"), + (4488, 260, "1.0", "1.0.0"), (5010, 769, "3.0", "1.0.0"), + (4999, 25057, "1.0", "27.0"), + (4448, 36866, "V1", "V1.0.0.5"), ) From 3f7e57dde224587b6559f67fbc011885ec1578b5 Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Fri, 24 May 2024 16:13:44 +0200 Subject: [PATCH 112/114] Bump version to 2024.5.5 --- 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 278050b69e1..e0832f7cc85 100644 --- a/homeassistant/const.py +++ b/homeassistant/const.py @@ -23,7 +23,7 @@ if TYPE_CHECKING: APPLICATION_NAME: Final = "HomeAssistant" MAJOR_VERSION: Final = 2024 MINOR_VERSION: Final = 5 -PATCH_VERSION: Final = "4" +PATCH_VERSION: Final = "5" __short_version__: Final = f"{MAJOR_VERSION}.{MINOR_VERSION}" __version__: Final = f"{__short_version__}.{PATCH_VERSION}" REQUIRED_PYTHON_VER: Final[tuple[int, int, int]] = (3, 12, 0) diff --git a/pyproject.toml b/pyproject.toml index 1805545235f..b84159eb457 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "homeassistant" -version = "2024.5.4" +version = "2024.5.5" license = {text = "Apache-2.0"} description = "Open-source home automation platform running on Python 3." readme = "README.rst" From 8128449879c557e72161584c2e383608e48219a2 Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Fri, 24 May 2024 18:41:46 +0200 Subject: [PATCH 113/114] Fix rc pylint warning in MQTT (#118050) --- homeassistant/components/mqtt/client.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/mqtt/client.py b/homeassistant/components/mqtt/client.py index e6e4bb52049..0261512fe99 100644 --- a/homeassistant/components/mqtt/client.py +++ b/homeassistant/components/mqtt/client.py @@ -548,7 +548,7 @@ class MQTT: # Remove this once # https://github.com/eclipse/paho.mqtt.python/pull/843 # is available. - sock = sock._socket # noqa: SLF001 + sock = sock._socket # pylint: disable=protected-access new_buffer_size = PREFERRED_BUFFER_SIZE while True: From 750ec261be3891d5fde345455317b7855e2cb5ec Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Fri, 10 May 2024 17:09:28 -0500 Subject: [PATCH 114/114] Add state check to config entry setup to ensure it cannot be setup twice (#117193) --- homeassistant/config_entries.py | 9 +++++ tests/components/upnp/test_config_flow.py | 8 ++-- tests/test_config_entries.py | 46 +++++++++++++++++++++-- 3 files changed, 56 insertions(+), 7 deletions(-) diff --git a/homeassistant/config_entries.py b/homeassistant/config_entries.py index 9635d5cba48..252f7be8b7e 100644 --- a/homeassistant/config_entries.py +++ b/homeassistant/config_entries.py @@ -514,6 +514,15 @@ class ConfigEntry: # Only store setup result as state if it was not forwarded. if domain_is_integration := self.domain == integration.domain: + if self.state in ( + ConfigEntryState.LOADED, + ConfigEntryState.SETUP_IN_PROGRESS, + ): + raise OperationNotAllowed( + f"The config entry {self.title} ({self.domain}) with entry_id" + f" {self.entry_id} cannot be setup because is already loaded in the" + f" {self.state} state" + ) self._async_set_state(hass, ConfigEntryState.SETUP_IN_PROGRESS, None) if self.supports_unload is None: diff --git a/tests/components/upnp/test_config_flow.py b/tests/components/upnp/test_config_flow.py index a3d2b97f3ed..a4598346a51 100644 --- a/tests/components/upnp/test_config_flow.py +++ b/tests/components/upnp/test_config_flow.py @@ -196,7 +196,7 @@ async def test_flow_ssdp_discovery_changed_udn_match_mac(hass: HomeAssistant) -> CONFIG_ENTRY_MAC_ADDRESS: TEST_MAC_ADDRESS, }, source=config_entries.SOURCE_SSDP, - state=config_entries.ConfigEntryState.LOADED, + state=config_entries.ConfigEntryState.NOT_LOADED, ) entry.add_to_hass(hass) @@ -228,7 +228,7 @@ async def test_flow_ssdp_discovery_changed_udn_match_host(hass: HomeAssistant) - CONFIG_ENTRY_HOST: TEST_HOST, }, source=config_entries.SOURCE_SSDP, - state=config_entries.ConfigEntryState.LOADED, + state=config_entries.ConfigEntryState.NOT_LOADED, ) entry.add_to_hass(hass) @@ -266,7 +266,7 @@ async def test_flow_ssdp_discovery_changed_udn_but_st_differs( CONFIG_ENTRY_MAC_ADDRESS: TEST_MAC_ADDRESS, }, source=config_entries.SOURCE_SSDP, - state=config_entries.ConfigEntryState.LOADED, + state=config_entries.ConfigEntryState.NOT_LOADED, ) entry.add_to_hass(hass) @@ -320,7 +320,7 @@ async def test_flow_ssdp_discovery_changed_location(hass: HomeAssistant) -> None CONFIG_ENTRY_MAC_ADDRESS: TEST_MAC_ADDRESS, }, source=config_entries.SOURCE_SSDP, - state=config_entries.ConfigEntryState.LOADED, + state=config_entries.ConfigEntryState.NOT_LOADED, ) entry.add_to_hass(hass) diff --git a/tests/test_config_entries.py b/tests/test_config_entries.py index 9c491987d79..1394ca1e435 100644 --- a/tests/test_config_entries.py +++ b/tests/test_config_entries.py @@ -469,7 +469,7 @@ async def test_remove_entry( ] # Setup entry - await entry.async_setup(hass) + await manager.async_setup(entry.entry_id) await hass.async_block_till_done() # Check entity state got added @@ -1696,7 +1696,9 @@ async def test_entry_reload_succeed( hass: HomeAssistant, manager: config_entries.ConfigEntries ) -> None: """Test that we can reload an entry.""" - entry = MockConfigEntry(domain="comp", state=config_entries.ConfigEntryState.LOADED) + entry = MockConfigEntry( + domain="comp", state=config_entries.ConfigEntryState.NOT_LOADED + ) entry.add_to_hass(hass) async_setup = AsyncMock(return_value=True) @@ -1720,6 +1722,42 @@ async def test_entry_reload_succeed( assert entry.state is config_entries.ConfigEntryState.LOADED +@pytest.mark.parametrize( + "state", + [ + config_entries.ConfigEntryState.LOADED, + config_entries.ConfigEntryState.SETUP_IN_PROGRESS, + ], +) +async def test_entry_cannot_be_loaded_twice( + hass: HomeAssistant, state: config_entries.ConfigEntryState +) -> None: + """Test that a config entry cannot be loaded twice.""" + entry = MockConfigEntry(domain="comp", state=state) + entry.add_to_hass(hass) + + async_setup = AsyncMock(return_value=True) + async_setup_entry = AsyncMock(return_value=True) + async_unload_entry = AsyncMock(return_value=True) + + mock_integration( + hass, + MockModule( + "comp", + async_setup=async_setup, + async_setup_entry=async_setup_entry, + async_unload_entry=async_unload_entry, + ), + ) + mock_platform(hass, "comp.config_flow", None) + + with pytest.raises(config_entries.OperationNotAllowed, match=str(state)): + await entry.async_setup(hass) + assert len(async_setup.mock_calls) == 0 + assert len(async_setup_entry.mock_calls) == 0 + assert entry.state is state + + @pytest.mark.parametrize( "state", [ @@ -4088,7 +4126,9 @@ async def test_entry_reload_concurrency_not_setup_setup( hass: HomeAssistant, manager: config_entries.ConfigEntries ) -> None: """Test multiple reload calls do not cause a reload race.""" - entry = MockConfigEntry(domain="comp", state=config_entries.ConfigEntryState.LOADED) + entry = MockConfigEntry( + domain="comp", state=config_entries.ConfigEntryState.NOT_LOADED + ) entry.add_to_hass(hass) async_setup = AsyncMock(return_value=True)