diff --git a/.coveragerc b/.coveragerc index f15d36918ecf..9e5044dff640 100644 --- a/.coveragerc +++ b/.coveragerc @@ -404,6 +404,9 @@ omit = homeassistant/components/fjaraskupan/sensor.py homeassistant/components/fleetgo/device_tracker.py homeassistant/components/flexit/climate.py + homeassistant/components/flexit_bacnet/__init__.py + homeassistant/components/flexit_bacnet/const.py + homeassistant/components/flexit_bacnet/climate.py homeassistant/components/flic/binary_sensor.py homeassistant/components/flick_electric/__init__.py homeassistant/components/flick_electric/sensor.py @@ -633,8 +636,6 @@ omit = homeassistant/components/kodi/browse_media.py homeassistant/components/kodi/media_player.py homeassistant/components/kodi/notify.py - homeassistant/components/komfovent/__init__.py - homeassistant/components/komfovent/climate.py homeassistant/components/konnected/__init__.py homeassistant/components/konnected/panel.py homeassistant/components/konnected/switch.py diff --git a/.strict-typing b/.strict-typing index a4969bcc8104..daa4a56deadf 100644 --- a/.strict-typing +++ b/.strict-typing @@ -128,6 +128,7 @@ homeassistant.components.file_upload.* homeassistant.components.filesize.* homeassistant.components.filter.* homeassistant.components.fitbit.* +homeassistant.components.flexit_bacnet.* homeassistant.components.flux_led.* homeassistant.components.forecast_solar.* homeassistant.components.fritz.* diff --git a/CODEOWNERS b/CODEOWNERS index ec32f941d56f..33587dec7217 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -395,6 +395,8 @@ build.json @home-assistant/supervisor /tests/components/fivem/ @Sander0542 /homeassistant/components/fjaraskupan/ @elupus /tests/components/fjaraskupan/ @elupus +/homeassistant/components/flexit_bacnet/ @lellky @piotrbulinski +/tests/components/flexit_bacnet/ @lellky @piotrbulinski /homeassistant/components/flick_electric/ @ZephireNZ /tests/components/flick_electric/ @ZephireNZ /homeassistant/components/flipr/ @cnico @@ -663,8 +665,6 @@ build.json @home-assistant/supervisor /tests/components/knx/ @Julius2342 @farmio @marvin-w /homeassistant/components/kodi/ @OnFreund /tests/components/kodi/ @OnFreund -/homeassistant/components/komfovent/ @ProstoSanja -/tests/components/komfovent/ @ProstoSanja /homeassistant/components/konnected/ @heythisisnate /tests/components/konnected/ @heythisisnate /homeassistant/components/kostal_plenticore/ @stegm @@ -1398,8 +1398,8 @@ build.json @home-assistant/supervisor /homeassistant/components/versasense/ @imstevenxyz /homeassistant/components/version/ @ludeeus /tests/components/version/ @ludeeus -/homeassistant/components/vesync/ @markperdue @webdjoe @thegardenmonkey -/tests/components/vesync/ @markperdue @webdjoe @thegardenmonkey +/homeassistant/components/vesync/ @markperdue @webdjoe @thegardenmonkey @cdnninja +/tests/components/vesync/ @markperdue @webdjoe @thegardenmonkey @cdnninja /homeassistant/components/vicare/ @CFenner /tests/components/vicare/ @CFenner /homeassistant/components/vilfo/ @ManneW diff --git a/homeassistant/brands/flexit.json b/homeassistant/brands/flexit.json new file mode 100644 index 000000000000..4c61c5eeb07a --- /dev/null +++ b/homeassistant/brands/flexit.json @@ -0,0 +1,5 @@ +{ + "domain": "flexit", + "name": "Flexit", + "integrations": ["flexit", "flexit_bacnet"] +} diff --git a/homeassistant/components/apcupsd/coordinator.py b/homeassistant/components/apcupsd/coordinator.py index 321da56095ac..ae4c94a93824 100644 --- a/homeassistant/components/apcupsd/coordinator.py +++ b/homeassistant/components/apcupsd/coordinator.py @@ -9,6 +9,7 @@ from typing import Final from apcaccess import status +from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant from homeassistant.helpers.debounce import Debouncer from homeassistant.helpers.device_registry import DeviceInfo @@ -32,6 +33,8 @@ class APCUPSdCoordinator(DataUpdateCoordinator[OrderedDict[str, str]]): updates from the server. """ + config_entry: ConfigEntry + def __init__(self, hass: HomeAssistant, host: str, port: int) -> None: """Initialize the data object.""" super().__init__( @@ -70,13 +73,10 @@ class APCUPSdCoordinator(DataUpdateCoordinator[OrderedDict[str, str]]): return self.data.get("SERIALNO") @property - def device_info(self) -> DeviceInfo | None: + def device_info(self) -> DeviceInfo: """Return the DeviceInfo of this APC UPS, if serial number is available.""" - if not self.ups_serial_no: - return None - return DeviceInfo( - identifiers={(DOMAIN, self.ups_serial_no)}, + identifiers={(DOMAIN, self.ups_serial_no or self.config_entry.entry_id)}, model=self.ups_model, manufacturer="APC", name=self.ups_name if self.ups_name else "APC UPS", diff --git a/homeassistant/components/blink/__init__.py b/homeassistant/components/blink/__init__.py index 42ad5cabeb74..d83c2686563b 100644 --- a/homeassistant/components/blink/__init__.py +++ b/homeassistant/components/blink/__init__.py @@ -25,7 +25,7 @@ from homeassistant.helpers.typing import ConfigType from .const import DEFAULT_SCAN_INTERVAL, DOMAIN, PLATFORMS from .coordinator import BlinkUpdateCoordinator -from .services import async_setup_services +from .services import setup_services _LOGGER = logging.getLogger(__name__) @@ -74,7 +74,7 @@ async def async_migrate_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: """Set up Blink.""" - await async_setup_services(hass) + setup_services(hass) return True diff --git a/homeassistant/components/blink/services.py b/homeassistant/components/blink/services.py index 8ea0b6c03a40..12ac0d3b859d 100644 --- a/homeassistant/components/blink/services.py +++ b/homeassistant/components/blink/services.py @@ -1,8 +1,6 @@ """Services for the Blink integration.""" from __future__ import annotations -import logging - import voluptuous as vol from homeassistant.config_entries import ConfigEntry, ConfigEntryState @@ -14,7 +12,7 @@ from homeassistant.const import ( CONF_PIN, ) from homeassistant.core import HomeAssistant, ServiceCall -from homeassistant.exceptions import HomeAssistantError +from homeassistant.exceptions import HomeAssistantError, ServiceValidationError import homeassistant.helpers.config_validation as cv import homeassistant.helpers.device_registry as dr @@ -27,56 +25,67 @@ from .const import ( ) from .coordinator import BlinkUpdateCoordinator -_LOGGER = logging.getLogger(__name__) - SERVICE_SAVE_VIDEO_SCHEMA = vol.Schema( { - vol.Required(ATTR_DEVICE_ID): cv.ensure_list, + vol.Required(ATTR_DEVICE_ID): vol.All(cv.ensure_list, [cv.string]), vol.Required(CONF_NAME): cv.string, vol.Required(CONF_FILENAME): cv.string, } ) SERVICE_SEND_PIN_SCHEMA = vol.Schema( - {vol.Required(ATTR_DEVICE_ID): cv.ensure_list, vol.Optional(CONF_PIN): cv.string} + { + vol.Required(ATTR_DEVICE_ID): vol.All(cv.ensure_list, [cv.string]), + vol.Optional(CONF_PIN): cv.string, + } ) SERVICE_SAVE_RECENT_CLIPS_SCHEMA = vol.Schema( { - vol.Required(ATTR_DEVICE_ID): cv.ensure_list, + vol.Required(ATTR_DEVICE_ID): vol.All(cv.ensure_list, [cv.string]), vol.Required(CONF_NAME): cv.string, vol.Required(CONF_FILE_PATH): cv.string, } ) -async def async_setup_services(hass: HomeAssistant) -> None: +def setup_services(hass: HomeAssistant) -> None: """Set up the services for the Blink integration.""" - async def collect_coordinators( + def collect_coordinators( device_ids: list[str], ) -> list[BlinkUpdateCoordinator]: - config_entries = list[ConfigEntry]() + config_entries: list[ConfigEntry] = [] registry = dr.async_get(hass) for target in device_ids: device = registry.async_get(target) if device: - device_entries = list[ConfigEntry]() + device_entries: list[ConfigEntry] = [] for entry_id in device.config_entries: entry = hass.config_entries.async_get_entry(entry_id) if entry and entry.domain == DOMAIN: device_entries.append(entry) if not device_entries: - raise HomeAssistantError( - f"Device '{target}' is not a {DOMAIN} device" + raise ServiceValidationError( + translation_domain=DOMAIN, + translation_key="invalid_device", + translation_placeholders={"target": target, "domain": DOMAIN}, ) config_entries.extend(device_entries) else: raise HomeAssistantError( - f"Device '{target}' not found in device registry" + translation_domain=DOMAIN, + translation_key="device_not_found", + translation_placeholders={"target": target}, ) - coordinators = list[BlinkUpdateCoordinator]() + + coordinators: list[BlinkUpdateCoordinator] = [] for config_entry in config_entries: if config_entry.state != ConfigEntryState.LOADED: - raise HomeAssistantError(f"{config_entry.title} is not loaded") + raise HomeAssistantError( + translation_domain=DOMAIN, + translation_key="not_loaded", + translation_placeholders={"target": config_entry.title}, + ) + coordinators.append(hass.data[DOMAIN][config_entry.entry_id]) return coordinators @@ -85,24 +94,36 @@ async def async_setup_services(hass: HomeAssistant) -> None: camera_name = call.data[CONF_NAME] video_path = call.data[CONF_FILENAME] if not hass.config.is_allowed_path(video_path): - _LOGGER.error("Can't write %s, no access to path!", video_path) - return - for coordinator in await collect_coordinators(call.data[ATTR_DEVICE_ID]): + raise ServiceValidationError( + translation_domain=DOMAIN, + translation_key="no_path", + translation_placeholders={"target": video_path}, + ) + + for coordinator in collect_coordinators(call.data[ATTR_DEVICE_ID]): all_cameras = coordinator.api.cameras if camera_name in all_cameras: try: await all_cameras[camera_name].video_to_file(video_path) except OSError as err: - _LOGGER.error("Can't write image to file: %s", err) + raise ServiceValidationError( + str(err), + translation_domain=DOMAIN, + translation_key="cant_write", + ) from err async def async_handle_save_recent_clips_service(call: ServiceCall) -> None: """Save multiple recent clips to output directory.""" camera_name = call.data[CONF_NAME] clips_dir = call.data[CONF_FILE_PATH] if not hass.config.is_allowed_path(clips_dir): - _LOGGER.error("Can't write to directory %s, no access to path!", clips_dir) - return - for coordinator in await collect_coordinators(call.data[ATTR_DEVICE_ID]): + raise ServiceValidationError( + translation_domain=DOMAIN, + translation_key="no_path", + translation_placeholders={"target": clips_dir}, + ) + + for coordinator in collect_coordinators(call.data[ATTR_DEVICE_ID]): all_cameras = coordinator.api.cameras if camera_name in all_cameras: try: @@ -110,11 +131,15 @@ async def async_setup_services(hass: HomeAssistant) -> None: output_dir=clips_dir ) except OSError as err: - _LOGGER.error("Can't write recent clips to directory: %s", err) + raise ServiceValidationError( + str(err), + translation_domain=DOMAIN, + translation_key="cant_write", + ) from err async def send_pin(call: ServiceCall): """Call blink to send new pin.""" - for coordinator in await collect_coordinators(call.data[ATTR_DEVICE_ID]): + for coordinator in collect_coordinators(call.data[ATTR_DEVICE_ID]): await coordinator.api.auth.send_auth_key( coordinator.api, call.data[CONF_PIN], @@ -122,7 +147,7 @@ async def async_setup_services(hass: HomeAssistant) -> None: async def blink_refresh(call: ServiceCall): """Call blink to refresh info.""" - for coordinator in await collect_coordinators(call.data[ATTR_DEVICE_ID]): + for coordinator in collect_coordinators(call.data[ATTR_DEVICE_ID]): await coordinator.api.refresh(force_cache=True) # Register all the above services diff --git a/homeassistant/components/blink/strings.json b/homeassistant/components/blink/strings.json index c29c4c765b7b..f47f72acb9cf 100644 --- a/homeassistant/components/blink/strings.json +++ b/homeassistant/components/blink/strings.json @@ -101,5 +101,22 @@ } } } + }, + "exceptions": { + "invalid_device": { + "message": "Device '{target}' is not a {domain} device" + }, + "device_not_found": { + "message": "Device '{target}' not found in device registry" + }, + "no_path": { + "message": "Can't write to directory {target}, no access to path!" + }, + "cant_write": { + "message": "Can't write to file" + }, + "not_loaded": { + "message": "{target} is not loaded" + } } } diff --git a/homeassistant/components/bluetooth/active_update_coordinator.py b/homeassistant/components/bluetooth/active_update_coordinator.py index cdf51d349784..174e5c66ce85 100644 --- a/homeassistant/components/bluetooth/active_update_coordinator.py +++ b/homeassistant/components/bluetooth/active_update_coordinator.py @@ -9,10 +9,10 @@ import logging from typing import Any, Generic, TypeVar from bleak import BleakError +from bluetooth_data_tools import monotonic_time_coarse from homeassistant.core import HomeAssistant, callback from homeassistant.helpers.debounce import Debouncer -from homeassistant.util.dt import monotonic_time_coarse from . import BluetoothChange, BluetoothScanningMode, BluetoothServiceInfoBleak from .passive_update_coordinator import PassiveBluetoothDataUpdateCoordinator diff --git a/homeassistant/components/bluetooth/active_update_processor.py b/homeassistant/components/bluetooth/active_update_processor.py index a3f5e20a9e97..3a13dda28a8a 100644 --- a/homeassistant/components/bluetooth/active_update_processor.py +++ b/homeassistant/components/bluetooth/active_update_processor.py @@ -9,10 +9,10 @@ import logging from typing import Any, Generic, TypeVar from bleak import BleakError +from bluetooth_data_tools import monotonic_time_coarse from homeassistant.core import HomeAssistant, callback from homeassistant.helpers.debounce import Debouncer -from homeassistant.util.dt import monotonic_time_coarse from . import BluetoothChange, BluetoothScanningMode, BluetoothServiceInfoBleak from .passive_update_processor import PassiveBluetoothProcessorCoordinator diff --git a/homeassistant/components/bluetooth/base_scanner.py b/homeassistant/components/bluetooth/base_scanner.py index 637ebbaf8678..21935dab6ef1 100644 --- a/homeassistant/components/bluetooth/base_scanner.py +++ b/homeassistant/components/bluetooth/base_scanner.py @@ -14,6 +14,7 @@ from bleak.backends.device import BLEDevice from bleak.backends.scanner import AdvertisementData from bleak_retry_connector import NO_RSSI_VALUE from bluetooth_adapters import DiscoveredDeviceAdvertisementData, adapter_human_name +from bluetooth_data_tools import monotonic_time_coarse from home_assistant_bluetooth import BluetoothServiceInfoBleak from homeassistant.const import EVENT_HOMEASSISTANT_STOP @@ -25,7 +26,6 @@ from homeassistant.core import ( ) from homeassistant.helpers.event import async_track_time_interval import homeassistant.util.dt as dt_util -from homeassistant.util.dt import monotonic_time_coarse from . import models from .const import ( diff --git a/homeassistant/components/bluetooth/manager.py b/homeassistant/components/bluetooth/manager.py index ce047747a0c6..e9f490285c99 100644 --- a/homeassistant/components/bluetooth/manager.py +++ b/homeassistant/components/bluetooth/manager.py @@ -16,6 +16,7 @@ from bluetooth_adapters import ( AdapterDetails, BluetoothAdapters, ) +from bluetooth_data_tools import monotonic_time_coarse from homeassistant import config_entries from homeassistant.const import EVENT_LOGGING_CHANGED @@ -27,7 +28,6 @@ from homeassistant.core import ( ) from homeassistant.helpers import discovery_flow from homeassistant.helpers.event import async_track_time_interval -from homeassistant.util.dt import monotonic_time_coarse from .advertisement_tracker import ( TRACKER_BUFFERING_WOBBLE_SECONDS, diff --git a/homeassistant/components/bluetooth/manifest.json b/homeassistant/components/bluetooth/manifest.json index c39c28b13f78..b4975e61507d 100644 --- a/homeassistant/components/bluetooth/manifest.json +++ b/homeassistant/components/bluetooth/manifest.json @@ -18,7 +18,7 @@ "bleak-retry-connector==3.3.0", "bluetooth-adapters==0.16.1", "bluetooth-auto-recovery==1.2.3", - "bluetooth-data-tools==1.15.0", + "bluetooth-data-tools==1.16.0", "dbus-fast==2.14.0" ] } diff --git a/homeassistant/components/bluetooth/models.py b/homeassistant/components/bluetooth/models.py index 1856ccd59944..1b8c12c6eb3f 100644 --- a/homeassistant/components/bluetooth/models.py +++ b/homeassistant/components/bluetooth/models.py @@ -7,10 +7,9 @@ from enum import Enum from typing import TYPE_CHECKING, Final from bleak import BaseBleakClient +from bluetooth_data_tools import monotonic_time_coarse from home_assistant_bluetooth import BluetoothServiceInfoBleak -from homeassistant.util.dt import monotonic_time_coarse - if TYPE_CHECKING: from .manager import BluetoothManager diff --git a/homeassistant/components/bluetooth/util.py b/homeassistant/components/bluetooth/util.py index e78eb51a38c1..f276b6b51e5a 100644 --- a/homeassistant/components/bluetooth/util.py +++ b/homeassistant/components/bluetooth/util.py @@ -3,9 +3,9 @@ from __future__ import annotations from bluetooth_adapters import BluetoothAdapters from bluetooth_auto_recovery import recover_adapter +from bluetooth_data_tools import monotonic_time_coarse from homeassistant.core import callback -from homeassistant.util.dt import monotonic_time_coarse from .models import BluetoothServiceInfoBleak from .storage import BluetoothStorage diff --git a/homeassistant/components/caldav/api.py b/homeassistant/components/caldav/api.py index f92360490486..fa89d6acc384 100644 --- a/homeassistant/components/caldav/api.py +++ b/homeassistant/components/caldav/api.py @@ -11,7 +11,11 @@ async def async_get_calendars( hass: HomeAssistant, client: caldav.DAVClient, component: str ) -> list[caldav.Calendar]: """Get all calendars that support the specified component.""" - calendars = await hass.async_add_executor_job(client.principal().calendars) + + def _get_calendars() -> list[caldav.Calendar]: + return client.principal().calendars() + + calendars = await hass.async_add_executor_job(_get_calendars) components_results = await asyncio.gather( *[ hass.async_add_executor_job(calendar.get_supported_components) diff --git a/homeassistant/components/deconz/strings.json b/homeassistant/components/deconz/strings.json index e32ab875c28b..c06a07e6ce5c 100644 --- a/homeassistant/components/deconz/strings.json +++ b/homeassistant/components/deconz/strings.json @@ -11,11 +11,14 @@ "data": { "host": "[%key:common::config_flow::data::host%]", "port": "[%key:common::config_flow::data::port%]" + }, + "data_description": { + "host": "The hostname or IP address of your deCONZ host." } }, "link": { "title": "Link with deCONZ", - "description": "Unlock your deCONZ gateway to register with Home Assistant.\n\n1. Go to deCONZ Settings -> Gateway -> Advanced\n2. Press \"Authenticate app\" button" + "description": "Unlock your deCONZ gateway to register with Home Assistant.\n\n1. Go to deCONZ Settings > Gateway > Advanced\n2. Press \"Authenticate app\" button" }, "hassio_confirm": { "title": "deCONZ Zigbee gateway via Home Assistant add-on", diff --git a/homeassistant/components/deluge/strings.json b/homeassistant/components/deluge/strings.json index e0266d004e2b..52706f398945 100644 --- a/homeassistant/components/deluge/strings.json +++ b/homeassistant/components/deluge/strings.json @@ -9,6 +9,9 @@ "password": "[%key:common::config_flow::data::password%]", "port": "[%key:common::config_flow::data::port%]", "web_port": "Web port (for visiting service)" + }, + "data_description": { + "host": "The hostname or IP address of your Deluge device." } } }, diff --git a/homeassistant/components/directv/strings.json b/homeassistant/components/directv/strings.json index 8ed52cd36321..2c30e3db85c3 100644 --- a/homeassistant/components/directv/strings.json +++ b/homeassistant/components/directv/strings.json @@ -8,6 +8,9 @@ "user": { "data": { "host": "[%key:common::config_flow::data::host%]" + }, + "data_description": { + "host": "The hostname or IP address of your DirectTV device." } } }, diff --git a/homeassistant/components/dlink/strings.json b/homeassistant/components/dlink/strings.json index 8c60d59fa6b2..9f21a9571e96 100644 --- a/homeassistant/components/dlink/strings.json +++ b/homeassistant/components/dlink/strings.json @@ -9,6 +9,7 @@ "use_legacy_protocol": "Use legacy protocol" }, "data_description": { + "host": "The hostname or IP address of your D-Link device", "password": "Default: PIN code on the back." } }, diff --git a/homeassistant/components/doorbird/strings.json b/homeassistant/components/doorbird/strings.json index ceaf1a891eed..c851de379d4c 100644 --- a/homeassistant/components/doorbird/strings.json +++ b/homeassistant/components/doorbird/strings.json @@ -17,8 +17,11 @@ "data": { "password": "[%key:common::config_flow::data::password%]", "host": "[%key:common::config_flow::data::host%]", - "name": "Device Name", + "name": "Device name", "username": "[%key:common::config_flow::data::username%]" + }, + "data_description": { + "host": "The hostname or IP address of your DoorBird device." } } }, diff --git a/homeassistant/components/dremel_3d_printer/strings.json b/homeassistant/components/dremel_3d_printer/strings.json index 0016b8f2bca1..9f6870b57f6f 100644 --- a/homeassistant/components/dremel_3d_printer/strings.json +++ b/homeassistant/components/dremel_3d_printer/strings.json @@ -4,6 +4,9 @@ "user": { "data": { "host": "[%key:common::config_flow::data::host%]" + }, + "data_description": { + "host": "The hostname or IP address of your Dremel 3D printer." } } }, diff --git a/homeassistant/components/dsmr/sensor.py b/homeassistant/components/dsmr/sensor.py index f56e2c3ed336..0fa04dee4898 100644 --- a/homeassistant/components/dsmr/sensor.py +++ b/homeassistant/components/dsmr/sensor.py @@ -799,6 +799,10 @@ class DSMREntity(SensorEntity): float(value), self._entry.data.get(CONF_PRECISION, DEFAULT_PRECISION) ) + # Make sure we do not return a zero value for an energy sensor + if not value and self.state_class == SensorStateClass.TOTAL_INCREASING: + return None + return value @staticmethod diff --git a/homeassistant/components/dunehd/strings.json b/homeassistant/components/dunehd/strings.json index f7e12b39f169..7d60a720a98c 100644 --- a/homeassistant/components/dunehd/strings.json +++ b/homeassistant/components/dunehd/strings.json @@ -5,6 +5,9 @@ "description": "Ensure that your player is turned on.", "data": { "host": "[%key:common::config_flow::data::host%]" + }, + "data_description": { + "host": "The hostname or IP address of your Dune HD device." } } }, diff --git a/homeassistant/components/duotecno/strings.json b/homeassistant/components/duotecno/strings.json index 93a545d31dc1..a5585c3dd2c8 100644 --- a/homeassistant/components/duotecno/strings.json +++ b/homeassistant/components/duotecno/strings.json @@ -6,6 +6,9 @@ "host": "[%key:common::config_flow::data::host%]", "username": "[%key:common::config_flow::data::username%]", "password": "[%key:common::config_flow::data::password%]" + }, + "data_description": { + "host": "The hostname or IP address of your Duotecno device." } } }, diff --git a/homeassistant/components/ecoforest/strings.json b/homeassistant/components/ecoforest/strings.json index d1767be5cdaa..1094e10ada3c 100644 --- a/homeassistant/components/ecoforest/strings.json +++ b/homeassistant/components/ecoforest/strings.json @@ -6,6 +6,9 @@ "host": "[%key:common::config_flow::data::host%]", "username": "[%key:common::config_flow::data::username%]", "password": "[%key:common::config_flow::data::password%]" + }, + "data_description": { + "host": "The hostname or IP address of your Ecoforest device." } } }, diff --git a/homeassistant/components/elgato/strings.json b/homeassistant/components/elgato/strings.json index e6b16215793c..6e1031c8ddff 100644 --- a/homeassistant/components/elgato/strings.json +++ b/homeassistant/components/elgato/strings.json @@ -7,6 +7,9 @@ "data": { "host": "[%key:common::config_flow::data::host%]", "port": "[%key:common::config_flow::data::port%]" + }, + "data_description": { + "host": "The hostname or IP address of your Elgato device." } }, "zeroconf_confirm": { diff --git a/homeassistant/components/emonitor/strings.json b/homeassistant/components/emonitor/strings.json index 675db1079351..08ffe030890a 100644 --- a/homeassistant/components/emonitor/strings.json +++ b/homeassistant/components/emonitor/strings.json @@ -5,6 +5,9 @@ "user": { "data": { "host": "[%key:common::config_flow::data::host%]" + }, + "data_description": { + "host": "The hostname or IP address of your SiteSage Emonitor device." } }, "confirm": { diff --git a/homeassistant/components/enphase_envoy/strings.json b/homeassistant/components/enphase_envoy/strings.json index 94cf92337455..fe32002e6b2a 100644 --- a/homeassistant/components/enphase_envoy/strings.json +++ b/homeassistant/components/enphase_envoy/strings.json @@ -8,6 +8,9 @@ "host": "[%key:common::config_flow::data::host%]", "username": "[%key:common::config_flow::data::username%]", "password": "[%key:common::config_flow::data::password%]" + }, + "data_description": { + "host": "The hostname or IP address of your Enphase Envoy gateway." } } }, diff --git a/homeassistant/components/epson/strings.json b/homeassistant/components/epson/strings.json index 4e3780322e93..94544c32d1d6 100644 --- a/homeassistant/components/epson/strings.json +++ b/homeassistant/components/epson/strings.json @@ -5,6 +5,9 @@ "data": { "host": "[%key:common::config_flow::data::host%]", "name": "[%key:common::config_flow::data::name%]" + }, + "data_description": { + "host": "The hostname or IP address of your Epson projector." } } }, diff --git a/homeassistant/components/esphome/manifest.json b/homeassistant/components/esphome/manifest.json index 7eca285681d9..db9cd9ba72c1 100644 --- a/homeassistant/components/esphome/manifest.json +++ b/homeassistant/components/esphome/manifest.json @@ -16,7 +16,7 @@ "loggers": ["aioesphomeapi", "noiseprotocol"], "requirements": [ "aioesphomeapi==19.2.1", - "bluetooth-data-tools==1.15.0", + "bluetooth-data-tools==1.16.0", "esphome-dashboard-api==1.2.3" ], "zeroconf": ["_esphomelib._tcp.local."] diff --git a/homeassistant/components/evil_genius_labs/strings.json b/homeassistant/components/evil_genius_labs/strings.json index 790e9a69c7fd..123d164444db 100644 --- a/homeassistant/components/evil_genius_labs/strings.json +++ b/homeassistant/components/evil_genius_labs/strings.json @@ -4,6 +4,9 @@ "user": { "data": { "host": "[%key:common::config_flow::data::host%]" + }, + "data_description": { + "host": "The hostname or IP address of your Evil Genius Labs device." } } }, diff --git a/homeassistant/components/evohome/__init__.py b/homeassistant/components/evohome/__init__.py index f4ceaf2c48c1..9c33b0fbf311 100644 --- a/homeassistant/components/evohome/__init__.py +++ b/homeassistant/components/evohome/__init__.py @@ -190,14 +190,14 @@ def _handle_exception(err) -> None: async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: """Create a (EMEA/EU-based) Honeywell TCC system.""" - async def load_auth_tokens(store) -> tuple[dict, dict | None]: + async def load_auth_tokens(store) -> tuple[dict[str, str | dt], dict[str, str]]: app_storage = await store.async_load() tokens = dict(app_storage or {}) if tokens.pop(CONF_USERNAME, None) != config[DOMAIN][CONF_USERNAME]: # any tokens won't be valid, and store might be corrupt await store.async_save({}) - return ({}, None) + return ({}, {}) # evohomeasync2 requires naive/local datetimes as strings if tokens.get(ACCESS_TOKEN_EXPIRES) is not None and ( @@ -205,7 +205,7 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: ): tokens[ACCESS_TOKEN_EXPIRES] = _dt_aware_to_naive(expires) - user_data = tokens.pop(USER_DATA, None) + user_data = tokens.pop(USER_DATA, {}) return (tokens, user_data) store = Store[dict[str, Any]](hass, STORAGE_VER, STORAGE_KEY) @@ -214,7 +214,7 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: client_v2 = evohomeasync2.EvohomeClient( config[DOMAIN][CONF_USERNAME], config[DOMAIN][CONF_PASSWORD], - **tokens, + **tokens, # type: ignore[arg-type] session=async_get_clientsession(hass), ) @@ -253,7 +253,7 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: client_v1 = evohomeasync.EvohomeClient( client_v2.username, client_v2.password, - user_data=user_data, + session_id=user_data.get("sessionId") if user_data else None, # STORAGE_VER 1 session=async_get_clientsession(hass), ) @@ -425,7 +425,7 @@ class EvoBroker: self.tcs_utc_offset = timedelta( minutes=client.locations[loc_idx].timeZone[UTC_OFFSET] ) - self.temps: dict[str, Any] | None = {} + self.temps: dict[str, float | None] = {} async def save_auth_tokens(self) -> None: """Save access tokens and session IDs to the store for later use.""" @@ -441,14 +441,12 @@ class EvoBroker: ACCESS_TOKEN_EXPIRES: access_token_expires.isoformat(), } - if self.client_v1 and self.client_v1.user_data: - user_id = self.client_v1.user_data["userInfo"]["userID"] # type: ignore[index] + if self.client_v1: app_storage[USER_DATA] = { # type: ignore[assignment] - "userInfo": {"userID": user_id}, - "sessionId": self.client_v1.user_data["sessionId"], - } + "sessionId": self.client_v1.broker.session_id, + } # this is the schema for STORAGE_VER == 1 else: - app_storage[USER_DATA] = None + app_storage[USER_DATA] = {} # type: ignore[assignment] await self._store.async_save(app_storage) @@ -468,16 +466,13 @@ class EvoBroker: async def _update_v1_api_temps(self, *args, **kwargs) -> None: """Get the latest high-precision temperatures of the default Location.""" - assert self.client_v1 + assert self.client_v1 # mypy check - def get_session_id(client_v1) -> str | None: - user_data = client_v1.user_data if client_v1 else None - return user_data.get("sessionId") if user_data else None - - session_id = get_session_id(self.client_v1) + session_id = self.client_v1.broker.session_id # maybe receive a new session_id? + self.temps = {} # these are now stale, will fall back to v2 temps try: - temps = list(await self.client_v1.temperatures(force_refresh=True)) + temps = await self.client_v1.get_temperatures() except evohomeasync.InvalidSchema as exc: _LOGGER.warning( @@ -489,7 +484,7 @@ class EvoBroker: ), exc, ) - self.temps = self.client_v1 = None + self.client_v1 = None except evohomeasync.EvohomeError as exc: _LOGGER.warning( @@ -501,7 +496,6 @@ class EvoBroker: ), exc, ) - self.temps = None # these are now stale, will fall back to v2 temps else: if ( @@ -513,19 +507,20 @@ class EvoBroker: "the v1 API's default location (there is more than one location), " "so the high-precision feature will be disabled until next restart" ) - self.temps = self.client_v1 = None + self.client_v1 = None else: self.temps = {str(i["id"]): i["temp"] for i in temps} finally: - if session_id != get_session_id(self.client_v1): + if self.client_v1 and session_id != self.client_v1.broker.session_id: await self.save_auth_tokens() _LOGGER.debug("Temperatures = %s", self.temps) async def _update_v2_api_state(self, *args, **kwargs) -> None: """Get the latest modes, temperatures, setpoints of a Location.""" - access_token = self.client.access_token + + access_token = self.client.access_token # maybe receive a new token? loc_idx = self.params[CONF_LOCATION_IDX] try: @@ -536,9 +531,9 @@ class EvoBroker: async_dispatcher_send(self.hass, DOMAIN) _LOGGER.debug("Status = %s", status) - - if access_token != self.client.access_token: - await self.save_auth_tokens() + finally: + if access_token != self.client.access_token: + await self.save_auth_tokens() async def async_update(self, *args, **kwargs) -> None: """Get the latest state data of an entire Honeywell TCC Location. @@ -562,6 +557,8 @@ class EvoDevice(Entity): _attr_should_poll = False + _evo_id: str + def __init__(self, evo_broker, evo_device) -> None: """Initialize the evohome entity.""" self._evo_device = evo_device @@ -623,18 +620,10 @@ class EvoChild(EvoDevice): @property def current_temperature(self) -> float | None: """Return the current temperature of a Zone.""" - if self._evo_device.TYPE == "domesticHotWater": - dev_id = self._evo_device.dhwId - else: - dev_id = self._evo_device.zoneId - if self._evo_broker.temps and self._evo_broker.temps[dev_id] is not None: - return self._evo_broker.temps[dev_id] - - if self._evo_device.temperatureStatus["isAvailable"]: - return self._evo_device.temperatureStatus["temperature"] - - return None + if self._evo_broker.temps.get(self._evo_id) is not None: + return self._evo_broker.temps[self._evo_id] + return self._evo_device.temperature @property def setpoints(self) -> dict[str, Any]: @@ -679,7 +668,7 @@ class EvoChild(EvoDevice): switchpoint_time_of_day = dt_util.parse_datetime( f"{sp_date}T{switchpoint['TimeOfDay']}" ) - assert switchpoint_time_of_day + assert switchpoint_time_of_day # mypy check dt_aware = _dt_evo_to_aware( switchpoint_time_of_day, self._evo_broker.tcs_utc_offset ) diff --git a/homeassistant/components/evohome/climate.py b/homeassistant/components/evohome/climate.py index fb608262a7df..dea5676d3328 100644 --- a/homeassistant/components/evohome/climate.py +++ b/homeassistant/components/evohome/climate.py @@ -150,6 +150,7 @@ class EvoZone(EvoChild, EvoClimateEntity): self._attr_unique_id = f"{evo_device.zoneId}z" else: self._attr_unique_id = evo_device.zoneId + self._evo_id = evo_device.zoneId self._attr_name = evo_device.name @@ -189,24 +190,27 @@ class EvoZone(EvoChild, EvoClimateEntity): ) @property - def hvac_mode(self) -> HVACMode: + def hvac_mode(self) -> HVACMode | None: """Return the current operating mode of a Zone.""" - if self._evo_tcs.systemModeStatus["mode"] in (EVO_AWAY, EVO_HEATOFF): + if self._evo_tcs.system_mode in (EVO_AWAY, EVO_HEATOFF): return HVACMode.AUTO - is_off = self.target_temperature <= self.min_temp - return HVACMode.OFF if is_off else HVACMode.HEAT + if self.target_temperature is None: + return None + if self.target_temperature <= self.min_temp: + return HVACMode.OFF + return HVACMode.HEAT @property - def target_temperature(self) -> float: + def target_temperature(self) -> float | None: """Return the target temperature of a Zone.""" - return self._evo_device.setpointStatus["targetHeatTemperature"] + return self._evo_device.target_heat_temperature @property def preset_mode(self) -> str | None: """Return the current preset mode, e.g., home, away, temp.""" - if self._evo_tcs.systemModeStatus["mode"] in (EVO_AWAY, EVO_HEATOFF): - return TCS_PRESET_TO_HA.get(self._evo_tcs.systemModeStatus["mode"]) - return EVO_PRESET_TO_HA.get(self._evo_device.setpointStatus["setpointMode"]) + if self._evo_tcs.system_mode in (EVO_AWAY, EVO_HEATOFF): + return TCS_PRESET_TO_HA.get(self._evo_tcs.system_mode) + return EVO_PRESET_TO_HA.get(self._evo_device.mode) @property def min_temp(self) -> float: @@ -214,7 +218,7 @@ class EvoZone(EvoChild, EvoClimateEntity): The default is 5, but is user-configurable within 5-35 (in Celsius). """ - return self._evo_device.setpointCapabilities["minHeatSetpoint"] + return self._evo_device.min_heat_setpoint @property def max_temp(self) -> float: @@ -222,17 +226,17 @@ class EvoZone(EvoChild, EvoClimateEntity): The default is 35, but is user-configurable within 5-35 (in Celsius). """ - return self._evo_device.setpointCapabilities["maxHeatSetpoint"] + return self._evo_device.max_heat_setpoint async def async_set_temperature(self, **kwargs: Any) -> None: """Set a new target temperature.""" temperature = kwargs["temperature"] if (until := kwargs.get("until")) is None: - if self._evo_device.setpointStatus["setpointMode"] == EVO_FOLLOW: + if self._evo_device.mode == EVO_FOLLOW: await self._update_schedule() until = dt_util.parse_datetime(self.setpoints.get("next_sp_from", "")) - elif self._evo_device.setpointStatus["setpointMode"] == EVO_TEMPOVER: + elif self._evo_device.mode == EVO_TEMPOVER: until = dt_util.parse_datetime(self._evo_device.setpointStatus["until"]) until = dt_util.as_utc(until) if until else None @@ -272,7 +276,7 @@ class EvoZone(EvoChild, EvoClimateEntity): await self._evo_broker.call_client_api(self._evo_device.reset_mode()) return - temperature = self._evo_device.setpointStatus["targetHeatTemperature"] + temperature = self._evo_device.target_heat_temperature if evo_preset_mode == EVO_TEMPOVER: await self._update_schedule() @@ -311,6 +315,7 @@ class EvoController(EvoClimateEntity): super().__init__(evo_broker, evo_device) self._attr_unique_id = evo_device.systemId + self._evo_id = evo_device.systemId self._attr_name = evo_device.location.name modes = [m["systemMode"] for m in evo_broker.config["allowedSystemModes"]] @@ -352,7 +357,7 @@ class EvoController(EvoClimateEntity): @property def hvac_mode(self) -> HVACMode: """Return the current operating mode of a Controller.""" - tcs_mode = self._evo_tcs.systemModeStatus["mode"] + tcs_mode = self._evo_tcs.system_mode return HVACMode.OFF if tcs_mode == EVO_HEATOFF else HVACMode.HEAT @property @@ -362,16 +367,18 @@ class EvoController(EvoClimateEntity): Controllers do not have a current temp, but one is expected by HA. """ temps = [ - z.temperatureStatus["temperature"] + z.temperature for z in self._evo_tcs.zones.values() - if z.temperatureStatus["isAvailable"] + if z.temperature is not None ] return round(sum(temps) / len(temps), 1) if temps else None @property def preset_mode(self) -> str | None: """Return the current preset mode, e.g., home, away, temp.""" - return TCS_PRESET_TO_HA.get(self._evo_tcs.systemModeStatus["mode"]) + if not self._evo_tcs.system_mode: + return None + return TCS_PRESET_TO_HA.get(self._evo_tcs.system_mode) async def async_set_temperature(self, **kwargs: Any) -> None: """Raise exception as Controllers don't have a target temperature.""" diff --git a/homeassistant/components/evohome/manifest.json b/homeassistant/components/evohome/manifest.json index 58efb2c25b27..769c8e597cdd 100644 --- a/homeassistant/components/evohome/manifest.json +++ b/homeassistant/components/evohome/manifest.json @@ -5,5 +5,5 @@ "documentation": "https://www.home-assistant.io/integrations/evohome", "iot_class": "cloud_polling", "loggers": ["evohomeasync", "evohomeasync2"], - "requirements": ["evohome-async==0.4.6"] + "requirements": ["evohome-async==0.4.9"] } diff --git a/homeassistant/components/evohome/water_heater.py b/homeassistant/components/evohome/water_heater.py index 5d49e9b46ec5..51617bdf1cff 100644 --- a/homeassistant/components/evohome/water_heater.py +++ b/homeassistant/components/evohome/water_heater.py @@ -68,6 +68,7 @@ class EvoDHW(EvoChild, WaterHeaterEntity): super().__init__(evo_broker, evo_device) self._attr_unique_id = evo_device.dhwId + self._evo_id = evo_device.dhwId self._attr_precision = ( PRECISION_TENTHS if evo_broker.client_v1 else PRECISION_WHOLE @@ -79,15 +80,15 @@ class EvoDHW(EvoChild, WaterHeaterEntity): @property def current_operation(self) -> str: """Return the current operating mode (Auto, On, or Off).""" - if self._evo_device.stateStatus["mode"] == EVO_FOLLOW: + if self._evo_device.mode == EVO_FOLLOW: return STATE_AUTO - return EVO_STATE_TO_HA[self._evo_device.stateStatus["state"]] + return EVO_STATE_TO_HA[self._evo_device.state] @property def is_away_mode_on(self): """Return True if away mode is on.""" - is_off = EVO_STATE_TO_HA[self._evo_device.stateStatus["state"]] == STATE_OFF - is_permanent = self._evo_device.stateStatus["mode"] == EVO_PERMOVER + is_off = EVO_STATE_TO_HA[self._evo_device.state] == STATE_OFF + is_permanent = self._evo_device.mode == EVO_PERMOVER return is_off and is_permanent async def async_set_operation_mode(self, operation_mode: str) -> None: diff --git a/homeassistant/components/fastdotcom/sensor.py b/homeassistant/components/fastdotcom/sensor.py index 33ad4853404f..939ab4a40e52 100644 --- a/homeassistant/components/fastdotcom/sensor.py +++ b/homeassistant/components/fastdotcom/sensor.py @@ -24,7 +24,7 @@ async def async_setup_entry( async_add_entities: AddEntitiesCallback, ) -> None: """Set up the Fast.com sensor.""" - async_add_entities([SpeedtestSensor(hass.data[DOMAIN])]) + async_add_entities([SpeedtestSensor(entry.entry_id, hass.data[DOMAIN])]) # pylint: disable-next=hass-invalid-inheritance # needs fixing @@ -38,9 +38,10 @@ class SpeedtestSensor(RestoreEntity, SensorEntity): _attr_icon = "mdi:speedometer" _attr_should_poll = False - def __init__(self, speedtest_data: dict[str, Any]) -> None: + def __init__(self, entry_id: str, speedtest_data: dict[str, Any]) -> None: """Initialize the sensor.""" self._speedtest_data = speedtest_data + self._attr_unique_id = entry_id async def async_added_to_hass(self) -> None: """Handle entity which will be added.""" diff --git a/homeassistant/components/fivem/strings.json b/homeassistant/components/fivem/strings.json index 2ffb401f8c06..abdef61fb28e 100644 --- a/homeassistant/components/fivem/strings.json +++ b/homeassistant/components/fivem/strings.json @@ -6,6 +6,9 @@ "name": "[%key:common::config_flow::data::name%]", "host": "[%key:common::config_flow::data::host%]", "port": "[%key:common::config_flow::data::port%]" + }, + "data_description": { + "host": "The hostname or IP address of your FiveM server." } } }, diff --git a/homeassistant/components/flexit_bacnet/__init__.py b/homeassistant/components/flexit_bacnet/__init__.py new file mode 100644 index 000000000000..c9a0b332d936 --- /dev/null +++ b/homeassistant/components/flexit_bacnet/__init__.py @@ -0,0 +1,43 @@ +"""The Flexit Nordic (BACnet) integration.""" +from __future__ import annotations + +import asyncio.exceptions + +from flexit_bacnet import FlexitBACnet +from flexit_bacnet.bacnet import DecodingError + +from homeassistant.config_entries import ConfigEntry +from homeassistant.const import CONF_DEVICE_ID, CONF_IP_ADDRESS, Platform +from homeassistant.core import HomeAssistant +from homeassistant.exceptions import ConfigEntryNotReady + +from .const import DOMAIN + +PLATFORMS: list[Platform] = [Platform.CLIMATE] + + +async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: + """Set up Flexit Nordic (BACnet) from a config entry.""" + + device = FlexitBACnet(entry.data[CONF_IP_ADDRESS], entry.data[CONF_DEVICE_ID]) + + try: + await device.update() + except (asyncio.exceptions.TimeoutError, ConnectionError, DecodingError) as exc: + raise ConfigEntryNotReady( + f"Timeout while connecting to {entry.data['address']}" + ) from exc + + hass.data.setdefault(DOMAIN, {})[entry.entry_id] = device + + await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS) + + return True + + +async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: + """Unload a config entry.""" + if unload_ok := await hass.config_entries.async_unload_platforms(entry, PLATFORMS): + hass.data[DOMAIN].pop(entry.entry_id) + + return unload_ok diff --git a/homeassistant/components/flexit_bacnet/climate.py b/homeassistant/components/flexit_bacnet/climate.py new file mode 100644 index 000000000000..28f4a6ae178a --- /dev/null +++ b/homeassistant/components/flexit_bacnet/climate.py @@ -0,0 +1,148 @@ +"""The Flexit Nordic (BACnet) integration.""" +import asyncio.exceptions +from typing import Any + +from flexit_bacnet import ( + VENTILATION_MODE_AWAY, + VENTILATION_MODE_HOME, + VENTILATION_MODE_STOP, + FlexitBACnet, +) +from flexit_bacnet.bacnet import DecodingError + +from homeassistant.components.climate import ( + PRESET_AWAY, + PRESET_BOOST, + PRESET_HOME, + ClimateEntity, + ClimateEntityFeature, + HVACMode, +) +from homeassistant.config_entries import ConfigEntry +from homeassistant.const import ATTR_TEMPERATURE, PRECISION_WHOLE, UnitOfTemperature +from homeassistant.core import HomeAssistant +from homeassistant.exceptions import HomeAssistantError +from homeassistant.helpers.device_registry import DeviceInfo +from homeassistant.helpers.entity_platform import AddEntitiesCallback + +from .const import ( + DOMAIN, + PRESET_TO_VENTILATION_MODE_MAP, + VENTILATION_TO_PRESET_MODE_MAP, +) + + +async def async_setup_entry( + hass: HomeAssistant, + config_entry: ConfigEntry, + async_add_devices: AddEntitiesCallback, +) -> None: + """Set up the Flexit Nordic unit.""" + device = hass.data[DOMAIN][config_entry.entry_id] + + async_add_devices([FlexitClimateEntity(device)]) + + +class FlexitClimateEntity(ClimateEntity): + """Flexit air handling unit.""" + + _attr_name = None + + _attr_has_entity_name = True + + _attr_hvac_modes = [ + HVACMode.OFF, + HVACMode.FAN_ONLY, + ] + + _attr_preset_modes = [ + PRESET_AWAY, + PRESET_HOME, + PRESET_BOOST, + ] + + _attr_supported_features = ( + ClimateEntityFeature.PRESET_MODE | ClimateEntityFeature.TARGET_TEMPERATURE + ) + + _attr_target_temperature_step = PRECISION_WHOLE + _attr_temperature_unit = UnitOfTemperature.CELSIUS + + def __init__(self, device: FlexitBACnet) -> None: + """Initialize the unit.""" + self._device = device + self._attr_unique_id = device.serial_number + self._attr_device_info = DeviceInfo( + identifiers={ + (DOMAIN, device.serial_number), + }, + name=device.device_name, + manufacturer="Flexit", + model="Nordic", + serial_number=device.serial_number, + ) + + async def async_update(self) -> None: + """Refresh unit state.""" + await self._device.update() + + @property + def current_temperature(self) -> float: + """Return the current temperature.""" + return self._device.room_temperature + + @property + def target_temperature(self) -> float: + """Return the temperature we try to reach.""" + if self._device.ventilation_mode == VENTILATION_MODE_AWAY: + return self._device.air_temp_setpoint_away + + return self._device.air_temp_setpoint_home + + async def async_set_temperature(self, **kwargs: Any) -> None: + """Set new target temperature.""" + if (temperature := kwargs.get(ATTR_TEMPERATURE)) is None: + return + + try: + if self._device.ventilation_mode == VENTILATION_MODE_AWAY: + await self._device.set_air_temp_setpoint_away(temperature) + else: + await self._device.set_air_temp_setpoint_home(temperature) + except (asyncio.exceptions.TimeoutError, ConnectionError, DecodingError) as exc: + raise HomeAssistantError from exc + + @property + def preset_mode(self) -> str: + """Return the current preset mode, e.g., home, away, temp. + + Requires ClimateEntityFeature.PRESET_MODE. + """ + return VENTILATION_TO_PRESET_MODE_MAP[self._device.ventilation_mode] + + async def async_set_preset_mode(self, preset_mode: str) -> None: + """Set new preset mode.""" + ventilation_mode = PRESET_TO_VENTILATION_MODE_MAP[preset_mode] + + try: + await self._device.set_ventilation_mode(ventilation_mode) + except (asyncio.exceptions.TimeoutError, ConnectionError, DecodingError) as exc: + raise HomeAssistantError from exc + + @property + def hvac_mode(self) -> HVACMode: + """Return hvac operation ie. heat, cool mode.""" + if self._device.ventilation_mode == VENTILATION_MODE_STOP: + return HVACMode.OFF + + return HVACMode.FAN_ONLY + + async def async_set_hvac_mode(self, hvac_mode: HVACMode) -> None: + """Set new target hvac mode.""" + try: + if hvac_mode == HVACMode.OFF: + await self._device.set_ventilation_mode(VENTILATION_MODE_STOP) + else: + await self._device.set_ventilation_mode(VENTILATION_MODE_HOME) + except (asyncio.exceptions.TimeoutError, ConnectionError, DecodingError) as exc: + raise HomeAssistantError from exc diff --git a/homeassistant/components/flexit_bacnet/config_flow.py b/homeassistant/components/flexit_bacnet/config_flow.py new file mode 100644 index 000000000000..2c87dfc5b97d --- /dev/null +++ b/homeassistant/components/flexit_bacnet/config_flow.py @@ -0,0 +1,62 @@ +"""Config flow for Flexit Nordic (BACnet) integration.""" +from __future__ import annotations + +import asyncio.exceptions +import logging +from typing import Any + +from flexit_bacnet import FlexitBACnet +from flexit_bacnet.bacnet import DecodingError +import voluptuous as vol + +from homeassistant import config_entries +from homeassistant.const import CONF_DEVICE_ID, CONF_IP_ADDRESS +from homeassistant.data_entry_flow import FlowResult + +from .const import DOMAIN + +_LOGGER = logging.getLogger(__name__) + +DEFAULT_DEVICE_ID = 2 + +STEP_USER_DATA_SCHEMA = vol.Schema( + { + vol.Required(CONF_IP_ADDRESS): str, + vol.Required(CONF_DEVICE_ID, default=DEFAULT_DEVICE_ID): int, + } +) + + +class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): + """Handle a config flow for Flexit Nordic (BACnet).""" + + VERSION = 1 + + async def async_step_user( + self, user_input: dict[str, Any] | None = None + ) -> FlowResult: + """Handle the initial step.""" + errors: dict[str, str] = {} + + if user_input is not None: + device = FlexitBACnet( + user_input[CONF_IP_ADDRESS], user_input[CONF_DEVICE_ID] + ) + try: + await device.update() + except (asyncio.exceptions.TimeoutError, ConnectionError, DecodingError): + errors["base"] = "cannot_connect" + except Exception: # pylint: disable=broad-except + _LOGGER.exception("Unexpected exception") + errors["base"] = "unknown" + else: + await self.async_set_unique_id(device.serial_number) + self._abort_if_unique_id_configured() + + return self.async_create_entry( + title=device.device_name, data=user_input + ) + + return self.async_show_form( + step_id="user", data_schema=STEP_USER_DATA_SCHEMA, errors=errors + ) diff --git a/homeassistant/components/flexit_bacnet/const.py b/homeassistant/components/flexit_bacnet/const.py new file mode 100644 index 000000000000..269a88c4cec8 --- /dev/null +++ b/homeassistant/components/flexit_bacnet/const.py @@ -0,0 +1,30 @@ +"""Constants for the Flexit Nordic (BACnet) integration.""" +from flexit_bacnet import ( + VENTILATION_MODE_AWAY, + VENTILATION_MODE_HIGH, + VENTILATION_MODE_HOME, + VENTILATION_MODE_STOP, +) + +from homeassistant.components.climate import ( + PRESET_AWAY, + PRESET_BOOST, + PRESET_HOME, + PRESET_NONE, +) + +DOMAIN = "flexit_bacnet" + +VENTILATION_TO_PRESET_MODE_MAP = { + VENTILATION_MODE_STOP: PRESET_NONE, + VENTILATION_MODE_AWAY: PRESET_AWAY, + VENTILATION_MODE_HOME: PRESET_HOME, + VENTILATION_MODE_HIGH: PRESET_BOOST, +} + +PRESET_TO_VENTILATION_MODE_MAP = { + PRESET_NONE: VENTILATION_MODE_STOP, + PRESET_AWAY: VENTILATION_MODE_AWAY, + PRESET_HOME: VENTILATION_MODE_HOME, + PRESET_BOOST: VENTILATION_MODE_HIGH, +} diff --git a/homeassistant/components/flexit_bacnet/manifest.json b/homeassistant/components/flexit_bacnet/manifest.json new file mode 100644 index 000000000000..d230e4ebb7a7 --- /dev/null +++ b/homeassistant/components/flexit_bacnet/manifest.json @@ -0,0 +1,10 @@ +{ + "domain": "flexit_bacnet", + "name": "Flexit Nordic (BACnet)", + "codeowners": ["@lellky", "@piotrbulinski"], + "config_flow": true, + "documentation": "https://www.home-assistant.io/integrations/flexit_bacnet", + "integration_type": "device", + "iot_class": "local_polling", + "requirements": ["flexit_bacnet==2.1.0"] +} diff --git a/homeassistant/components/komfovent/strings.json b/homeassistant/components/flexit_bacnet/strings.json similarity index 52% rename from homeassistant/components/komfovent/strings.json rename to homeassistant/components/flexit_bacnet/strings.json index 074754c1fe00..fd2725c6403f 100644 --- a/homeassistant/components/komfovent/strings.json +++ b/homeassistant/components/flexit_bacnet/strings.json @@ -3,16 +3,13 @@ "step": { "user": { "data": { - "host": "[%key:common::config_flow::data::host%]", - "username": "[%key:common::config_flow::data::username%]", - "password": "[%key:common::config_flow::data::password%]" + "ip_address": "[%key:common::config_flow::data::ip%]", + "device_id": "[%key:common::config_flow::data::device%]" } } }, "error": { "cannot_connect": "[%key:common::config_flow::error::cannot_connect%]", - "invalid_auth": "[%key:common::config_flow::error::invalid_auth%]", - "invalid_input": "Failed to parse provided hostname", "unknown": "[%key:common::config_flow::error::unknown%]" }, "abort": { diff --git a/homeassistant/components/flo/strings.json b/homeassistant/components/flo/strings.json index 627f562be7e1..3444911fbd45 100644 --- a/homeassistant/components/flo/strings.json +++ b/homeassistant/components/flo/strings.json @@ -6,6 +6,9 @@ "host": "[%key:common::config_flow::data::host%]", "username": "[%key:common::config_flow::data::username%]", "password": "[%key:common::config_flow::data::password%]" + }, + "data_description": { + "host": "The hostname or IP address of your Flo device." } } }, diff --git a/homeassistant/components/foscam/strings.json b/homeassistant/components/foscam/strings.json index 35964ee4546c..de22006b2745 100644 --- a/homeassistant/components/foscam/strings.json +++ b/homeassistant/components/foscam/strings.json @@ -9,6 +9,9 @@ "password": "[%key:common::config_flow::data::password%]", "rtsp_port": "RTSP port", "stream": "Stream" + }, + "data_description": { + "host": "The hostname or IP address of your Foscam camera." } } }, diff --git a/homeassistant/components/freebox/strings.json b/homeassistant/components/freebox/strings.json index 5c4143b4562a..eaa56a38da10 100644 --- a/homeassistant/components/freebox/strings.json +++ b/homeassistant/components/freebox/strings.json @@ -5,6 +5,9 @@ "data": { "host": "[%key:common::config_flow::data::host%]", "port": "[%key:common::config_flow::data::port%]" + }, + "data_description": { + "host": "The hostname or IP address of your Freebox router." } }, "link": { diff --git a/homeassistant/components/fritz/strings.json b/homeassistant/components/fritz/strings.json index 7cbb10a236b2..5eed2f59fc4d 100644 --- a/homeassistant/components/fritz/strings.json +++ b/homeassistant/components/fritz/strings.json @@ -26,6 +26,9 @@ "port": "[%key:common::config_flow::data::port%]", "username": "[%key:common::config_flow::data::username%]", "password": "[%key:common::config_flow::data::password%]" + }, + "data_description": { + "host": "The hostname or IP address of your FRITZ!Box router." } } }, diff --git a/homeassistant/components/fritzbox/strings.json b/homeassistant/components/fritzbox/strings.json index d5607aa30900..f4d2fe3670ef 100644 --- a/homeassistant/components/fritzbox/strings.json +++ b/homeassistant/components/fritzbox/strings.json @@ -8,6 +8,9 @@ "host": "[%key:common::config_flow::data::host%]", "username": "[%key:common::config_flow::data::username%]", "password": "[%key:common::config_flow::data::password%]" + }, + "data_description": { + "host": "The hostname or IP address of your FRITZ!Box router." } }, "confirm": { diff --git a/homeassistant/components/fritzbox_callmonitor/strings.json b/homeassistant/components/fritzbox_callmonitor/strings.json index 89f049bfbe90..ac36942eec2d 100644 --- a/homeassistant/components/fritzbox_callmonitor/strings.json +++ b/homeassistant/components/fritzbox_callmonitor/strings.json @@ -8,6 +8,9 @@ "port": "[%key:common::config_flow::data::port%]", "username": "[%key:common::config_flow::data::username%]", "password": "[%key:common::config_flow::data::password%]" + }, + "data_description": { + "host": "The hostname or IP address of your FRITZ!Box router." } }, "phonebook": { diff --git a/homeassistant/components/frontend/manifest.json b/homeassistant/components/frontend/manifest.json index 7a587d56d741..b6668383b548 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==20231129.1"] + "requirements": ["home-assistant-frontend==20231130.0"] } diff --git a/homeassistant/components/frontend/storage.py b/homeassistant/components/frontend/storage.py index 82f169dc6c96..91646dcb7458 100644 --- a/homeassistant/components/frontend/storage.py +++ b/homeassistant/components/frontend/storage.py @@ -1,7 +1,7 @@ """API for persistent storage for the frontend.""" from __future__ import annotations -from collections.abc import Callable +from collections.abc import Callable, Coroutine from functools import wraps from typing import Any @@ -50,12 +50,19 @@ async def async_user_store( return store, data[user_id] -def with_store(orig_func: Callable) -> Callable: +def with_store( + orig_func: Callable[ + [HomeAssistant, ActiveConnection, dict[str, Any], Store, dict[str, Any]], + Coroutine[Any, Any, None], + ], +) -> Callable[ + [HomeAssistant, ActiveConnection, dict[str, Any]], Coroutine[Any, Any, None] +]: """Decorate function to provide data.""" @wraps(orig_func) async def with_store_func( - hass: HomeAssistant, connection: ActiveConnection, msg: dict + hass: HomeAssistant, connection: ActiveConnection, msg: dict[str, Any] ) -> None: """Provide user specific data and store to function.""" user_id = connection.user.id diff --git a/homeassistant/components/frontier_silicon/strings.json b/homeassistant/components/frontier_silicon/strings.json index a10c3f535a10..03d9f28c0162 100644 --- a/homeassistant/components/frontier_silicon/strings.json +++ b/homeassistant/components/frontier_silicon/strings.json @@ -5,10 +5,13 @@ "data": { "host": "[%key:common::config_flow::data::host%]", "port": "[%key:common::config_flow::data::port%]" + }, + "data_description": { + "host": "The hostname or IP address of your Frontier Silicon device." } }, "device_config": { - "title": "Device Configuration", + "title": "Device configuration", "description": "The pin can be found via 'MENU button > Main Menu > System setting > Network > NetRemote PIN setup'", "data": { "pin": "[%key:common::config_flow::data::pin%]" diff --git a/homeassistant/components/fully_kiosk/coordinator.py b/homeassistant/components/fully_kiosk/coordinator.py index 17facb79dbbd..203251351ae5 100644 --- a/homeassistant/components/fully_kiosk/coordinator.py +++ b/homeassistant/components/fully_kiosk/coordinator.py @@ -19,13 +19,14 @@ class FullyKioskDataUpdateCoordinator(DataUpdateCoordinator): def __init__(self, hass: HomeAssistant, entry: ConfigEntry) -> None: """Initialize.""" + self.use_ssl = entry.data.get(CONF_SSL, False) self.fully = FullyKiosk( async_get_clientsession(hass), entry.data[CONF_HOST], DEFAULT_PORT, entry.data[CONF_PASSWORD], - use_ssl=entry.data[CONF_SSL], - verify_ssl=entry.data[CONF_VERIFY_SSL], + use_ssl=self.use_ssl, + verify_ssl=entry.data.get(CONF_VERIFY_SSL, False), ) super().__init__( hass, @@ -33,7 +34,6 @@ class FullyKioskDataUpdateCoordinator(DataUpdateCoordinator): name=entry.data[CONF_HOST], update_interval=UPDATE_INTERVAL, ) - self.use_ssl = entry.data[CONF_SSL] async def _async_update_data(self) -> dict[str, Any]: """Update data via library.""" diff --git a/homeassistant/components/fully_kiosk/strings.json b/homeassistant/components/fully_kiosk/strings.json index bf46feeec3ff..c1a1ef1fcf05 100644 --- a/homeassistant/components/fully_kiosk/strings.json +++ b/homeassistant/components/fully_kiosk/strings.json @@ -13,6 +13,9 @@ "password": "[%key:common::config_flow::data::password%]", "ssl": "[%key:common::config_flow::data::ssl%]", "verify_ssl": "[%key:common::config_flow::data::verify_ssl%]" + }, + "data_description": { + "host": "The hostname or IP address of the device running your Fully Kiosk Browser application." } } }, diff --git a/homeassistant/components/glances/strings.json b/homeassistant/components/glances/strings.json index fdd0c44b31ba..1bab098d65f7 100644 --- a/homeassistant/components/glances/strings.json +++ b/homeassistant/components/glances/strings.json @@ -10,6 +10,9 @@ "version": "Glances API Version (2 or 3)", "ssl": "[%key:common::config_flow::data::ssl%]", "verify_ssl": "[%key:common::config_flow::data::verify_ssl%]" + }, + "data_description": { + "host": "The hostname or IP address of the system running your Glances system monitor." } }, "reauth_confirm": { diff --git a/homeassistant/components/goalzero/strings.json b/homeassistant/components/goalzero/strings.json index d94f52196077..c6d85bd4c101 100644 --- a/homeassistant/components/goalzero/strings.json +++ b/homeassistant/components/goalzero/strings.json @@ -6,6 +6,9 @@ "data": { "host": "[%key:common::config_flow::data::host%]", "name": "[%key:common::config_flow::data::name%]" + }, + "data_description": { + "host": "The hostname or IP address of your Goal Zero Yeti." } }, "confirm_discovery": { diff --git a/homeassistant/components/google_assistant/helpers.py b/homeassistant/components/google_assistant/helpers.py index af892f15af48..c89925664e0c 100644 --- a/homeassistant/components/google_assistant/helpers.py +++ b/homeassistant/components/google_assistant/helpers.py @@ -686,8 +686,12 @@ class GoogleEntity: return device # Add Matter info - if "matter" in self.hass.config.components and ( - matter_info := matter.get_matter_device_info(self.hass, device_entry.id) + if ( + "matter" in self.hass.config.components + and any(x for x in device_entry.identifiers if x[0] == "matter") + and ( + matter_info := matter.get_matter_device_info(self.hass, device_entry.id) + ) ): device["matterUniqueId"] = matter_info["unique_id"] device["matterOriginalVendorId"] = matter_info["vendor_id"] diff --git a/homeassistant/components/guardian/__init__.py b/homeassistant/components/guardian/__init__.py index d7a9fe4e8361..bd2cb8c96de7 100644 --- a/homeassistant/components/guardian/__init__.py +++ b/homeassistant/components/guardian/__init__.py @@ -2,9 +2,9 @@ from __future__ import annotations import asyncio -from collections.abc import Awaitable, Callable +from collections.abc import Awaitable, Callable, Coroutine from dataclasses import dataclass -from typing import cast +from typing import Any, cast from aioguardian import Client from aioguardian.errors import GuardianError @@ -170,7 +170,9 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS) @callback - def call_with_data(func: Callable) -> Callable: + def call_with_data( + func: Callable[[ServiceCall, GuardianData], Coroutine[Any, Any, None]] + ) -> Callable[[ServiceCall], Coroutine[Any, Any, None]]: """Hydrate a service call with the appropriate GuardianData object.""" async def wrapper(call: ServiceCall) -> None: diff --git a/homeassistant/components/harmony/strings.json b/homeassistant/components/harmony/strings.json index c9c7a5597582..f6862ca3c834 100644 --- a/homeassistant/components/harmony/strings.json +++ b/homeassistant/components/harmony/strings.json @@ -7,6 +7,9 @@ "data": { "host": "[%key:common::config_flow::data::host%]", "name": "Hub Name" + }, + "data_description": { + "host": "The hostname or IP address of your Logitech Harmony Hub." } }, "link": { diff --git a/homeassistant/components/harmony/switch.py b/homeassistant/components/harmony/switch.py index a3c588c06bbe..6b833df97202 100644 --- a/homeassistant/components/harmony/switch.py +++ b/homeassistant/components/harmony/switch.py @@ -27,7 +27,7 @@ async def async_setup_entry( hass, DOMAIN, "deprecated_switches", - breaks_in_ha_version="2023.8.0", + breaks_in_ha_version="2024.6.0", is_fixable=False, severity=IssueSeverity.WARNING, translation_key="deprecated_switches", @@ -91,7 +91,7 @@ class HarmonyActivitySwitch(HarmonyEntity, SwitchEntity): self.hass, DOMAIN, f"deprecated_switches_{self.entity_id}_{item}", - breaks_in_ha_version="2023.8.0", + breaks_in_ha_version="2024.6.0", is_fixable=False, severity=IssueSeverity.WARNING, translation_key="deprecated_switches_entity", diff --git a/homeassistant/components/hassio/repairs.py b/homeassistant/components/hassio/repairs.py index 8337405641c7..fcfe23dda6e6 100644 --- a/homeassistant/components/hassio/repairs.py +++ b/homeassistant/components/hassio/repairs.py @@ -1,6 +1,7 @@ """Repairs implementation for supervisor integration.""" +from __future__ import annotations -from collections.abc import Callable +from collections.abc import Callable, Coroutine from types import MethodType from typing import Any @@ -116,7 +117,12 @@ class SupervisorIssueRepairFlow(RepairsFlow): return self.async_create_entry(data={}) @staticmethod - def _async_step(suggestion: Suggestion) -> Callable: + def _async_step( + suggestion: Suggestion, + ) -> Callable[ + [SupervisorIssueRepairFlow, dict[str, str] | None], + Coroutine[Any, Any, FlowResult], + ]: """Generate a step handler for a suggestion.""" async def _async_step( diff --git a/homeassistant/components/heos/strings.json b/homeassistant/components/heos/strings.json index 7bd362cf3d7c..df18fc7834a7 100644 --- a/homeassistant/components/heos/strings.json +++ b/homeassistant/components/heos/strings.json @@ -6,6 +6,9 @@ "description": "Please enter the host name or IP address of a Heos device (preferably one connected via wire to the network).", "data": { "host": "[%key:common::config_flow::data::host%]" + }, + "data_description": { + "host": "The hostname or IP address of your HEOS device." } } }, diff --git a/homeassistant/components/hlk_sw16/strings.json b/homeassistant/components/hlk_sw16/strings.json index d6e3212b4eab..ba74547e355e 100644 --- a/homeassistant/components/hlk_sw16/strings.json +++ b/homeassistant/components/hlk_sw16/strings.json @@ -6,6 +6,9 @@ "host": "[%key:common::config_flow::data::host%]", "username": "[%key:common::config_flow::data::username%]", "password": "[%key:common::config_flow::data::password%]" + }, + "data_description": { + "host": "The hostname or IP address of your Hi-Link HLK-SW-16 device." } } }, diff --git a/homeassistant/components/hue/strings.json b/homeassistant/components/hue/strings.json index 4022c61bc36e..122cb489d26d 100644 --- a/homeassistant/components/hue/strings.json +++ b/homeassistant/components/hue/strings.json @@ -5,12 +5,18 @@ "title": "Pick Hue bridge", "data": { "host": "[%key:common::config_flow::data::host%]" + }, + "data_description": { + "host": "The hostname or IP address of your Hue bridge." } }, "manual": { "title": "Manual configure a Hue bridge", "data": { "host": "[%key:common::config_flow::data::host%]" + }, + "data_description": { + "host": "The hostname or IP address of your Hue bridge." } }, "link": { diff --git a/homeassistant/components/hyperion/strings.json b/homeassistant/components/hyperion/strings.json index a2f8838e2ea3..8d7e3751c4cd 100644 --- a/homeassistant/components/hyperion/strings.json +++ b/homeassistant/components/hyperion/strings.json @@ -5,6 +5,9 @@ "data": { "host": "[%key:common::config_flow::data::host%]", "port": "[%key:common::config_flow::data::port%]" + }, + "data_description": { + "host": "The hostname or IP address of your Hyperion server." } }, "auth": { diff --git a/homeassistant/components/ialarm/strings.json b/homeassistant/components/ialarm/strings.json index 1ac7a25e6f89..cb2c75d74a9d 100644 --- a/homeassistant/components/ialarm/strings.json +++ b/homeassistant/components/ialarm/strings.json @@ -5,6 +5,9 @@ "data": { "host": "[%key:common::config_flow::data::host%]", "port": "[%key:common::config_flow::data::port%]" + }, + "data_description": { + "host": "The hostname or IP address of Antifurto365 iAlarm system." } } }, diff --git a/homeassistant/components/iotawatt/strings.json b/homeassistant/components/iotawatt/strings.json index f21dfe0cd09e..266b32c5c313 100644 --- a/homeassistant/components/iotawatt/strings.json +++ b/homeassistant/components/iotawatt/strings.json @@ -4,6 +4,9 @@ "user": { "data": { "host": "[%key:common::config_flow::data::host%]" + }, + "data_description": { + "host": "The hostname or IP address of your IoTaWatt device." } }, "auth": { diff --git a/homeassistant/components/keenetic_ndms2/strings.json b/homeassistant/components/keenetic_ndms2/strings.json index 13e3fabfbff1..765a3fc4d47c 100644 --- a/homeassistant/components/keenetic_ndms2/strings.json +++ b/homeassistant/components/keenetic_ndms2/strings.json @@ -9,6 +9,9 @@ "username": "[%key:common::config_flow::data::username%]", "password": "[%key:common::config_flow::data::password%]", "port": "[%key:common::config_flow::data::port%]" + }, + "data_description": { + "host": "The hostname or IP address of your Keenetic router." } } }, diff --git a/homeassistant/components/kmtronic/strings.json b/homeassistant/components/kmtronic/strings.json index 2a3a3a406877..6cecea12f223 100644 --- a/homeassistant/components/kmtronic/strings.json +++ b/homeassistant/components/kmtronic/strings.json @@ -6,6 +6,9 @@ "host": "[%key:common::config_flow::data::host%]", "username": "[%key:common::config_flow::data::username%]", "password": "[%key:common::config_flow::data::password%]" + }, + "data_description": { + "host": "The hostname or IP address of your KMtronic device." } } }, diff --git a/homeassistant/components/kodi/strings.json b/homeassistant/components/kodi/strings.json index 51431b317d64..7c7d53b33acd 100644 --- a/homeassistant/components/kodi/strings.json +++ b/homeassistant/components/kodi/strings.json @@ -8,6 +8,9 @@ "host": "[%key:common::config_flow::data::host%]", "port": "[%key:common::config_flow::data::port%]", "ssl": "[%key:common::config_flow::data::ssl%]" + }, + "data_description": { + "host": "The hostname or IP address of the system hosting your Kodi server." } }, "discovery_confirm": { diff --git a/homeassistant/components/komfovent/__init__.py b/homeassistant/components/komfovent/__init__.py deleted file mode 100644 index 0366a429b212..000000000000 --- a/homeassistant/components/komfovent/__init__.py +++ /dev/null @@ -1,34 +0,0 @@ -"""The Komfovent integration.""" -from __future__ import annotations - -import komfovent_api - -from homeassistant.config_entries import ConfigEntry -from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_USERNAME, Platform -from homeassistant.core import HomeAssistant -from homeassistant.exceptions import ConfigEntryNotReady - -from .const import DOMAIN - -PLATFORMS: list[Platform] = [Platform.CLIMATE] - - -async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: - """Set up Komfovent from a config entry.""" - host = entry.data[CONF_HOST] - username = entry.data[CONF_USERNAME] - password = entry.data[CONF_PASSWORD] - _, credentials = komfovent_api.get_credentials(host, username, password) - result, settings = await komfovent_api.get_settings(credentials) - if result != komfovent_api.KomfoventConnectionResult.SUCCESS: - raise ConfigEntryNotReady(f"Unable to connect to {host}: {result}") - - hass.data.setdefault(DOMAIN, {})[entry.entry_id] = (credentials, settings) - - await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS) - return True - - -async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: - """Unload a config entry.""" - return await hass.config_entries.async_unload_platforms(entry, PLATFORMS) diff --git a/homeassistant/components/komfovent/climate.py b/homeassistant/components/komfovent/climate.py deleted file mode 100644 index 2e51fddf4f2d..000000000000 --- a/homeassistant/components/komfovent/climate.py +++ /dev/null @@ -1,91 +0,0 @@ -"""Ventilation Units from Komfovent integration.""" -from __future__ import annotations - -import komfovent_api - -from homeassistant.components.climate import ( - ClimateEntity, - ClimateEntityFeature, - HVACMode, -) -from homeassistant.config_entries import ConfigEntry -from homeassistant.const import UnitOfTemperature -from homeassistant.core import HomeAssistant -from homeassistant.helpers.device_registry import DeviceInfo -from homeassistant.helpers.entity_platform import AddEntitiesCallback - -from .const import DOMAIN - -HASS_TO_KOMFOVENT_MODES = { - HVACMode.COOL: komfovent_api.KomfoventModes.COOL, - HVACMode.HEAT_COOL: komfovent_api.KomfoventModes.HEAT_COOL, - HVACMode.OFF: komfovent_api.KomfoventModes.OFF, - HVACMode.AUTO: komfovent_api.KomfoventModes.AUTO, -} -KOMFOVENT_TO_HASS_MODES = {v: k for k, v in HASS_TO_KOMFOVENT_MODES.items()} - - -async def async_setup_entry( - hass: HomeAssistant, - entry: ConfigEntry, - async_add_entities: AddEntitiesCallback, -) -> None: - """Set up the Komfovent unit control.""" - credentials, settings = hass.data[DOMAIN][entry.entry_id] - async_add_entities([KomfoventDevice(credentials, settings)], True) - - -class KomfoventDevice(ClimateEntity): - """Representation of a ventilation unit.""" - - _attr_hvac_modes = list(HASS_TO_KOMFOVENT_MODES.keys()) - _attr_preset_modes = [mode.name for mode in komfovent_api.KomfoventPresets] - _attr_supported_features = ClimateEntityFeature.PRESET_MODE - _attr_temperature_unit = UnitOfTemperature.CELSIUS - _attr_has_entity_name = True - _attr_name = None - - def __init__( - self, - credentials: komfovent_api.KomfoventCredentials, - settings: komfovent_api.KomfoventSettings, - ) -> None: - """Initialize the ventilation unit.""" - self._komfovent_credentials = credentials - self._komfovent_settings = settings - - self._attr_unique_id = settings.serial_number - self._attr_device_info = DeviceInfo( - identifiers={(DOMAIN, settings.serial_number)}, - model=settings.model, - name=settings.name, - serial_number=settings.serial_number, - sw_version=settings.version, - manufacturer="Komfovent", - ) - - async def async_set_preset_mode(self, preset_mode: str) -> None: - """Set new target preset mode.""" - await komfovent_api.set_preset( - self._komfovent_credentials, - komfovent_api.KomfoventPresets[preset_mode], - ) - - async def async_set_hvac_mode(self, hvac_mode: HVACMode) -> None: - """Set new target hvac mode.""" - await komfovent_api.set_mode( - self._komfovent_credentials, HASS_TO_KOMFOVENT_MODES[hvac_mode] - ) - - async def async_update(self) -> None: - """Get the latest data.""" - result, status = await komfovent_api.get_unit_status( - self._komfovent_credentials - ) - if result != komfovent_api.KomfoventConnectionResult.SUCCESS or not status: - self._attr_available = False - return - self._attr_available = True - self._attr_preset_mode = status.preset - self._attr_current_temperature = status.temp_extract - self._attr_hvac_mode = KOMFOVENT_TO_HASS_MODES[status.mode] diff --git a/homeassistant/components/komfovent/config_flow.py b/homeassistant/components/komfovent/config_flow.py deleted file mode 100644 index fb5390a30c66..000000000000 --- a/homeassistant/components/komfovent/config_flow.py +++ /dev/null @@ -1,74 +0,0 @@ -"""Config flow for Komfovent integration.""" -from __future__ import annotations - -import logging -from typing import Any - -import komfovent_api -import voluptuous as vol - -from homeassistant import config_entries -from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_USERNAME -from homeassistant.data_entry_flow import FlowResult - -from .const import DOMAIN - -_LOGGER = logging.getLogger(__name__) - -STEP_USER = "user" -STEP_USER_DATA_SCHEMA = vol.Schema( - { - vol.Required(CONF_HOST): str, - vol.Optional(CONF_USERNAME, default="user"): str, - vol.Required(CONF_PASSWORD): str, - } -) - -ERRORS_MAP = { - komfovent_api.KomfoventConnectionResult.NOT_FOUND: "cannot_connect", - komfovent_api.KomfoventConnectionResult.UNAUTHORISED: "invalid_auth", - komfovent_api.KomfoventConnectionResult.INVALID_INPUT: "invalid_input", -} - - -class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): - """Handle a config flow for Komfovent.""" - - VERSION = 1 - - def __return_error( - self, result: komfovent_api.KomfoventConnectionResult - ) -> FlowResult: - return self.async_show_form( - step_id=STEP_USER, - data_schema=STEP_USER_DATA_SCHEMA, - errors={"base": ERRORS_MAP.get(result, "unknown")}, - ) - - async def async_step_user( - self, user_input: dict[str, Any] | None = None - ) -> FlowResult: - """Handle the initial step.""" - if user_input is None: - return self.async_show_form( - step_id=STEP_USER, data_schema=STEP_USER_DATA_SCHEMA - ) - - conf_host = user_input[CONF_HOST] - conf_username = user_input[CONF_USERNAME] - conf_password = user_input[CONF_PASSWORD] - - result, credentials = komfovent_api.get_credentials( - conf_host, conf_username, conf_password - ) - if result != komfovent_api.KomfoventConnectionResult.SUCCESS: - return self.__return_error(result) - - result, settings = await komfovent_api.get_settings(credentials) - if result != komfovent_api.KomfoventConnectionResult.SUCCESS: - return self.__return_error(result) - - await self.async_set_unique_id(settings.serial_number) - self._abort_if_unique_id_configured() - - return self.async_create_entry(title=settings.name, data=user_input) diff --git a/homeassistant/components/komfovent/const.py b/homeassistant/components/komfovent/const.py deleted file mode 100644 index a7881a58c414..000000000000 --- a/homeassistant/components/komfovent/const.py +++ /dev/null @@ -1,3 +0,0 @@ -"""Constants for the Komfovent integration.""" - -DOMAIN = "komfovent" diff --git a/homeassistant/components/komfovent/manifest.json b/homeassistant/components/komfovent/manifest.json deleted file mode 100644 index cbe00ef8dc57..000000000000 --- a/homeassistant/components/komfovent/manifest.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "domain": "komfovent", - "name": "Komfovent", - "codeowners": ["@ProstoSanja"], - "config_flow": true, - "documentation": "https://www.home-assistant.io/integrations/komfovent", - "iot_class": "local_polling", - "requirements": ["komfovent-api==0.0.3"] -} diff --git a/homeassistant/components/lacrosse/sensor.py b/homeassistant/components/lacrosse/sensor.py index 7355a60f5f0f..40d38da55ebc 100644 --- a/homeassistant/components/lacrosse/sensor.py +++ b/homeassistant/components/lacrosse/sensor.py @@ -209,7 +209,7 @@ class LaCrosseHumidity(LaCrosseSensor): _attr_native_unit_of_measurement = PERCENTAGE _attr_state_class = SensorStateClass.MEASUREMENT - _attr_icon = "mdi:water-percent" + _attr_device_class = SensorDeviceClass.HUMIDITY @property def native_value(self) -> int | None: diff --git a/homeassistant/components/ld2410_ble/manifest.json b/homeassistant/components/ld2410_ble/manifest.json index 9fd407b16360..a90b5a71c2d0 100644 --- a/homeassistant/components/ld2410_ble/manifest.json +++ b/homeassistant/components/ld2410_ble/manifest.json @@ -20,5 +20,5 @@ "documentation": "https://www.home-assistant.io/integrations/ld2410_ble", "integration_type": "device", "iot_class": "local_push", - "requirements": ["bluetooth-data-tools==1.15.0", "ld2410-ble==0.1.1"] + "requirements": ["bluetooth-data-tools==1.16.0", "ld2410-ble==0.1.1"] } diff --git a/homeassistant/components/led_ble/manifest.json b/homeassistant/components/led_ble/manifest.json index 6ecd4ed636e0..ca46565b7737 100644 --- a/homeassistant/components/led_ble/manifest.json +++ b/homeassistant/components/led_ble/manifest.json @@ -32,5 +32,5 @@ "dependencies": ["bluetooth_adapters"], "documentation": "https://www.home-assistant.io/integrations/led_ble", "iot_class": "local_polling", - "requirements": ["bluetooth-data-tools==1.15.0", "led-ble==1.0.1"] + "requirements": ["bluetooth-data-tools==1.16.0", "led-ble==1.0.1"] } diff --git a/homeassistant/components/lg_soundbar/strings.json b/homeassistant/components/lg_soundbar/strings.json index 8c6a9909ff58..ee16a39350c0 100644 --- a/homeassistant/components/lg_soundbar/strings.json +++ b/homeassistant/components/lg_soundbar/strings.json @@ -4,6 +4,9 @@ "user": { "data": { "host": "[%key:common::config_flow::data::host%]" + }, + "data_description": { + "host": "The hostname or IP address of your LG Soundbar." } } }, diff --git a/homeassistant/components/light/__init__.py b/homeassistant/components/light/__init__.py index 78cccde5890f..3bb3797c284b 100644 --- a/homeassistant/components/light/__init__.py +++ b/homeassistant/components/light/__init__.py @@ -500,6 +500,14 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: # noqa: ) elif ColorMode.XY in supported_color_modes: params[ATTR_XY_COLOR] = color_util.color_hs_to_xy(*hs_color) + elif ColorMode.COLOR_TEMP in supported_color_modes: + xy_color = color_util.color_hs_to_xy(*hs_color) + params[ATTR_COLOR_TEMP_KELVIN] = color_util.color_xy_to_temperature( + *xy_color + ) + params[ATTR_COLOR_TEMP] = color_util.color_temperature_kelvin_to_mired( + params[ATTR_COLOR_TEMP_KELVIN] + ) elif ATTR_RGB_COLOR in params and ColorMode.RGB not in supported_color_modes: assert (rgb_color := params.pop(ATTR_RGB_COLOR)) is not None if ColorMode.RGBW in supported_color_modes: @@ -515,6 +523,14 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: # noqa: params[ATTR_HS_COLOR] = color_util.color_RGB_to_hs(*rgb_color) elif ColorMode.XY in supported_color_modes: params[ATTR_XY_COLOR] = color_util.color_RGB_to_xy(*rgb_color) + elif ColorMode.COLOR_TEMP in supported_color_modes: + xy_color = color_util.color_RGB_to_xy(*rgb_color) + params[ATTR_COLOR_TEMP_KELVIN] = color_util.color_xy_to_temperature( + *xy_color + ) + params[ATTR_COLOR_TEMP] = color_util.color_temperature_kelvin_to_mired( + params[ATTR_COLOR_TEMP_KELVIN] + ) elif ATTR_XY_COLOR in params and ColorMode.XY not in supported_color_modes: xy_color = params.pop(ATTR_XY_COLOR) if ColorMode.HS in supported_color_modes: @@ -529,6 +545,13 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: # noqa: params[ATTR_RGBWW_COLOR] = color_util.color_rgb_to_rgbww( *rgb_color, light.min_color_temp_kelvin, light.max_color_temp_kelvin ) + elif ColorMode.COLOR_TEMP in supported_color_modes: + params[ATTR_COLOR_TEMP_KELVIN] = color_util.color_xy_to_temperature( + *xy_color + ) + params[ATTR_COLOR_TEMP] = color_util.color_temperature_kelvin_to_mired( + params[ATTR_COLOR_TEMP_KELVIN] + ) elif ATTR_RGBW_COLOR in params and ColorMode.RGBW not in supported_color_modes: rgbw_color = params.pop(ATTR_RGBW_COLOR) rgb_color = color_util.color_rgbw_to_rgb(*rgbw_color) @@ -542,6 +565,14 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: # noqa: params[ATTR_HS_COLOR] = color_util.color_RGB_to_hs(*rgb_color) elif ColorMode.XY in supported_color_modes: params[ATTR_XY_COLOR] = color_util.color_RGB_to_xy(*rgb_color) + elif ColorMode.COLOR_TEMP in supported_color_modes: + xy_color = color_util.color_RGB_to_xy(*rgb_color) + params[ATTR_COLOR_TEMP_KELVIN] = color_util.color_xy_to_temperature( + *xy_color + ) + params[ATTR_COLOR_TEMP] = color_util.color_temperature_kelvin_to_mired( + params[ATTR_COLOR_TEMP_KELVIN] + ) elif ( ATTR_RGBWW_COLOR in params and ColorMode.RGBWW not in supported_color_modes ): @@ -558,6 +589,14 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: # noqa: params[ATTR_HS_COLOR] = color_util.color_RGB_to_hs(*rgb_color) elif ColorMode.XY in supported_color_modes: params[ATTR_XY_COLOR] = color_util.color_RGB_to_xy(*rgb_color) + elif ColorMode.COLOR_TEMP in supported_color_modes: + xy_color = color_util.color_RGB_to_xy(*rgb_color) + params[ATTR_COLOR_TEMP_KELVIN] = color_util.color_xy_to_temperature( + *xy_color + ) + params[ATTR_COLOR_TEMP] = color_util.color_temperature_kelvin_to_mired( + params[ATTR_COLOR_TEMP_KELVIN] + ) # If white is set to True, set it to the light's brightness # Add a warning in Home Assistant Core 2023.5 if the brightness is set to an diff --git a/homeassistant/components/matter/api.py b/homeassistant/components/matter/api.py index 7b4b7d35b7f0..227d0c73e89b 100644 --- a/homeassistant/components/matter/api.py +++ b/homeassistant/components/matter/api.py @@ -1,9 +1,9 @@ """Handle websocket api for Matter.""" from __future__ import annotations -from collections.abc import Callable +from collections.abc import Callable, Coroutine from functools import wraps -from typing import Any +from typing import Any, Concatenate, ParamSpec from matter_server.common.errors import MatterError import voluptuous as vol @@ -15,6 +15,8 @@ from homeassistant.core import HomeAssistant, callback from .adapter import MatterAdapter from .helpers import get_matter +_P = ParamSpec("_P") + ID = "id" TYPE = "type" @@ -28,12 +30,19 @@ def async_register_api(hass: HomeAssistant) -> None: websocket_api.async_register_command(hass, websocket_set_wifi_credentials) -def async_get_matter_adapter(func: Callable) -> Callable: +def async_get_matter_adapter( + func: Callable[ + [HomeAssistant, ActiveConnection, dict[str, Any], MatterAdapter], + Coroutine[Any, Any, None], + ], +) -> Callable[ + [HomeAssistant, ActiveConnection, dict[str, Any]], Coroutine[Any, Any, None] +]: """Decorate function to get the MatterAdapter.""" @wraps(func) async def _get_matter( - hass: HomeAssistant, connection: ActiveConnection, msg: dict + hass: HomeAssistant, connection: ActiveConnection, msg: dict[str, Any] ) -> None: """Provide the Matter client to the function.""" matter = get_matter(hass) @@ -43,7 +52,15 @@ def async_get_matter_adapter(func: Callable) -> Callable: return _get_matter -def async_handle_failed_command(func: Callable) -> Callable: +def async_handle_failed_command( + func: Callable[ + Concatenate[HomeAssistant, ActiveConnection, dict[str, Any], _P], + Coroutine[Any, Any, None], + ], +) -> Callable[ + Concatenate[HomeAssistant, ActiveConnection, dict[str, Any], _P], + Coroutine[Any, Any, None], +]: """Decorate function to handle MatterError and send relevant error.""" @wraps(func) @@ -51,8 +68,8 @@ def async_handle_failed_command(func: Callable) -> Callable: hass: HomeAssistant, connection: ActiveConnection, msg: dict[str, Any], - *args: Any, - **kwargs: Any, + *args: _P.args, + **kwargs: _P.kwargs, ) -> None: """Handle MatterError within function and send relevant error.""" try: diff --git a/homeassistant/components/matter/helpers.py b/homeassistant/components/matter/helpers.py index dcd6a30ee1fd..446d5dc3591b 100644 --- a/homeassistant/components/matter/helpers.py +++ b/homeassistant/components/matter/helpers.py @@ -94,7 +94,7 @@ def get_node_from_device_entry( ) if device_id_full is None: - raise ValueError(f"Device {device.id} is not a Matter device") + return None device_id = device_id_full.lstrip(device_id_type_prefix) matter_client = matter.matter_client diff --git a/homeassistant/components/matter/manifest.json b/homeassistant/components/matter/manifest.json index 174ebb1cab92..f350cda9227b 100644 --- a/homeassistant/components/matter/manifest.json +++ b/homeassistant/components/matter/manifest.json @@ -6,5 +6,5 @@ "dependencies": ["websocket_api"], "documentation": "https://www.home-assistant.io/integrations/matter", "iot_class": "local_push", - "requirements": ["python-matter-server==4.0.2"] + "requirements": ["python-matter-server==5.0.0"] } diff --git a/homeassistant/components/mill/manifest.json b/homeassistant/components/mill/manifest.json index cb0ba4522bf3..7bb78eb05e78 100644 --- a/homeassistant/components/mill/manifest.json +++ b/homeassistant/components/mill/manifest.json @@ -6,5 +6,5 @@ "documentation": "https://www.home-assistant.io/integrations/mill", "iot_class": "local_polling", "loggers": ["mill", "mill_local"], - "requirements": ["millheater==0.11.6", "mill-local==0.3.0"] + "requirements": ["millheater==0.11.7", "mill-local==0.3.0"] } diff --git a/homeassistant/components/ping/strings.json b/homeassistant/components/ping/strings.json index 31441df77367..12bc1d25c7a6 100644 --- a/homeassistant/components/ping/strings.json +++ b/homeassistant/components/ping/strings.json @@ -7,6 +7,9 @@ "data": { "host": "[%key:common::config_flow::data::host%]", "count": "Ping count" + }, + "data_description": { + "host": "The hostname or IP address of the device you want to ping." } } }, diff --git a/homeassistant/components/powerwall/__init__.py b/homeassistant/components/powerwall/__init__.py index 33395f5fe6ab..8587101a42a5 100644 --- a/homeassistant/components/powerwall/__init__.py +++ b/homeassistant/components/powerwall/__init__.py @@ -122,9 +122,9 @@ class PowerwallDataManager: async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: """Set up Tesla Powerwall from a config entry.""" http_session = requests.Session() - ip_address = entry.data[CONF_IP_ADDRESS] + ip_address: str = entry.data[CONF_IP_ADDRESS] - password = entry.data.get(CONF_PASSWORD) + password: str | None = entry.data.get(CONF_PASSWORD) power_wall = Powerwall(ip_address, http_session=http_session) try: base_info = await hass.async_add_executor_job( @@ -184,7 +184,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: def _login_and_fetch_base_info( - power_wall: Powerwall, host: str, password: str + power_wall: Powerwall, host: str, password: str | None ) -> PowerwallBaseInfo: """Login to the powerwall and fetch the base info.""" if password is not None: diff --git a/homeassistant/components/private_ble_device/manifest.json b/homeassistant/components/private_ble_device/manifest.json index b18716d80205..d894b18f5452 100644 --- a/homeassistant/components/private_ble_device/manifest.json +++ b/homeassistant/components/private_ble_device/manifest.json @@ -6,5 +6,5 @@ "dependencies": ["bluetooth_adapters"], "documentation": "https://www.home-assistant.io/integrations/private_ble_device", "iot_class": "local_push", - "requirements": ["bluetooth-data-tools==1.15.0"] + "requirements": ["bluetooth-data-tools==1.16.0"] } diff --git a/homeassistant/components/progettihwsw/strings.json b/homeassistant/components/progettihwsw/strings.json index bb98d565594e..d50c6f8d4e39 100644 --- a/homeassistant/components/progettihwsw/strings.json +++ b/homeassistant/components/progettihwsw/strings.json @@ -13,6 +13,9 @@ "data": { "host": "[%key:common::config_flow::data::host%]", "port": "[%key:common::config_flow::data::port%]" + }, + "data_description": { + "host": "The hostname or IP address of your ProgettiHWSW board." } }, "relay_modes": { diff --git a/homeassistant/components/qnap/strings.json b/homeassistant/components/qnap/strings.json index a5fa3c8a8971..d535b9f0e879 100644 --- a/homeassistant/components/qnap/strings.json +++ b/homeassistant/components/qnap/strings.json @@ -11,6 +11,9 @@ "port": "[%key:common::config_flow::data::port%]", "ssl": "[%key:common::config_flow::data::ssl%]", "verify_ssl": "[%key:common::config_flow::data::verify_ssl%]" + }, + "data_description": { + "host": "The hostname or IP address of your QNAP device." } } }, diff --git a/homeassistant/components/rainmachine/__init__.py b/homeassistant/components/rainmachine/__init__.py index c29154a941c4..fde9b945e531 100644 --- a/homeassistant/components/rainmachine/__init__.py +++ b/homeassistant/components/rainmachine/__init__.py @@ -2,7 +2,7 @@ from __future__ import annotations import asyncio -from collections.abc import Awaitable, Callable +from collections.abc import Callable, Coroutine from dataclasses import dataclass from datetime import timedelta from functools import partial, wraps @@ -326,10 +326,17 @@ async def async_setup_entry( # noqa: C901 entry.async_on_unload(entry.add_update_listener(async_reload_entry)) - def call_with_controller(update_programs_and_zones: bool = True) -> Callable: + def call_with_controller( + update_programs_and_zones: bool = True, + ) -> Callable[ + [Callable[[ServiceCall, Controller], Coroutine[Any, Any, None]]], + Callable[[ServiceCall], Coroutine[Any, Any, None]], + ]: """Hydrate a service call with the appropriate controller.""" - def decorator(func: Callable) -> Callable[..., Awaitable]: + def decorator( + func: Callable[[ServiceCall, Controller], Coroutine[Any, Any, None]] + ) -> Callable[[ServiceCall], Coroutine[Any, Any, None]]: """Define the decorator.""" @wraps(func) diff --git a/homeassistant/components/remote/significant_change.py b/homeassistant/components/remote/significant_change.py new file mode 100644 index 000000000000..8e5a36690411 --- /dev/null +++ b/homeassistant/components/remote/significant_change.py @@ -0,0 +1,27 @@ +"""Helper to test significant Remote state changes.""" +from __future__ import annotations + +from typing import Any + +from homeassistant.core import HomeAssistant, callback + +from . import ATTR_CURRENT_ACTIVITY + + +@callback +def async_check_significant_change( + hass: HomeAssistant, + old_state: str, + old_attrs: dict, + new_state: str, + new_attrs: dict, + **kwargs: Any, +) -> bool | None: + """Test if state significantly changed.""" + if old_state != new_state: + return True + + if old_attrs.get(ATTR_CURRENT_ACTIVITY) != new_attrs.get(ATTR_CURRENT_ACTIVITY): + return True + + return False diff --git a/homeassistant/components/reolink/__init__.py b/homeassistant/components/reolink/__init__.py index eca5a5aa853c..7f8448d277dd 100644 --- a/homeassistant/components/reolink/__init__.py +++ b/homeassistant/components/reolink/__init__.py @@ -16,7 +16,7 @@ from homeassistant.config_entries import ConfigEntry from homeassistant.const import EVENT_HOMEASSISTANT_STOP, Platform from homeassistant.core import HomeAssistant from homeassistant.exceptions import ConfigEntryAuthFailed, ConfigEntryNotReady -from homeassistant.helpers import device_registry as dr, entity_registry as er +from homeassistant.helpers import device_registry as dr from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed from .const import DOMAIN @@ -183,7 +183,7 @@ async def async_unload_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> def cleanup_disconnected_cams( hass: HomeAssistant, config_entry_id: str, host: ReolinkHost ) -> None: - """Clean-up disconnected camera channels or channels where a different model camera is connected.""" + """Clean-up disconnected camera channels.""" if not host.api.is_nvr: return @@ -206,14 +206,16 @@ def cleanup_disconnected_cams( if ch not in host.api.channels: remove = True _LOGGER.debug( - "Removing Reolink device %s, since no camera is connected to NVR channel %s anymore", + "Removing Reolink device %s, " + "since no camera is connected to NVR channel %s anymore", device.name, ch, ) if ch_model not in [device.model, "Unknown"]: remove = True _LOGGER.debug( - "Removing Reolink device %s, since the camera model connected to channel %s changed from %s to %s", + "Removing Reolink device %s, " + "since the camera model connected to channel %s changed from %s to %s", device.name, ch, device.model, @@ -222,12 +224,5 @@ def cleanup_disconnected_cams( if not remove: continue - # clean entity and device registry - entity_reg = er.async_get(hass) - entities = er.async_entries_for_device( - entity_reg, device.id, include_disabled_entities=True - ) - for entity in entities: - entity_reg.async_remove(entity.entity_id) - + # clean device registry and associated entities device_reg.async_remove_device(device.id) diff --git a/homeassistant/components/reolink/binary_sensor.py b/homeassistant/components/reolink/binary_sensor.py index e2e8e6b24f91..226b81b1c744 100644 --- a/homeassistant/components/reolink/binary_sensor.py +++ b/homeassistant/components/reolink/binary_sensor.py @@ -25,16 +25,18 @@ from homeassistant.helpers.entity_platform import AddEntitiesCallback from . import ReolinkData from .const import DOMAIN -from .entity import ReolinkChannelCoordinatorEntity +from .entity import ReolinkChannelCoordinatorEntity, ReolinkChannelEntityDescription @dataclass(kw_only=True) -class ReolinkBinarySensorEntityDescription(BinarySensorEntityDescription): +class ReolinkBinarySensorEntityDescription( + BinarySensorEntityDescription, + ReolinkChannelEntityDescription, +): """A class that describes binary sensor entities.""" icon_off: str = "mdi:motion-sensor-off" icon: str = "mdi:motion-sensor" - supported: Callable[[Host, int], bool] = lambda host, ch: True value: Callable[[Host, int], bool] @@ -128,8 +130,8 @@ class ReolinkBinarySensorEntity(ReolinkChannelCoordinatorEntity, BinarySensorEnt entity_description: ReolinkBinarySensorEntityDescription, ) -> None: """Initialize Reolink binary sensor.""" - super().__init__(reolink_data, channel) self.entity_description = entity_description + super().__init__(reolink_data, channel) if self._host.api.model in DUAL_LENS_DUAL_MOTION_MODELS: if entity_description.translation_key is not None: @@ -138,10 +140,6 @@ class ReolinkBinarySensorEntity(ReolinkChannelCoordinatorEntity, BinarySensorEnt key = entity_description.key self._attr_translation_key = f"{key}_lens_{self._channel}" - self._attr_unique_id = ( - f"{self._host.unique_id}_{self._channel}_{entity_description.key}" - ) - @property def icon(self) -> str | None: """Icon of the sensor.""" diff --git a/homeassistant/components/reolink/button.py b/homeassistant/components/reolink/button.py index 6e9c9c2e3863..88204d9a8066 100644 --- a/homeassistant/components/reolink/button.py +++ b/homeassistant/components/reolink/button.py @@ -27,7 +27,12 @@ from homeassistant.helpers.entity_platform import ( from . import ReolinkData from .const import DOMAIN -from .entity import ReolinkChannelCoordinatorEntity, ReolinkHostCoordinatorEntity +from .entity import ( + ReolinkChannelCoordinatorEntity, + ReolinkChannelEntityDescription, + ReolinkHostCoordinatorEntity, + ReolinkHostEntityDescription, +) ATTR_SPEED = "speed" SUPPORT_PTZ_SPEED = CameraEntityFeature.STREAM @@ -36,21 +41,23 @@ SUPPORT_PTZ_SPEED = CameraEntityFeature.STREAM @dataclass(kw_only=True) class ReolinkButtonEntityDescription( ButtonEntityDescription, + ReolinkChannelEntityDescription, ): """A class that describes button entities for a camera channel.""" enabled_default: Callable[[Host, int], bool] | None = None method: Callable[[Host, int], Any] - supported: Callable[[Host, int], bool] = lambda api, ch: True ptz_cmd: str | None = None @dataclass(kw_only=True) -class ReolinkHostButtonEntityDescription(ButtonEntityDescription): +class ReolinkHostButtonEntityDescription( + ButtonEntityDescription, + ReolinkHostEntityDescription, +): """A class that describes button entities for the host.""" method: Callable[[Host], Any] - supported: Callable[[Host], bool] = lambda api: True BUTTON_ENTITIES = ( @@ -195,12 +202,9 @@ class ReolinkButtonEntity(ReolinkChannelCoordinatorEntity, ButtonEntity): entity_description: ReolinkButtonEntityDescription, ) -> None: """Initialize Reolink button entity.""" - super().__init__(reolink_data, channel) self.entity_description = entity_description + super().__init__(reolink_data, channel) - self._attr_unique_id = ( - f"{self._host.unique_id}_{channel}_{entity_description.key}" - ) if entity_description.enabled_default is not None: self._attr_entity_registry_enabled_default = ( entity_description.enabled_default(self._host.api, self._channel) @@ -241,10 +245,8 @@ class ReolinkHostButtonEntity(ReolinkHostCoordinatorEntity, ButtonEntity): entity_description: ReolinkHostButtonEntityDescription, ) -> None: """Initialize Reolink button entity.""" - super().__init__(reolink_data) self.entity_description = entity_description - - self._attr_unique_id = f"{self._host.unique_id}_{entity_description.key}" + super().__init__(reolink_data) async def async_press(self) -> None: """Execute the button action.""" diff --git a/homeassistant/components/reolink/camera.py b/homeassistant/components/reolink/camera.py index ea9b84cd53f9..2ad8105c66c6 100644 --- a/homeassistant/components/reolink/camera.py +++ b/homeassistant/components/reolink/camera.py @@ -1,11 +1,10 @@ """Component providing support for Reolink IP cameras.""" from __future__ import annotations -from collections.abc import Callable from dataclasses import dataclass import logging -from reolink_aio.api import DUAL_LENS_MODELS, Host +from reolink_aio.api import DUAL_LENS_MODELS from reolink_aio.exceptions import ReolinkError from homeassistant.components.camera import ( @@ -20,7 +19,7 @@ from homeassistant.helpers.entity_platform import AddEntitiesCallback from . import ReolinkData from .const import DOMAIN -from .entity import ReolinkChannelCoordinatorEntity +from .entity import ReolinkChannelCoordinatorEntity, ReolinkChannelEntityDescription _LOGGER = logging.getLogger(__name__) @@ -28,11 +27,11 @@ _LOGGER = logging.getLogger(__name__) @dataclass(kw_only=True) class ReolinkCameraEntityDescription( CameraEntityDescription, + ReolinkChannelEntityDescription, ): """A class that describes camera entities for a camera channel.""" stream: str - supported: Callable[[Host, int], bool] = lambda api, ch: True CAMERA_ENTITIES = ( @@ -135,10 +134,6 @@ class ReolinkCamera(ReolinkChannelCoordinatorEntity, Camera): f"{entity_description.translation_key}_lens_{self._channel}" ) - self._attr_unique_id = ( - f"{self._host.unique_id}_{channel}_{entity_description.key}" - ) - async def stream_source(self) -> str | None: """Return the source of the stream.""" return await self._host.api.get_stream_source( diff --git a/homeassistant/components/reolink/entity.py b/homeassistant/components/reolink/entity.py index 5c874fb7ff92..584b380f3916 100644 --- a/homeassistant/components/reolink/entity.py +++ b/homeassistant/components/reolink/entity.py @@ -1,11 +1,14 @@ """Reolink parent entity class.""" from __future__ import annotations +from collections.abc import Callable +from dataclasses import dataclass from typing import TypeVar -from reolink_aio.api import DUAL_LENS_MODELS +from reolink_aio.api import DUAL_LENS_MODELS, Host from homeassistant.helpers.device_registry import CONNECTION_NETWORK_MAC, DeviceInfo +from homeassistant.helpers.entity import EntityDescription from homeassistant.helpers.update_coordinator import ( CoordinatorEntity, DataUpdateCoordinator, @@ -17,8 +20,22 @@ from .const import DOMAIN _T = TypeVar("_T") +@dataclass(kw_only=True) +class ReolinkChannelEntityDescription(EntityDescription): + """A class that describes entities for a camera channel.""" + + supported: Callable[[Host, int], bool] = lambda api, ch: True + + +@dataclass(kw_only=True) +class ReolinkHostEntityDescription(EntityDescription): + """A class that describes host entities.""" + + supported: Callable[[Host], bool] = lambda api: True + + class ReolinkBaseCoordinatorEntity(CoordinatorEntity[DataUpdateCoordinator[_T]]): - """Parent class fo Reolink entities.""" + """Parent class for Reolink entities.""" _attr_has_entity_name = True @@ -59,14 +76,20 @@ class ReolinkHostCoordinatorEntity(ReolinkBaseCoordinatorEntity[None]): basically a NVR with a single channel that has the camera connected to that channel. """ + entity_description: ReolinkHostEntityDescription | ReolinkChannelEntityDescription + def __init__(self, reolink_data: ReolinkData) -> None: """Initialize ReolinkHostCoordinatorEntity.""" super().__init__(reolink_data, reolink_data.device_coordinator) + self._attr_unique_id = f"{self._host.unique_id}_{self.entity_description.key}" + class ReolinkChannelCoordinatorEntity(ReolinkHostCoordinatorEntity): """Parent class for Reolink hardware camera entities connected to a channel of the NVR.""" + entity_description: ReolinkChannelEntityDescription + def __init__( self, reolink_data: ReolinkData, @@ -76,6 +99,9 @@ class ReolinkChannelCoordinatorEntity(ReolinkHostCoordinatorEntity): super().__init__(reolink_data) self._channel = channel + self._attr_unique_id = ( + f"{self._host.unique_id}_{channel}_{self.entity_description.key}" + ) dev_ch = channel if self._host.api.model in DUAL_LENS_MODELS: diff --git a/homeassistant/components/reolink/light.py b/homeassistant/components/reolink/light.py index f1aa0cb9ee2c..b2d0402b1b95 100644 --- a/homeassistant/components/reolink/light.py +++ b/homeassistant/components/reolink/light.py @@ -22,17 +22,19 @@ from homeassistant.helpers.entity_platform import AddEntitiesCallback from . import ReolinkData from .const import DOMAIN -from .entity import ReolinkChannelCoordinatorEntity +from .entity import ReolinkChannelCoordinatorEntity, ReolinkChannelEntityDescription @dataclass(kw_only=True) -class ReolinkLightEntityDescription(LightEntityDescription): +class ReolinkLightEntityDescription( + LightEntityDescription, + ReolinkChannelEntityDescription, +): """A class that describes light entities.""" get_brightness_fn: Callable[[Host, int], int | None] | None = None is_on_fn: Callable[[Host, int], bool] set_brightness_fn: Callable[[Host, int, int], Any] | None = None - supported_fn: Callable[[Host, int], bool] = lambda api, ch: True turn_on_off_fn: Callable[[Host, int, bool], Any] @@ -41,7 +43,7 @@ LIGHT_ENTITIES = ( key="floodlight", translation_key="floodlight", icon="mdi:spotlight-beam", - supported_fn=lambda api, ch: api.supported(ch, "floodLight"), + supported=lambda api, ch: api.supported(ch, "floodLight"), is_on_fn=lambda api, ch: api.whiteled_state(ch), turn_on_off_fn=lambda api, ch, value: api.set_whiteled(ch, state=value), get_brightness_fn=lambda api, ch: api.whiteled_brightness(ch), @@ -52,7 +54,7 @@ LIGHT_ENTITIES = ( translation_key="ir_lights", icon="mdi:led-off", entity_category=EntityCategory.CONFIG, - supported_fn=lambda api, ch: api.supported(ch, "ir_lights"), + supported=lambda api, ch: api.supported(ch, "ir_lights"), is_on_fn=lambda api, ch: api.ir_enabled(ch), turn_on_off_fn=lambda api, ch, value: api.set_ir_lights(ch, value), ), @@ -61,7 +63,7 @@ LIGHT_ENTITIES = ( translation_key="status_led", icon="mdi:lightning-bolt-circle", entity_category=EntityCategory.CONFIG, - supported_fn=lambda api, ch: api.supported(ch, "power_led"), + supported=lambda api, ch: api.supported(ch, "power_led"), is_on_fn=lambda api, ch: api.status_led_enabled(ch), turn_on_off_fn=lambda api, ch, value: api.set_status_led(ch, value), ), @@ -80,7 +82,7 @@ async def async_setup_entry( ReolinkLightEntity(reolink_data, channel, entity_description) for entity_description in LIGHT_ENTITIES for channel in reolink_data.host.api.channels - if entity_description.supported_fn(reolink_data.host.api, channel) + if entity_description.supported(reolink_data.host.api, channel) ) @@ -96,12 +98,8 @@ class ReolinkLightEntity(ReolinkChannelCoordinatorEntity, LightEntity): entity_description: ReolinkLightEntityDescription, ) -> None: """Initialize Reolink light entity.""" - super().__init__(reolink_data, channel) self.entity_description = entity_description - - self._attr_unique_id = ( - f"{self._host.unique_id}_{channel}_{entity_description.key}" - ) + super().__init__(reolink_data, channel) if entity_description.set_brightness_fn is None: self._attr_supported_color_modes = {ColorMode.ONOFF} diff --git a/homeassistant/components/reolink/number.py b/homeassistant/components/reolink/number.py index 1780465850a5..6a89eabba2b3 100644 --- a/homeassistant/components/reolink/number.py +++ b/homeassistant/components/reolink/number.py @@ -21,18 +21,20 @@ from homeassistant.helpers.entity_platform import AddEntitiesCallback from . import ReolinkData from .const import DOMAIN -from .entity import ReolinkChannelCoordinatorEntity +from .entity import ReolinkChannelCoordinatorEntity, ReolinkChannelEntityDescription @dataclass(kw_only=True) -class ReolinkNumberEntityDescription(NumberEntityDescription): +class ReolinkNumberEntityDescription( + NumberEntityDescription, + ReolinkChannelEntityDescription, +): """A class that describes number entities.""" get_max_value: Callable[[Host, int], float] | None = None get_min_value: Callable[[Host, int], float] | None = None method: Callable[[Host, int, float], Any] mode: NumberMode = NumberMode.AUTO - supported: Callable[[Host, int], bool] = lambda api, ch: True value: Callable[[Host, int], float | None] @@ -378,8 +380,8 @@ class ReolinkNumberEntity(ReolinkChannelCoordinatorEntity, NumberEntity): entity_description: ReolinkNumberEntityDescription, ) -> None: """Initialize Reolink number entity.""" - super().__init__(reolink_data, channel) self.entity_description = entity_description + super().__init__(reolink_data, channel) if entity_description.get_min_value is not None: self._attr_native_min_value = entity_description.get_min_value( @@ -390,9 +392,6 @@ class ReolinkNumberEntity(ReolinkChannelCoordinatorEntity, NumberEntity): self._host.api, channel ) self._attr_mode = entity_description.mode - self._attr_unique_id = ( - f"{self._host.unique_id}_{channel}_{entity_description.key}" - ) @property def native_value(self) -> float | None: diff --git a/homeassistant/components/reolink/select.py b/homeassistant/components/reolink/select.py index 566dbc92fbec..3d75b08b5d1e 100644 --- a/homeassistant/components/reolink/select.py +++ b/homeassistant/components/reolink/select.py @@ -24,18 +24,20 @@ from homeassistant.helpers.entity_platform import AddEntitiesCallback from . import ReolinkData from .const import DOMAIN -from .entity import ReolinkChannelCoordinatorEntity +from .entity import ReolinkChannelCoordinatorEntity, ReolinkChannelEntityDescription _LOGGER = logging.getLogger(__name__) @dataclass(kw_only=True) -class ReolinkSelectEntityDescription(SelectEntityDescription): +class ReolinkSelectEntityDescription( + SelectEntityDescription, + ReolinkChannelEntityDescription, +): """A class that describes select entities.""" get_options: list[str] | Callable[[Host, int], list[str]] method: Callable[[Host, int, str], Any] - supported: Callable[[Host, int], bool] = lambda api, ch: True value: Callable[[Host, int], str] | None = None @@ -131,14 +133,10 @@ class ReolinkSelectEntity(ReolinkChannelCoordinatorEntity, SelectEntity): entity_description: ReolinkSelectEntityDescription, ) -> None: """Initialize Reolink select entity.""" - super().__init__(reolink_data, channel) self.entity_description = entity_description + super().__init__(reolink_data, channel) self._log_error = True - self._attr_unique_id = ( - f"{self._host.unique_id}_{channel}_{entity_description.key}" - ) - if callable(entity_description.get_options): self._attr_options = entity_description.get_options(self._host.api, channel) else: diff --git a/homeassistant/components/reolink/sensor.py b/homeassistant/components/reolink/sensor.py index 9a03f497944a..3a5da97dc618 100644 --- a/homeassistant/components/reolink/sensor.py +++ b/homeassistant/components/reolink/sensor.py @@ -21,31 +21,32 @@ from homeassistant.helpers.typing import StateType from . import ReolinkData from .const import DOMAIN -from .entity import ReolinkChannelCoordinatorEntity, ReolinkHostCoordinatorEntity +from .entity import ( + ReolinkChannelCoordinatorEntity, + ReolinkChannelEntityDescription, + ReolinkHostCoordinatorEntity, + ReolinkHostEntityDescription, +) @dataclass(kw_only=True) -class ReolinkSensorEntityDescription(SensorEntityDescription): +class ReolinkSensorEntityDescription( + SensorEntityDescription, + ReolinkChannelEntityDescription, +): """A class that describes sensor entities for a camera channel.""" - supported: Callable[[Host, int], bool] = lambda api, ch: True value: Callable[[Host, int], int] -@dataclass -class ReolinkHostSensorEntityDescriptionMixin: - """Mixin values for Reolink host sensor entities.""" - - value: Callable[[Host], int | None] - - -@dataclass +@dataclass(kw_only=True) class ReolinkHostSensorEntityDescription( - SensorEntityDescription, ReolinkHostSensorEntityDescriptionMixin + SensorEntityDescription, + ReolinkHostEntityDescription, ): """A class that describes host sensor entities.""" - supported: Callable[[Host], bool] = lambda api: True + value: Callable[[Host], int | None] SENSORS = ( @@ -110,12 +111,8 @@ class ReolinkSensorEntity(ReolinkChannelCoordinatorEntity, SensorEntity): entity_description: ReolinkSensorEntityDescription, ) -> None: """Initialize Reolink sensor.""" - super().__init__(reolink_data, channel) self.entity_description = entity_description - - self._attr_unique_id = ( - f"{self._host.unique_id}_{channel}_{entity_description.key}" - ) + super().__init__(reolink_data, channel) @property def native_value(self) -> StateType | date | datetime | Decimal: @@ -134,10 +131,8 @@ class ReolinkHostSensorEntity(ReolinkHostCoordinatorEntity, SensorEntity): entity_description: ReolinkHostSensorEntityDescription, ) -> None: """Initialize Reolink host sensor.""" - super().__init__(reolink_data) self.entity_description = entity_description - - self._attr_unique_id = f"{self._host.unique_id}_{entity_description.key}" + super().__init__(reolink_data) @property def native_value(self) -> StateType | date | datetime | Decimal: diff --git a/homeassistant/components/reolink/siren.py b/homeassistant/components/reolink/siren.py index f063b65e2b4e..ec709f6ae3d8 100644 --- a/homeassistant/components/reolink/siren.py +++ b/homeassistant/components/reolink/siren.py @@ -1,11 +1,9 @@ """Component providing support for Reolink siren entities.""" from __future__ import annotations -from collections.abc import Callable from dataclasses import dataclass from typing import Any -from reolink_aio.api import Host from reolink_aio.exceptions import InvalidParameterError, ReolinkError from homeassistant.components.siren import ( @@ -22,15 +20,15 @@ from homeassistant.helpers.entity_platform import AddEntitiesCallback from . import ReolinkData from .const import DOMAIN -from .entity import ReolinkChannelCoordinatorEntity +from .entity import ReolinkChannelCoordinatorEntity, ReolinkChannelEntityDescription @dataclass -class ReolinkSirenEntityDescription(SirenEntityDescription): +class ReolinkSirenEntityDescription( + SirenEntityDescription, ReolinkChannelEntityDescription +): """A class that describes siren entities.""" - supported: Callable[[Host, int], bool] = lambda api, ch: True - SIREN_ENTITIES = ( ReolinkSirenEntityDescription( @@ -76,12 +74,8 @@ class ReolinkSirenEntity(ReolinkChannelCoordinatorEntity, SirenEntity): entity_description: ReolinkSirenEntityDescription, ) -> None: """Initialize Reolink siren entity.""" - super().__init__(reolink_data, channel) self.entity_description = entity_description - - self._attr_unique_id = ( - f"{self._host.unique_id}_{channel}_{entity_description.key}" - ) + super().__init__(reolink_data, channel) async def async_turn_on(self, **kwargs: Any) -> None: """Turn on the siren.""" diff --git a/homeassistant/components/reolink/switch.py b/homeassistant/components/reolink/switch.py index eb77b16478fb..fbb8922188db 100644 --- a/homeassistant/components/reolink/switch.py +++ b/homeassistant/components/reolink/switch.py @@ -17,24 +17,33 @@ from homeassistant.helpers.entity_platform import AddEntitiesCallback from . import ReolinkData from .const import DOMAIN -from .entity import ReolinkChannelCoordinatorEntity, ReolinkHostCoordinatorEntity +from .entity import ( + ReolinkChannelCoordinatorEntity, + ReolinkChannelEntityDescription, + ReolinkHostCoordinatorEntity, + ReolinkHostEntityDescription, +) @dataclass(kw_only=True) -class ReolinkSwitchEntityDescription(SwitchEntityDescription): +class ReolinkSwitchEntityDescription( + SwitchEntityDescription, + ReolinkChannelEntityDescription, +): """A class that describes switch entities.""" method: Callable[[Host, int, bool], Any] - supported: Callable[[Host, int], bool] = lambda api, ch: True value: Callable[[Host, int], bool] @dataclass(kw_only=True) -class ReolinkNVRSwitchEntityDescription(SwitchEntityDescription): +class ReolinkNVRSwitchEntityDescription( + SwitchEntityDescription, + ReolinkHostEntityDescription, +): """A class that describes NVR switch entities.""" method: Callable[[Host, bool], Any] - supported: Callable[[Host], bool] = lambda api: True value: Callable[[Host], bool] @@ -235,12 +244,8 @@ class ReolinkSwitchEntity(ReolinkChannelCoordinatorEntity, SwitchEntity): entity_description: ReolinkSwitchEntityDescription, ) -> None: """Initialize Reolink switch entity.""" - super().__init__(reolink_data, channel) self.entity_description = entity_description - - self._attr_unique_id = ( - f"{self._host.unique_id}_{channel}_{entity_description.key}" - ) + super().__init__(reolink_data, channel) @property def is_on(self) -> bool: @@ -275,8 +280,8 @@ class ReolinkNVRSwitchEntity(ReolinkHostCoordinatorEntity, SwitchEntity): entity_description: ReolinkNVRSwitchEntityDescription, ) -> None: """Initialize Reolink switch entity.""" - super().__init__(reolink_data) self.entity_description = entity_description + super().__init__(reolink_data) self._attr_unique_id = f"{self._host.unique_id}_{entity_description.key}" diff --git a/homeassistant/components/reolink/update.py b/homeassistant/components/reolink/update.py index a75af46e81ea..ffd429e92ad6 100644 --- a/homeassistant/components/reolink/update.py +++ b/homeassistant/components/reolink/update.py @@ -1,6 +1,7 @@ """Update entities for Reolink devices.""" from __future__ import annotations +from datetime import datetime import logging from typing import Any, Literal @@ -13,9 +14,10 @@ from homeassistant.components.update import ( UpdateEntityFeature, ) from homeassistant.config_entries import ConfigEntry -from homeassistant.core import HomeAssistant +from homeassistant.core import CALLBACK_TYPE, HomeAssistant from homeassistant.exceptions import HomeAssistantError from homeassistant.helpers.entity_platform import AddEntitiesCallback +from homeassistant.helpers.event import async_call_later from . import ReolinkData from .const import DOMAIN @@ -23,6 +25,8 @@ from .entity import ReolinkBaseCoordinatorEntity LOGGER = logging.getLogger(__name__) +POLL_AFTER_INSTALL = 120 + async def async_setup_entry( hass: HomeAssistant, @@ -51,6 +55,7 @@ class ReolinkUpdateEntity( super().__init__(reolink_data, reolink_data.firmware_coordinator) self._attr_unique_id = f"{self._host.unique_id}" + self._cancel_update: CALLBACK_TYPE | None = None @property def installed_version(self) -> str | None: @@ -100,3 +105,16 @@ class ReolinkUpdateEntity( ) from err finally: self.async_write_ha_state() + self._cancel_update = async_call_later( + self.hass, POLL_AFTER_INSTALL, self._async_update_future + ) + + async def _async_update_future(self, now: datetime | None = None) -> None: + """Request update.""" + await self.async_update() + + async def async_will_remove_from_hass(self) -> None: + """Entity removed.""" + await super().async_will_remove_from_hass() + if self._cancel_update is not None: + self._cancel_update() diff --git a/homeassistant/components/sabnzbd/__init__.py b/homeassistant/components/sabnzbd/__init__.py index babdbc573bd1..b1d118e6f75e 100644 --- a/homeassistant/components/sabnzbd/__init__.py +++ b/homeassistant/components/sabnzbd/__init__.py @@ -1,8 +1,9 @@ """Support for monitoring an SABnzbd NZB client.""" from __future__ import annotations -from collections.abc import Callable +from collections.abc import Callable, Coroutine import logging +from typing import Any from pysabnzbd import SabnzbdApiException import voluptuous as vol @@ -189,7 +190,9 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: update_device_identifiers(hass, entry) @callback - def extract_api(func: Callable) -> Callable: + def extract_api( + func: Callable[[ServiceCall, SabnzbdApiData], Coroutine[Any, Any, None]] + ) -> Callable[[ServiceCall], Coroutine[Any, Any, None]]: """Define a decorator to get the correct api for a service call.""" async def wrapper(call: ServiceCall) -> None: diff --git a/homeassistant/components/samsungtv/strings.json b/homeassistant/components/samsungtv/strings.json index f1f237fa4fb0..c9d08f756d01 100644 --- a/homeassistant/components/samsungtv/strings.json +++ b/homeassistant/components/samsungtv/strings.json @@ -7,6 +7,9 @@ "data": { "host": "[%key:common::config_flow::data::host%]", "name": "[%key:common::config_flow::data::name%]" + }, + "data_description": { + "host": "The hostname or IP address of your TV." } }, "confirm": { diff --git a/homeassistant/components/sensibo/entity.py b/homeassistant/components/sensibo/entity.py index 9f20c0515761..0a60fc4a85d9 100644 --- a/homeassistant/components/sensibo/entity.py +++ b/homeassistant/components/sensibo/entity.py @@ -23,22 +23,24 @@ def async_handle_api_call( ) -> Callable[Concatenate[_T, _P], Coroutine[Any, Any, Any]]: """Decorate api calls.""" - async def wrap_api_call(*args: Any, **kwargs: Any) -> None: + async def wrap_api_call(entity: _T, *args: _P.args, **kwargs: _P.kwargs) -> None: """Wrap services for api calls.""" res: bool = False try: async with asyncio.timeout(TIMEOUT): - res = await function(*args, **kwargs) + res = await function(entity, *args, **kwargs) except SENSIBO_ERRORS as err: raise HomeAssistantError from err - LOGGER.debug("Result %s for entity %s with arguments %s", res, args[0], kwargs) - entity: SensiboDeviceBaseEntity = args[0] + LOGGER.debug("Result %s for entity %s with arguments %s", res, entity, kwargs) if res is not True: raise HomeAssistantError(f"Could not execute service for {entity.name}") - if kwargs.get("key") is not None and kwargs.get("value") is not None: - setattr(entity.device_data, kwargs["key"], kwargs["value"]) - LOGGER.debug("Debug check key %s is now %s", kwargs["key"], kwargs["value"]) + if ( + isinstance(key := kwargs.get("key"), str) + and (value := kwargs.get("value")) is not None + ): + setattr(entity.device_data, key, value) + LOGGER.debug("Debug check key %s is now %s", key, value) entity.async_write_ha_state() await entity.coordinator.async_request_refresh() diff --git a/homeassistant/components/sfr_box/strings.json b/homeassistant/components/sfr_box/strings.json index 7ea183041642..6f0001e97ced 100644 --- a/homeassistant/components/sfr_box/strings.json +++ b/homeassistant/components/sfr_box/strings.json @@ -26,6 +26,9 @@ "data": { "host": "[%key:common::config_flow::data::host%]" }, + "data_description": { + "host": "The hostname or IP address of your SFR device." + }, "description": "Setting the credentials is optional, but enables additional functionality." } } diff --git a/homeassistant/components/shelly/climate.py b/homeassistant/components/shelly/climate.py index d855e8b238bf..6a592c904f6c 100644 --- a/homeassistant/components/shelly/climate.py +++ b/homeassistant/components/shelly/climate.py @@ -42,12 +42,7 @@ from .const import ( ) from .coordinator import ShellyBlockCoordinator, ShellyRpcCoordinator, get_entry_data from .entity import ShellyRpcEntity -from .utils import ( - async_remove_shelly_entity, - get_device_entry_gen, - get_rpc_key_ids, - is_relay_used_as_actuator, -) +from .utils import async_remove_shelly_entity, get_device_entry_gen, get_rpc_key_ids async def async_setup_entry( @@ -131,7 +126,9 @@ def async_setup_rpc_entry( for id_ in climate_key_ids: climate_ids.append(id_) - if is_relay_used_as_actuator(id_, coordinator.mac, coordinator.device.config): + if coordinator.device.shelly.get("relay_in_thermostat", False): + # Wall Display relay is used as the thermostat actuator, + # we need to remove a switch entity unique_id = f"{coordinator.mac}-switch:{id_}" async_remove_shelly_entity(hass, "switch", unique_id) diff --git a/homeassistant/components/shelly/strings.json b/homeassistant/components/shelly/strings.json index 49c66a564592..9230ae605e0e 100644 --- a/homeassistant/components/shelly/strings.json +++ b/homeassistant/components/shelly/strings.json @@ -8,7 +8,7 @@ "host": "[%key:common::config_flow::data::host%]" }, "data_description": { - "host": "The hostname or IP address of the Shelly device to control." + "host": "The hostname or IP address of the Shelly device to connect to." } }, "credentials": { diff --git a/homeassistant/components/shelly/switch.py b/homeassistant/components/shelly/switch.py index 35429c858f5d..5a398182e4d1 100644 --- a/homeassistant/components/shelly/switch.py +++ b/homeassistant/components/shelly/switch.py @@ -118,8 +118,9 @@ def async_setup_rpc_entry( continue if coordinator.model == MODEL_WALL_DISPLAY: - if coordinator.device.shelly["relay_operational"]: - # Wall Display in relay mode, we need to remove a climate entity + if not coordinator.device.shelly.get("relay_in_thermostat", False): + # Wall Display relay is not used as the thermostat actuator, + # we need to remove a climate entity unique_id = f"{coordinator.mac}-thermostat:{id_}" async_remove_shelly_entity(hass, "climate", unique_id) else: diff --git a/homeassistant/components/shelly/utils.py b/homeassistant/components/shelly/utils.py index 0209dc63aa86..6b5c59f28dbd 100644 --- a/homeassistant/components/shelly/utils.py +++ b/homeassistant/components/shelly/utils.py @@ -430,10 +430,3 @@ def get_release_url(gen: int, model: str, beta: bool) -> str | None: return None return GEN1_RELEASE_URL if gen == 1 else GEN2_RELEASE_URL - - -def is_relay_used_as_actuator(relay_id: int, mac: str, config: dict[str, Any]) -> bool: - """Return True if an internal relay is used as the thermostat actuator.""" - return f"{mac}/c/switch:{relay_id}".lower() in config[f"thermostat:{relay_id}"].get( - "actuator", "" - ) diff --git a/homeassistant/components/simplisafe/__init__.py b/homeassistant/components/simplisafe/__init__.py index 7b57fa1fc320..b1bd2c8e9d60 100644 --- a/homeassistant/components/simplisafe/__init__.py +++ b/homeassistant/components/simplisafe/__init__.py @@ -2,7 +2,7 @@ from __future__ import annotations import asyncio -from collections.abc import Callable, Iterable +from collections.abc import Callable, Coroutine, Iterable from datetime import timedelta from typing import Any, cast @@ -336,7 +336,9 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS) @callback - def extract_system(func: Callable) -> Callable: + def extract_system( + func: Callable[[ServiceCall, SystemType], Coroutine[Any, Any, None]] + ) -> Callable[[ServiceCall], Coroutine[Any, Any, None]]: """Define a decorator to get the correct system for a service call.""" async def wrapper(call: ServiceCall) -> None: diff --git a/homeassistant/components/sma/strings.json b/homeassistant/components/sma/strings.json index f5dc6c16c880..16e5d7408c48 100644 --- a/homeassistant/components/sma/strings.json +++ b/homeassistant/components/sma/strings.json @@ -19,6 +19,9 @@ "ssl": "[%key:common::config_flow::data::ssl%]", "verify_ssl": "[%key:common::config_flow::data::verify_ssl%]" }, + "data_description": { + "host": "The hostname or IP address of your SMA device." + }, "description": "Enter your SMA device information.", "title": "Set up SMA Solar" } diff --git a/homeassistant/components/snapcast/strings.json b/homeassistant/components/snapcast/strings.json index 0d51c7543f1b..b56739105952 100644 --- a/homeassistant/components/snapcast/strings.json +++ b/homeassistant/components/snapcast/strings.json @@ -7,6 +7,9 @@ "host": "[%key:common::config_flow::data::host%]", "port": "[%key:common::config_flow::data::port%]" }, + "data_description": { + "host": "The hostname or IP address of your Snapcast server." + }, "title": "[%key:common::action::connect%]" } }, diff --git a/homeassistant/components/synology_dsm/media_source.py b/homeassistant/components/synology_dsm/media_source.py index 16db365f708c..3f30fe9b4e9c 100644 --- a/homeassistant/components/synology_dsm/media_source.py +++ b/homeassistant/components/synology_dsm/media_source.py @@ -153,8 +153,7 @@ class SynologyPhotosMediaSource(MediaSource): ret = [] for album_item in album_items: mime_type, _ = mimetypes.guess_type(album_item.file_name) - assert isinstance(mime_type, str) - if mime_type.startswith("image/"): + if isinstance(mime_type, str) and mime_type.startswith("image/"): # Force small small thumbnails album_item.thumbnail_size = "sm" ret.append( diff --git a/homeassistant/components/trend/binary_sensor.py b/homeassistant/components/trend/binary_sensor.py index 2d00f35202c8..fa6ad8e5382d 100644 --- a/homeassistant/components/trend/binary_sensor.py +++ b/homeassistant/components/trend/binary_sensor.py @@ -52,23 +52,39 @@ from .const import ( CONF_INVERT, CONF_MAX_SAMPLES, CONF_MIN_GRADIENT, + CONF_MIN_SAMPLES, CONF_SAMPLE_DURATION, DOMAIN, ) _LOGGER = logging.getLogger(__name__) -SENSOR_SCHEMA = vol.Schema( - { - vol.Required(CONF_ENTITY_ID): cv.entity_id, - vol.Optional(CONF_ATTRIBUTE): cv.string, - vol.Optional(CONF_DEVICE_CLASS): DEVICE_CLASSES_SCHEMA, - vol.Optional(CONF_FRIENDLY_NAME): cv.string, - vol.Optional(CONF_INVERT, default=False): cv.boolean, - vol.Optional(CONF_MAX_SAMPLES, default=2): cv.positive_int, - vol.Optional(CONF_MIN_GRADIENT, default=0.0): vol.Coerce(float), - vol.Optional(CONF_SAMPLE_DURATION, default=0): cv.positive_int, - } + +def _validate_min_max(data: dict[str, Any]) -> dict[str, Any]: + if ( + CONF_MIN_SAMPLES in data + and CONF_MAX_SAMPLES in data + and data[CONF_MAX_SAMPLES] < data[CONF_MIN_SAMPLES] + ): + raise vol.Invalid("min_samples must be smaller than or equal to max_samples") + return data + + +SENSOR_SCHEMA = vol.All( + vol.Schema( + { + vol.Required(CONF_ENTITY_ID): cv.entity_id, + vol.Optional(CONF_ATTRIBUTE): cv.string, + vol.Optional(CONF_DEVICE_CLASS): DEVICE_CLASSES_SCHEMA, + vol.Optional(CONF_FRIENDLY_NAME): cv.string, + vol.Optional(CONF_INVERT, default=False): cv.boolean, + vol.Optional(CONF_MAX_SAMPLES, default=2): cv.positive_int, + vol.Optional(CONF_MIN_GRADIENT, default=0.0): vol.Coerce(float), + vol.Optional(CONF_SAMPLE_DURATION, default=0): cv.positive_int, + vol.Optional(CONF_MIN_SAMPLES, default=2): cv.positive_int, + } + ), + _validate_min_max, ) PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( @@ -96,6 +112,7 @@ async def async_setup_platform( max_samples = device_config[CONF_MAX_SAMPLES] min_gradient = device_config[CONF_MIN_GRADIENT] sample_duration = device_config[CONF_SAMPLE_DURATION] + min_samples = device_config[CONF_MIN_SAMPLES] sensors.append( SensorTrend( @@ -109,8 +126,10 @@ async def async_setup_platform( max_samples, min_gradient, sample_duration, + min_samples, ) ) + if not sensors: _LOGGER.error("No sensors added") return @@ -137,6 +156,7 @@ class SensorTrend(BinarySensorEntity, RestoreEntity): max_samples: int, min_gradient: float, sample_duration: int, + min_samples: int, ) -> None: """Initialize the sensor.""" self._hass = hass @@ -148,6 +168,7 @@ class SensorTrend(BinarySensorEntity, RestoreEntity): self._invert = invert self._sample_duration = sample_duration self._min_gradient = min_gradient + self._min_samples = min_samples self.samples: deque = deque(maxlen=max_samples) @property @@ -210,7 +231,7 @@ class SensorTrend(BinarySensorEntity, RestoreEntity): while self.samples and self.samples[0][0] < cutoff: self.samples.popleft() - if len(self.samples) < 2: + if len(self.samples) < self._min_samples: return # Calculate gradient of linear trend diff --git a/homeassistant/components/trend/const.py b/homeassistant/components/trend/const.py index 6787dc08445f..3d82bfcc6485 100644 --- a/homeassistant/components/trend/const.py +++ b/homeassistant/components/trend/const.py @@ -12,3 +12,4 @@ CONF_INVERT = "invert" CONF_MAX_SAMPLES = "max_samples" CONF_MIN_GRADIENT = "min_gradient" CONF_SAMPLE_DURATION = "sample_duration" +CONF_MIN_SAMPLES = "min_samples" diff --git a/homeassistant/components/vesync/const.py b/homeassistant/components/vesync/const.py index a0e5b9da52e2..b2fd090e7810 100644 --- a/homeassistant/components/vesync/const.py +++ b/homeassistant/components/vesync/const.py @@ -18,9 +18,21 @@ DEV_TYPE_TO_HA = { "ESWL01": "switch", "ESWL03": "switch", "ESO15-TB": "outlet", + "LV-PUR131S": "fan", + "Core200S": "fan", + "Core300S": "fan", + "Core400S": "fan", + "Core600S": "fan", + "Vital200S": "fan", + "Vital100S": "fan", + "ESD16": "walldimmer", + "ESWD16": "walldimmer", + "ESL100": "bulb-dimmable", + "ESL100CW": "bulb-tunable-white", } SKU_TO_BASE_DEVICE = { + # Air Purifiers "LV-PUR131S": "LV-PUR131S", "LV-RH131S": "LV-PUR131S", # Alt ID Model LV-PUR131S "Core200S": "Core200S", diff --git a/homeassistant/components/vesync/fan.py b/homeassistant/components/vesync/fan.py index 326e7daf12cf..22983054dc94 100644 --- a/homeassistant/components/vesync/fan.py +++ b/homeassistant/components/vesync/fan.py @@ -17,20 +17,10 @@ from homeassistant.util.percentage import ( ) from .common import VeSyncDevice -from .const import DOMAIN, SKU_TO_BASE_DEVICE, VS_DISCOVERY, VS_FANS +from .const import DEV_TYPE_TO_HA, DOMAIN, SKU_TO_BASE_DEVICE, VS_DISCOVERY, VS_FANS _LOGGER = logging.getLogger(__name__) -DEV_TYPE_TO_HA = { - "LV-PUR131S": "fan", - "Core200S": "fan", - "Core300S": "fan", - "Core400S": "fan", - "Core600S": "fan", - "Vital200S": "fan", - "Vital100S": "fan", -} - FAN_MODE_AUTO = "auto" FAN_MODE_SLEEP = "sleep" FAN_MODE_PET = "pet" diff --git a/homeassistant/components/vesync/light.py b/homeassistant/components/vesync/light.py index e6cc979e8082..040e9d5696dc 100644 --- a/homeassistant/components/vesync/light.py +++ b/homeassistant/components/vesync/light.py @@ -14,17 +14,10 @@ from homeassistant.helpers.dispatcher import async_dispatcher_connect from homeassistant.helpers.entity_platform import AddEntitiesCallback from .common import VeSyncDevice -from .const import DOMAIN, VS_DISCOVERY, VS_LIGHTS +from .const import DEV_TYPE_TO_HA, DOMAIN, VS_DISCOVERY, VS_LIGHTS _LOGGER = logging.getLogger(__name__) -DEV_TYPE_TO_HA = { - "ESD16": "walldimmer", - "ESWD16": "walldimmer", - "ESL100": "bulb-dimmable", - "ESL100CW": "bulb-tunable-white", -} - async def async_setup_entry( hass: HomeAssistant, diff --git a/homeassistant/components/vesync/manifest.json b/homeassistant/components/vesync/manifest.json index fb892acfd4fa..ff3f56dd1845 100644 --- a/homeassistant/components/vesync/manifest.json +++ b/homeassistant/components/vesync/manifest.json @@ -1,7 +1,7 @@ { "domain": "vesync", "name": "VeSync", - "codeowners": ["@markperdue", "@webdjoe", "@thegardenmonkey"], + "codeowners": ["@markperdue", "@webdjoe", "@thegardenmonkey", "@cdnninja"], "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/vesync", "iot_class": "cloud_polling", diff --git a/homeassistant/components/wirelesstag/__init__.py b/homeassistant/components/wirelesstag/__init__.py index 06fbfa3621ee..cfbdb6bdc92c 100644 --- a/homeassistant/components/wirelesstag/__init__.py +++ b/homeassistant/components/wirelesstag/__init__.py @@ -5,6 +5,7 @@ from requests.exceptions import ConnectTimeout, HTTPError import voluptuous as vol from wirelesstagpy import WirelessTags from wirelesstagpy.exceptions import WirelessTagsException +from wirelesstagpy.sensortag import SensorTag from homeassistant.components import persistent_notification from homeassistant.const import ( @@ -17,6 +18,7 @@ from homeassistant.const import ( UnitOfElectricPotential, ) from homeassistant.core import HomeAssistant +from homeassistant.helpers import entity_registry as er import homeassistant.helpers.config_validation as cv from homeassistant.helpers.dispatcher import dispatcher_send from homeassistant.helpers.entity import Entity @@ -126,6 +128,22 @@ class WirelessTagPlatform: self.api.start_monitoring(push_callback) +def async_migrate_unique_id( + hass: HomeAssistant, tag: SensorTag, domain: str, key: str +) -> None: + """Migrate old unique id to new one with use of tag's uuid.""" + registry = er.async_get(hass) + new_unique_id = f"{tag.uuid}_{key}" + + if registry.async_get_entity_id(domain, DOMAIN, new_unique_id): + return + + old_unique_id = f"{tag.tag_id}_{key}" + if entity_id := registry.async_get_entity_id(domain, DOMAIN, old_unique_id): + _LOGGER.debug("Updating unique id for %s %s", key, entity_id) + registry.async_update_entity(entity_id, new_unique_id=new_unique_id) + + def setup(hass: HomeAssistant, config: ConfigType) -> bool: """Set up the Wireless Sensor Tag component.""" conf = config[DOMAIN] diff --git a/homeassistant/components/wirelesstag/binary_sensor.py b/homeassistant/components/wirelesstag/binary_sensor.py index 711c2987735b..64a1097bcab2 100644 --- a/homeassistant/components/wirelesstag/binary_sensor.py +++ b/homeassistant/components/wirelesstag/binary_sensor.py @@ -4,7 +4,7 @@ from __future__ import annotations import voluptuous as vol from homeassistant.components.binary_sensor import PLATFORM_SCHEMA, BinarySensorEntity -from homeassistant.const import CONF_MONITORED_CONDITIONS, STATE_OFF, STATE_ON +from homeassistant.const import CONF_MONITORED_CONDITIONS, STATE_OFF, STATE_ON, Platform from homeassistant.core import HomeAssistant, callback import homeassistant.helpers.config_validation as cv from homeassistant.helpers.dispatcher import async_dispatcher_connect @@ -15,6 +15,7 @@ from . import ( DOMAIN as WIRELESSTAG_DOMAIN, SIGNAL_BINARY_EVENT_UPDATE, WirelessTagBaseSensor, + async_migrate_unique_id, ) # On means in range, Off means out of range @@ -72,10 +73,10 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( ) -def setup_platform( +async def async_setup_platform( hass: HomeAssistant, config: ConfigType, - add_entities: AddEntitiesCallback, + async_add_entities: AddEntitiesCallback, discovery_info: DiscoveryInfoType | None = None, ) -> None: """Set up the platform for a WirelessTags.""" @@ -87,9 +88,10 @@ def setup_platform( allowed_sensor_types = tag.supported_binary_events_types for sensor_type in config[CONF_MONITORED_CONDITIONS]: if sensor_type in allowed_sensor_types: + async_migrate_unique_id(hass, tag, Platform.BINARY_SENSOR, sensor_type) sensors.append(WirelessTagBinarySensor(platform, tag, sensor_type)) - add_entities(sensors, True) + async_add_entities(sensors, True) class WirelessTagBinarySensor(WirelessTagBaseSensor, BinarySensorEntity): @@ -100,7 +102,7 @@ class WirelessTagBinarySensor(WirelessTagBaseSensor, BinarySensorEntity): super().__init__(api, tag) self._sensor_type = sensor_type self._name = f"{self._tag.name} {self.event.human_readable_name}" - self._attr_unique_id = f"{self.tag_id}_{self._sensor_type}" + self._attr_unique_id = f"{self._uuid}_{self._sensor_type}" async def async_added_to_hass(self) -> None: """Register callbacks.""" diff --git a/homeassistant/components/wirelesstag/sensor.py b/homeassistant/components/wirelesstag/sensor.py index fd9a7898f920..8ae20031723b 100644 --- a/homeassistant/components/wirelesstag/sensor.py +++ b/homeassistant/components/wirelesstag/sensor.py @@ -12,14 +12,19 @@ from homeassistant.components.sensor import ( SensorEntityDescription, SensorStateClass, ) -from homeassistant.const import CONF_MONITORED_CONDITIONS +from homeassistant.const import CONF_MONITORED_CONDITIONS, Platform from homeassistant.core import HomeAssistant, callback import homeassistant.helpers.config_validation as cv from homeassistant.helpers.dispatcher import async_dispatcher_connect from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType -from . import DOMAIN as WIRELESSTAG_DOMAIN, SIGNAL_TAG_UPDATE, WirelessTagBaseSensor +from . import ( + DOMAIN as WIRELESSTAG_DOMAIN, + SIGNAL_TAG_UPDATE, + WirelessTagBaseSensor, + async_migrate_unique_id, +) _LOGGER = logging.getLogger(__name__) @@ -68,10 +73,10 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( ) -def setup_platform( +async def async_setup_platform( hass: HomeAssistant, config: ConfigType, - add_entities: AddEntitiesCallback, + async_add_entities: AddEntitiesCallback, discovery_info: DiscoveryInfoType | None = None, ) -> None: """Set up the sensor platform.""" @@ -83,9 +88,10 @@ def setup_platform( if key not in tag.allowed_sensor_types: continue description = SENSOR_TYPES[key] + async_migrate_unique_id(hass, tag, Platform.SENSOR, description.key) sensors.append(WirelessTagSensor(platform, tag, description)) - add_entities(sensors, True) + async_add_entities(sensors, True) class WirelessTagSensor(WirelessTagBaseSensor, SensorEntity): @@ -100,7 +106,7 @@ class WirelessTagSensor(WirelessTagBaseSensor, SensorEntity): self._sensor_type = description.key self.entity_description = description self._name = self._tag.name - self._attr_unique_id = f"{self.tag_id}_{self._sensor_type}" + self._attr_unique_id = f"{self._uuid}_{self._sensor_type}" # I want to see entity_id as: # sensor.wirelesstag_bedroom_temperature diff --git a/homeassistant/components/wirelesstag/switch.py b/homeassistant/components/wirelesstag/switch.py index df0f72aca186..7f4008623b16 100644 --- a/homeassistant/components/wirelesstag/switch.py +++ b/homeassistant/components/wirelesstag/switch.py @@ -10,13 +10,17 @@ from homeassistant.components.switch import ( SwitchEntity, SwitchEntityDescription, ) -from homeassistant.const import CONF_MONITORED_CONDITIONS +from homeassistant.const import CONF_MONITORED_CONDITIONS, Platform from homeassistant.core import HomeAssistant import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType -from . import DOMAIN as WIRELESSTAG_DOMAIN, WirelessTagBaseSensor +from . import ( + DOMAIN as WIRELESSTAG_DOMAIN, + WirelessTagBaseSensor, + async_migrate_unique_id, +) SWITCH_TYPES: tuple[SwitchEntityDescription, ...] = ( SwitchEntityDescription( @@ -52,10 +56,10 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( ) -def setup_platform( +async def async_setup_platform( hass: HomeAssistant, config: ConfigType, - add_entities: AddEntitiesCallback, + async_add_entities: AddEntitiesCallback, discovery_info: DiscoveryInfoType | None = None, ) -> None: """Set up switches for a Wireless Sensor Tags.""" @@ -63,15 +67,17 @@ def setup_platform( tags = platform.load_tags() monitored_conditions = config[CONF_MONITORED_CONDITIONS] - entities = [ - WirelessTagSwitch(platform, tag, description) - for tag in tags.values() - for description in SWITCH_TYPES - if description.key in monitored_conditions - and description.key in tag.allowed_monitoring_types - ] + entities = [] + for tag in tags.values(): + for description in SWITCH_TYPES: + if ( + description.key in monitored_conditions + and description.key in tag.allowed_monitoring_types + ): + async_migrate_unique_id(hass, tag, Platform.SWITCH, description.key) + entities.append(WirelessTagSwitch(platform, tag, description)) - add_entities(entities, True) + async_add_entities(entities, True) class WirelessTagSwitch(WirelessTagBaseSensor, SwitchEntity): @@ -82,7 +88,7 @@ class WirelessTagSwitch(WirelessTagBaseSensor, SwitchEntity): super().__init__(api, tag) self.entity_description = description self._name = f"{self._tag.name} {description.name}" - self._attr_unique_id = f"{self.tag_id}_{description.key}" + self._attr_unique_id = f"{self._uuid}_{description.key}" def turn_on(self, **kwargs: Any) -> None: """Turn on the switch.""" diff --git a/homeassistant/generated/config_flows.py b/homeassistant/generated/config_flows.py index 57503f0ef323..e83a2a74405d 100644 --- a/homeassistant/generated/config_flows.py +++ b/homeassistant/generated/config_flows.py @@ -149,6 +149,7 @@ FLOWS = { "fitbit", "fivem", "fjaraskupan", + "flexit_bacnet", "flick_electric", "flipr", "flo", @@ -246,7 +247,6 @@ FLOWS = { "kmtronic", "knx", "kodi", - "komfovent", "konnected", "kostal_plenticore", "kraken", diff --git a/homeassistant/generated/integrations.json b/homeassistant/generated/integrations.json index f0af72624f6a..56b0fa4ef9d2 100644 --- a/homeassistant/generated/integrations.json +++ b/homeassistant/generated/integrations.json @@ -1766,9 +1766,20 @@ }, "flexit": { "name": "Flexit", - "integration_type": "hub", - "config_flow": false, - "iot_class": "local_polling" + "integrations": { + "flexit": { + "integration_type": "hub", + "config_flow": false, + "iot_class": "local_polling", + "name": "Flexit" + }, + "flexit_bacnet": { + "integration_type": "device", + "config_flow": true, + "iot_class": "local_polling", + "name": "Flexit Nordic (BACnet)" + } + } }, "flexom": { "name": "Bouygues Flexom", @@ -2881,12 +2892,6 @@ "config_flow": true, "iot_class": "local_push" }, - "komfovent": { - "name": "Komfovent", - "integration_type": "hub", - "config_flow": true, - "iot_class": "local_polling" - }, "konnected": { "name": "Konnected.io", "integration_type": "hub", diff --git a/homeassistant/helpers/schema_config_entry_flow.py b/homeassistant/helpers/schema_config_entry_flow.py index dcf7f07bf6b3..2bbad0ed63af 100644 --- a/homeassistant/helpers/schema_config_entry_flow.py +++ b/homeassistant/helpers/schema_config_entry_flow.py @@ -331,7 +331,12 @@ class SchemaConfigFlowHandler(config_entries.ConfigFlow, ABC): return cls.options_flow is not None @staticmethod - def _async_step(step_id: str) -> Callable: + def _async_step( + step_id: str, + ) -> Callable[ + [SchemaConfigFlowHandler, dict[str, Any] | None], + Coroutine[Any, Any, FlowResult], + ]: """Generate a step handler.""" async def _async_step( @@ -421,7 +426,12 @@ class SchemaOptionsFlowHandler(config_entries.OptionsFlowWithConfigEntry): setattr(self, "async_setup_preview", async_setup_preview) @staticmethod - def _async_step(step_id: str) -> Callable: + def _async_step( + step_id: str, + ) -> Callable[ + [SchemaConfigFlowHandler, dict[str, Any] | None], + Coroutine[Any, Any, FlowResult], + ]: """Generate a step handler.""" async def _async_step( diff --git a/homeassistant/package_constraints.txt b/homeassistant/package_constraints.txt index 75209de59964..a219df6fd982 100644 --- a/homeassistant/package_constraints.txt +++ b/homeassistant/package_constraints.txt @@ -15,7 +15,7 @@ bleak-retry-connector==3.3.0 bleak==0.21.1 bluetooth-adapters==0.16.1 bluetooth-auto-recovery==1.2.3 -bluetooth-data-tools==1.15.0 +bluetooth-data-tools==1.16.0 certifi>=2021.5.30 ciso8601==2.3.0 cryptography==41.0.7 @@ -26,7 +26,7 @@ ha-ffmpeg==3.1.0 hass-nabucasa==0.74.0 hassil==1.5.1 home-assistant-bluetooth==1.10.4 -home-assistant-frontend==20231129.1 +home-assistant-frontend==20231130.0 home-assistant-intents==2023.11.29 httpx==0.25.0 ifaddr==0.2.0 diff --git a/homeassistant/util/color.py b/homeassistant/util/color.py index 8e7fc3dc155d..4520a62a5d8e 100644 --- a/homeassistant/util/color.py +++ b/homeassistant/util/color.py @@ -576,6 +576,18 @@ def _white_levels_to_color_temperature( ), min(255, round(brightness * 255)) +def color_xy_to_temperature(x: float, y: float) -> int: + """Convert an xy color to a color temperature in Kelvin. + + Uses McCamy's approximation (https://doi.org/10.1002/col.5080170211), + close enough for uses between 2000 K and 10000 K. + """ + n = (x - 0.3320) / (0.1858 - y) + CCT = 437 * (n**3) + 3601 * (n**2) + 6861 * n + 5517 + + return int(CCT) + + def _clamp(color_component: float, minimum: float = 0, maximum: float = 255) -> float: """Clamp the given color component value between the given min and max values. diff --git a/homeassistant/util/yaml/loader.py b/homeassistant/util/yaml/loader.py index fbffae448b22..275a51cd760d 100644 --- a/homeassistant/util/yaml/loader.py +++ b/homeassistant/util/yaml/loader.py @@ -23,6 +23,7 @@ except ImportError: ) from homeassistant.exceptions import HomeAssistantError +from homeassistant.helpers.deprecation import deprecated_class from .const import SECRET_YAML from .objects import Input, NodeDictClass, NodeListClass, NodeStrClass @@ -136,6 +137,11 @@ class FastSafeLoader(FastestAvailableSafeLoader, _LoaderMixin): self.secrets = secrets +@deprecated_class("FastSafeLoader") +class SafeLoader(FastSafeLoader): + """Provided for backwards compatibility. Logs when instantiated.""" + + class PythonSafeLoader(yaml.SafeLoader, _LoaderMixin): """Python safe loader.""" @@ -145,6 +151,11 @@ class PythonSafeLoader(yaml.SafeLoader, _LoaderMixin): self.secrets = secrets +@deprecated_class("PythonSafeLoader") +class SafeLineLoader(PythonSafeLoader): + """Provided for backwards compatibility. Logs when instantiated.""" + + LoaderType = FastSafeLoader | PythonSafeLoader diff --git a/homeassistant/util/yaml/objects.py b/homeassistant/util/yaml/objects.py index b2320a74d2c2..6aedc85cf607 100644 --- a/homeassistant/util/yaml/objects.py +++ b/homeassistant/util/yaml/objects.py @@ -2,7 +2,10 @@ from __future__ import annotations from dataclasses import dataclass +from typing import Any +import voluptuous as vol +from voluptuous.schema_builder import _compile_scalar import yaml @@ -13,6 +16,10 @@ class NodeListClass(list): class NodeStrClass(str): """Wrapper class to be able to add attributes on a string.""" + def __voluptuous_compile__(self, schema: vol.Schema) -> Any: + """Needed because vol.Schema.compile does not handle str subclasses.""" + return _compile_scalar(self) + class NodeDictClass(dict): """Wrapper class to be able to add attributes on a dict.""" diff --git a/mypy.ini b/mypy.ini index a27282fc667e..05525d033007 100644 --- a/mypy.ini +++ b/mypy.ini @@ -1041,6 +1041,16 @@ disallow_untyped_defs = true warn_return_any = true warn_unreachable = true +[mypy-homeassistant.components.flexit_bacnet.*] +check_untyped_defs = true +disallow_incomplete_defs = true +disallow_subclassing_any = true +disallow_untyped_calls = true +disallow_untyped_decorators = true +disallow_untyped_defs = true +warn_return_any = true +warn_unreachable = true + [mypy-homeassistant.components.flux_led.*] check_untyped_defs = true disallow_incomplete_defs = true diff --git a/requirements_all.txt b/requirements_all.txt index de84d8fb4209..f72fd5c18983 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -564,7 +564,7 @@ bluetooth-auto-recovery==1.2.3 # homeassistant.components.ld2410_ble # homeassistant.components.led_ble # homeassistant.components.private_ble_device -bluetooth-data-tools==1.15.0 +bluetooth-data-tools==1.16.0 # homeassistant.components.bond bond-async==0.2.1 @@ -789,7 +789,7 @@ eufylife-ble-client==0.1.8 # evdev==1.6.1 # homeassistant.components.evohome -evohome-async==0.4.6 +evohome-async==0.4.9 # homeassistant.components.faa_delays faadelays==2023.9.1 @@ -822,6 +822,9 @@ fixerio==1.0.0a0 # homeassistant.components.fjaraskupan fjaraskupan==2.2.0 +# homeassistant.components.flexit_bacnet +flexit_bacnet==2.1.0 + # homeassistant.components.flipr flipr-api==1.5.0 @@ -1014,7 +1017,7 @@ hole==0.8.0 holidays==0.36 # homeassistant.components.frontend -home-assistant-frontend==20231129.1 +home-assistant-frontend==20231130.0 # homeassistant.components.conversation home-assistant-intents==2023.11.29 @@ -1128,9 +1131,6 @@ kiwiki-client==0.1.1 # homeassistant.components.knx knx-frontend==2023.6.23.191712 -# homeassistant.components.komfovent -komfovent-api==0.0.3 - # homeassistant.components.konnected konnected==1.2.0 @@ -1249,7 +1249,7 @@ micloud==0.5 mill-local==0.3.0 # homeassistant.components.mill -millheater==0.11.6 +millheater==0.11.7 # homeassistant.components.minio minio==7.1.12 @@ -2169,7 +2169,7 @@ python-kasa[speedups]==0.5.4 # python-lirc==1.2.3 # homeassistant.components.matter -python-matter-server==4.0.2 +python-matter-server==5.0.0 # homeassistant.components.xiaomi_miio python-miio==0.5.12 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index f2241b1c5d4d..5672c4a82596 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -476,7 +476,7 @@ bluetooth-auto-recovery==1.2.3 # homeassistant.components.ld2410_ble # homeassistant.components.led_ble # homeassistant.components.private_ble_device -bluetooth-data-tools==1.15.0 +bluetooth-data-tools==1.16.0 # homeassistant.components.bond bond-async==0.2.1 @@ -651,6 +651,9 @@ fivem-api==0.1.2 # homeassistant.components.fjaraskupan fjaraskupan==2.2.0 +# homeassistant.components.flexit_bacnet +flexit_bacnet==2.1.0 + # homeassistant.components.flipr flipr-api==1.5.0 @@ -801,7 +804,7 @@ hole==0.8.0 holidays==0.36 # homeassistant.components.frontend -home-assistant-frontend==20231129.1 +home-assistant-frontend==20231130.0 # homeassistant.components.conversation home-assistant-intents==2023.11.29 @@ -885,9 +888,6 @@ kegtron-ble==0.4.0 # homeassistant.components.knx knx-frontend==2023.6.23.191712 -# homeassistant.components.komfovent -komfovent-api==0.0.3 - # homeassistant.components.konnected konnected==1.2.0 @@ -973,7 +973,7 @@ micloud==0.5 mill-local==0.3.0 # homeassistant.components.mill -millheater==0.11.6 +millheater==0.11.7 # homeassistant.components.minio minio==7.1.12 @@ -1623,7 +1623,7 @@ python-juicenet==1.1.0 python-kasa[speedups]==0.5.4 # homeassistant.components.matter -python-matter-server==4.0.2 +python-matter-server==5.0.0 # homeassistant.components.xiaomi_miio python-miio==0.5.12 diff --git a/tests/components/blink/test_services.py b/tests/components/blink/test_services.py index 438b47f38c56..ccc326dac1f6 100644 --- a/tests/components/blink/test_services.py +++ b/tests/components/blink/test_services.py @@ -19,7 +19,7 @@ from homeassistant.const import ( CONF_PIN, ) from homeassistant.core import HomeAssistant -from homeassistant.exceptions import HomeAssistantError +from homeassistant.exceptions import HomeAssistantError, ServiceValidationError from homeassistant.helpers import device_registry as dr from tests.common import MockConfigEntry @@ -58,7 +58,7 @@ async def test_refresh_service_calls( assert mock_blink_api.refresh.call_count == 2 - with pytest.raises(HomeAssistantError) as execinfo: + with pytest.raises(HomeAssistantError): await hass.services.async_call( DOMAIN, SERVICE_REFRESH, @@ -66,8 +66,6 @@ async def test_refresh_service_calls( blocking=True, ) - assert "Device 'bad-device_id' not found in device registry" in str(execinfo) - async def test_video_service_calls( hass: HomeAssistant, @@ -90,18 +88,17 @@ async def test_video_service_calls( assert mock_config_entry.state is ConfigEntryState.LOADED assert mock_blink_api.refresh.call_count == 1 - caplog.clear() - await hass.services.async_call( - DOMAIN, - SERVICE_SAVE_VIDEO, - { - ATTR_DEVICE_ID: [device_entry.id], - CONF_NAME: CAMERA_NAME, - CONF_FILENAME: FILENAME, - }, - blocking=True, - ) - assert "no access to path!" in caplog.text + with pytest.raises(HomeAssistantError): + await hass.services.async_call( + DOMAIN, + SERVICE_SAVE_VIDEO, + { + ATTR_DEVICE_ID: [device_entry.id], + CONF_NAME: CAMERA_NAME, + CONF_FILENAME: FILENAME, + }, + blocking=True, + ) hass.config.is_allowed_path = Mock(return_value=True) caplog.clear() @@ -118,7 +115,7 @@ async def test_video_service_calls( ) mock_blink_api.cameras[CAMERA_NAME].video_to_file.assert_awaited_once() - with pytest.raises(HomeAssistantError) as execinfo: + with pytest.raises(HomeAssistantError): await hass.services.async_call( DOMAIN, SERVICE_SAVE_VIDEO, @@ -130,22 +127,19 @@ async def test_video_service_calls( blocking=True, ) - assert "Device 'bad-device_id' not found in device registry" in str(execinfo) - mock_blink_api.cameras[CAMERA_NAME].video_to_file = AsyncMock(side_effect=OSError) - caplog.clear() - await hass.services.async_call( - DOMAIN, - SERVICE_SAVE_VIDEO, - { - ATTR_DEVICE_ID: [device_entry.id], - CONF_NAME: CAMERA_NAME, - CONF_FILENAME: FILENAME, - }, - blocking=True, - ) - assert "Can't write image" in caplog.text + with pytest.raises(ServiceValidationError): + await hass.services.async_call( + DOMAIN, + SERVICE_SAVE_VIDEO, + { + ATTR_DEVICE_ID: [device_entry.id], + CONF_NAME: CAMERA_NAME, + CONF_FILENAME: FILENAME, + }, + blocking=True, + ) hass.config.is_allowed_path = Mock(return_value=False) @@ -171,18 +165,17 @@ async def test_picture_service_calls( assert mock_config_entry.state is ConfigEntryState.LOADED assert mock_blink_api.refresh.call_count == 1 - caplog.clear() - await hass.services.async_call( - DOMAIN, - SERVICE_SAVE_RECENT_CLIPS, - { - ATTR_DEVICE_ID: [device_entry.id], - CONF_NAME: CAMERA_NAME, - CONF_FILE_PATH: FILENAME, - }, - blocking=True, - ) - assert "no access to path!" in caplog.text + with pytest.raises(ServiceValidationError): + await hass.services.async_call( + DOMAIN, + SERVICE_SAVE_RECENT_CLIPS, + { + ATTR_DEVICE_ID: [device_entry.id], + CONF_NAME: CAMERA_NAME, + CONF_FILE_PATH: FILENAME, + }, + blocking=True, + ) hass.config.is_allowed_path = Mock(return_value=True) mock_blink_api.cameras = {CAMERA_NAME: AsyncMock()} @@ -202,21 +195,20 @@ async def test_picture_service_calls( mock_blink_api.cameras[CAMERA_NAME].save_recent_clips = AsyncMock( side_effect=OSError ) - caplog.clear() - await hass.services.async_call( - DOMAIN, - SERVICE_SAVE_RECENT_CLIPS, - { - ATTR_DEVICE_ID: [device_entry.id], - CONF_NAME: CAMERA_NAME, - CONF_FILE_PATH: FILENAME, - }, - blocking=True, - ) - assert "Can't write recent clips to directory" in caplog.text + with pytest.raises(ServiceValidationError): + await hass.services.async_call( + DOMAIN, + SERVICE_SAVE_RECENT_CLIPS, + { + ATTR_DEVICE_ID: [device_entry.id], + CONF_NAME: CAMERA_NAME, + CONF_FILE_PATH: FILENAME, + }, + blocking=True, + ) - with pytest.raises(HomeAssistantError) as execinfo: + with pytest.raises(HomeAssistantError): await hass.services.async_call( DOMAIN, SERVICE_SAVE_RECENT_CLIPS, @@ -228,8 +220,6 @@ async def test_picture_service_calls( blocking=True, ) - assert "Device 'bad-device_id' not found in device registry" in str(execinfo) - async def test_pin_service_calls( hass: HomeAssistant, @@ -259,7 +249,7 @@ async def test_pin_service_calls( ) assert mock_blink_api.auth.send_auth_key.assert_awaited_once - with pytest.raises(HomeAssistantError) as execinfo: + with pytest.raises(HomeAssistantError): await hass.services.async_call( DOMAIN, SERVICE_SEND_PIN, @@ -267,8 +257,6 @@ async def test_pin_service_calls( blocking=True, ) - assert "Device 'bad-device_id' not found in device registry" in str(execinfo) - @pytest.mark.parametrize( ("service", "params"), @@ -325,7 +313,7 @@ async def test_service_called_with_non_blink_device( parameters = {ATTR_DEVICE_ID: [device_entry.id]} parameters.update(params) - with pytest.raises(HomeAssistantError) as execinfo: + with pytest.raises(ServiceValidationError): await hass.services.async_call( DOMAIN, service, @@ -333,8 +321,6 @@ async def test_service_called_with_non_blink_device( blocking=True, ) - assert f"Device '{device_entry.id}' is not a blink device" in str(execinfo) - @pytest.mark.parametrize( ("service", "params"), @@ -382,12 +368,10 @@ async def test_service_called_with_unloaded_entry( parameters = {ATTR_DEVICE_ID: [device_entry.id]} parameters.update(params) - with pytest.raises(HomeAssistantError) as execinfo: + with pytest.raises(HomeAssistantError): await hass.services.async_call( DOMAIN, service, parameters, blocking=True, ) - - assert "Mock Title is not loaded" in str(execinfo) diff --git a/tests/components/conftest.py b/tests/components/conftest.py index c985565b1bea..1ebcc864b4b8 100644 --- a/tests/components/conftest.py +++ b/tests/components/conftest.py @@ -16,7 +16,7 @@ def patch_zeroconf_multiple_catcher() -> Generator[None, None, None]: yield -@pytest.fixture(autouse=True) +@pytest.fixture(scope="session", autouse=True) def prevent_io() -> Generator[None, None, None]: """Fixture to prevent certain I/O from happening.""" with patch( diff --git a/tests/components/dsmr/test_sensor.py b/tests/components/dsmr/test_sensor.py index 1b7f8efb201a..0c71525be48b 100644 --- a/tests/components/dsmr/test_sensor.py +++ b/tests/components/dsmr/test_sensor.py @@ -10,6 +10,8 @@ from decimal import Decimal from itertools import chain, repeat from unittest.mock import DEFAULT, MagicMock +import pytest + from homeassistant import config_entries from homeassistant.components.sensor import ( ATTR_OPTIONS, @@ -22,6 +24,7 @@ from homeassistant.const import ( ATTR_FRIENDLY_NAME, ATTR_ICON, ATTR_UNIT_OF_MEASUREMENT, + STATE_UNKNOWN, UnitOfEnergy, UnitOfPower, UnitOfVolume, @@ -308,7 +311,17 @@ async def test_v4_meter(hass: HomeAssistant, dsmr_connection_fixture) -> None: ) -async def test_v5_meter(hass: HomeAssistant, dsmr_connection_fixture) -> None: +@pytest.mark.parametrize( + ("value", "state"), + [ + (Decimal(745.690), "745.69"), + (Decimal(745.695), "745.695"), + (Decimal(0.000), STATE_UNKNOWN), + ], +) +async def test_v5_meter( + hass: HomeAssistant, dsmr_connection_fixture, value: Decimal, state: str +) -> None: """Test if v5 meter is correctly parsed.""" (connection_factory, transport, protocol) = dsmr_connection_fixture @@ -335,7 +348,7 @@ async def test_v5_meter(hass: HomeAssistant, dsmr_connection_fixture) -> None: HOURLY_GAS_METER_READING, [ {"value": datetime.datetime.fromtimestamp(1551642213)}, - {"value": Decimal(745.695), "unit": "m3"}, + {"value": value, "unit": "m3"}, ], ), ELECTRICITY_ACTIVE_TARIFF: CosemObject( @@ -371,7 +384,7 @@ async def test_v5_meter(hass: HomeAssistant, dsmr_connection_fixture) -> None: # check if gas consumption is parsed correctly gas_consumption = hass.states.get("sensor.gas_meter_gas_consumption") - assert gas_consumption.state == "745.695" + assert gas_consumption.state == state assert gas_consumption.attributes.get(ATTR_DEVICE_CLASS) == SensorDeviceClass.GAS assert ( gas_consumption.attributes.get(ATTR_STATE_CLASS) diff --git a/tests/components/flexit_bacnet/__init__.py b/tests/components/flexit_bacnet/__init__.py new file mode 100644 index 000000000000..4cae6e4f4bfe --- /dev/null +++ b/tests/components/flexit_bacnet/__init__.py @@ -0,0 +1 @@ +"""Tests for the Flexit Nordic (BACnet) integration.""" diff --git a/tests/components/flexit_bacnet/conftest.py b/tests/components/flexit_bacnet/conftest.py new file mode 100644 index 000000000000..b136b134e018 --- /dev/null +++ b/tests/components/flexit_bacnet/conftest.py @@ -0,0 +1,44 @@ +"""Configuration for Flexit Nordic (BACnet) tests.""" +from unittest.mock import patch + +import pytest + +from homeassistant import config_entries +from homeassistant.components.flexit_bacnet.const import DOMAIN +from homeassistant.core import HomeAssistant +from homeassistant.data_entry_flow import FlowResultType + + +@pytest.fixture +async def flow_id(hass: HomeAssistant) -> str: + """Return initial ID for user-initiated configuration flow.""" + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": config_entries.SOURCE_USER} + ) + + assert result["type"] == FlowResultType.FORM + assert result["errors"] == {} + + return result["flow_id"] + + +@pytest.fixture(autouse=True) +def mock_serial_number_and_device_name(): + """Mock serial number of the device.""" + with patch( + "homeassistant.components.flexit_bacnet.config_flow.FlexitBACnet.serial_number", + "0000-0001", + ), patch( + "homeassistant.components.flexit_bacnet.config_flow.FlexitBACnet.device_name", + "Device Name", + ): + yield + + +@pytest.fixture +def mock_setup_entry(): + """Mock setting up a config entry.""" + with patch( + "homeassistant.components.flexit_bacnet.async_setup_entry", return_value=True + ) as setup_entry_mock: + yield setup_entry_mock diff --git a/tests/components/flexit_bacnet/test_config_flow.py b/tests/components/flexit_bacnet/test_config_flow.py new file mode 100644 index 000000000000..ed513587af6f --- /dev/null +++ b/tests/components/flexit_bacnet/test_config_flow.py @@ -0,0 +1,120 @@ +"""Test the Flexit Nordic (BACnet) config flow.""" +import asyncio.exceptions +from unittest.mock import patch + +from flexit_bacnet import DecodingError +import pytest + +from homeassistant.components.flexit_bacnet.const import DOMAIN +from homeassistant.const import CONF_DEVICE_ID, CONF_IP_ADDRESS +from homeassistant.core import HomeAssistant +from homeassistant.data_entry_flow import FlowResultType + +from tests.common import MockConfigEntry + + +async def test_form(hass: HomeAssistant, flow_id: str, mock_setup_entry) -> None: + """Test we get the form and the happy path works.""" + with patch( + "homeassistant.components.flexit_bacnet.config_flow.FlexitBACnet.update" + ): + result = await hass.config_entries.flow.async_configure( + flow_id, + { + CONF_IP_ADDRESS: "1.1.1.1", + CONF_DEVICE_ID: 2, + }, + ) + await hass.async_block_till_done() + + assert result["type"] == FlowResultType.CREATE_ENTRY + assert result["title"] == "Device Name" + assert result["context"]["unique_id"] == "0000-0001" + assert result["data"] == { + CONF_IP_ADDRESS: "1.1.1.1", + CONF_DEVICE_ID: 2, + } + assert len(mock_setup_entry.mock_calls) == 1 + + +@pytest.mark.parametrize( + ("error", "message"), + [ + ( + asyncio.exceptions.TimeoutError, + "cannot_connect", + ), + (ConnectionError, "cannot_connect"), + (DecodingError, "cannot_connect"), + (Exception(), "unknown"), + ], +) +async def test_flow_fails( + hass: HomeAssistant, flow_id: str, error: Exception, message: str, mock_setup_entry +) -> None: + """Test that we return 'cannot_connect' error when attempting to connect to an incorrect IP address. + + The flexit_bacnet library raises asyncio.exceptions.TimeoutError in that scenario. + """ + with patch( + "homeassistant.components.flexit_bacnet.config_flow.FlexitBACnet.update", + side_effect=error, + ): + result = await hass.config_entries.flow.async_configure( + flow_id, + { + CONF_IP_ADDRESS: "1.1.1.1", + CONF_DEVICE_ID: 2, + }, + ) + + assert result["type"] == FlowResultType.FORM + assert result["errors"] == {"base": message} + assert len(mock_setup_entry.mock_calls) == 0 + + # ensure that user can recover from this error + with patch( + "homeassistant.components.flexit_bacnet.config_flow.FlexitBACnet.update" + ): + result2 = await hass.config_entries.flow.async_configure( + result["flow_id"], + { + CONF_IP_ADDRESS: "1.1.1.1", + CONF_DEVICE_ID: 2, + }, + ) + + assert result2["type"] == FlowResultType.CREATE_ENTRY + assert result2["title"] == "Device Name" + assert result2["context"]["unique_id"] == "0000-0001" + assert result2["data"] == { + CONF_IP_ADDRESS: "1.1.1.1", + CONF_DEVICE_ID: 2, + } + assert len(mock_setup_entry.mock_calls) == 1 + + +async def test_form_device_already_exist(hass: HomeAssistant, flow_id: str) -> None: + """Test that we cannot add already added device.""" + entry = MockConfigEntry( + domain=DOMAIN, + data={ + CONF_IP_ADDRESS: "1.1.1.1", + CONF_DEVICE_ID: 2, + }, + unique_id="0000-0001", + ) + entry.add_to_hass(hass) + with patch( + "homeassistant.components.flexit_bacnet.config_flow.FlexitBACnet.update" + ): + result = await hass.config_entries.flow.async_configure( + flow_id, + { + CONF_IP_ADDRESS: "1.1.1.1", + CONF_DEVICE_ID: 2, + }, + ) + await hass.async_block_till_done() + assert result["type"] == FlowResultType.ABORT + assert result["reason"] == "already_configured" diff --git a/tests/components/google_assistant/test_helpers.py b/tests/components/google_assistant/test_helpers.py index 771df137278f..aaa3949caaf9 100644 --- a/tests/components/google_assistant/test_helpers.py +++ b/tests/components/google_assistant/test_helpers.py @@ -89,6 +89,7 @@ async def test_google_entity_sync_serialize_with_matter( manufacturer="Someone", model="Some model", sw_version="Some Version", + identifiers={("matter", "12345678")}, connections={(dr.CONNECTION_NETWORK_MAC, "12:34:56:AB:CD:EF")}, ) entity = entity_registry.async_get_or_create( diff --git a/tests/components/homewizard/conftest.py b/tests/components/homewizard/conftest.py index e778c82928bb..0c24d9daebe3 100644 --- a/tests/components/homewizard/conftest.py +++ b/tests/components/homewizard/conftest.py @@ -1,6 +1,5 @@ """Fixtures for HomeWizard integration tests.""" from collections.abc import Generator -import json from unittest.mock import AsyncMock, MagicMock, patch from homewizard_energy.errors import NotFoundError @@ -11,7 +10,7 @@ from homeassistant.components.homewizard.const import DOMAIN from homeassistant.const import CONF_IP_ADDRESS from homeassistant.core import HomeAssistant -from tests.common import MockConfigEntry, get_fixture_path, load_fixture +from tests.common import MockConfigEntry, get_fixture_path, load_json_object_fixture @pytest.fixture @@ -35,22 +34,22 @@ def mock_homewizardenergy( client = homewizard.return_value client.device.return_value = Device.from_dict( - json.loads(load_fixture(f"{device_fixture}/device.json", DOMAIN)) + load_json_object_fixture(f"{device_fixture}/device.json", DOMAIN) ) client.data.return_value = Data.from_dict( - json.loads(load_fixture(f"{device_fixture}/data.json", DOMAIN)) + load_json_object_fixture(f"{device_fixture}/data.json", DOMAIN) ) if get_fixture_path(f"{device_fixture}/state.json", DOMAIN).exists(): client.state.return_value = State.from_dict( - json.loads(load_fixture(f"{device_fixture}/state.json", DOMAIN)) + load_json_object_fixture(f"{device_fixture}/state.json", DOMAIN) ) else: client.state.side_effect = NotFoundError if get_fixture_path(f"{device_fixture}/system.json", DOMAIN).exists(): client.system.return_value = System.from_dict( - json.loads(load_fixture(f"{device_fixture}/system.json", DOMAIN)) + load_json_object_fixture(f"{device_fixture}/system.json", DOMAIN) ) else: client.system.side_effect = NotFoundError diff --git a/tests/components/komfovent/__init__.py b/tests/components/komfovent/__init__.py deleted file mode 100644 index e5492a52327b..000000000000 --- a/tests/components/komfovent/__init__.py +++ /dev/null @@ -1 +0,0 @@ -"""Tests for the Komfovent integration.""" diff --git a/tests/components/komfovent/conftest.py b/tests/components/komfovent/conftest.py deleted file mode 100644 index d9cb0950c74f..000000000000 --- a/tests/components/komfovent/conftest.py +++ /dev/null @@ -1,14 +0,0 @@ -"""Common fixtures for the Komfovent tests.""" -from collections.abc import Generator -from unittest.mock import AsyncMock, patch - -import pytest - - -@pytest.fixture -def mock_setup_entry() -> Generator[AsyncMock, None, None]: - """Override async_setup_entry.""" - with patch( - "homeassistant.components.komfovent.async_setup_entry", return_value=True - ) as mock_setup_entry: - yield mock_setup_entry diff --git a/tests/components/komfovent/test_config_flow.py b/tests/components/komfovent/test_config_flow.py deleted file mode 100644 index 008d92e36a3f..000000000000 --- a/tests/components/komfovent/test_config_flow.py +++ /dev/null @@ -1,189 +0,0 @@ -"""Test the Komfovent config flow.""" -from unittest.mock import AsyncMock, patch - -import komfovent_api -import pytest - -from homeassistant import config_entries -from homeassistant.components.komfovent.const import DOMAIN -from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_USERNAME -from homeassistant.core import HomeAssistant -from homeassistant.data_entry_flow import FlowResult, FlowResultType - -from tests.common import MockConfigEntry - - -async def test_form(hass: HomeAssistant, mock_setup_entry: AsyncMock) -> None: - """Test flow completes as expected.""" - result = await hass.config_entries.flow.async_init( - DOMAIN, context={"source": config_entries.SOURCE_USER} - ) - assert result["type"] == FlowResultType.FORM - assert result["errors"] is None - - final_result = await __test_normal_flow(hass, result["flow_id"]) - assert final_result["type"] == FlowResultType.CREATE_ENTRY - assert final_result["title"] == "test-name" - assert final_result["data"] == { - CONF_HOST: "1.1.1.1", - CONF_USERNAME: "test-username", - CONF_PASSWORD: "test-password", - } - assert len(mock_setup_entry.mock_calls) == 1 - - -@pytest.mark.parametrize( - ("error", "expected_response"), - [ - (komfovent_api.KomfoventConnectionResult.NOT_FOUND, "cannot_connect"), - (komfovent_api.KomfoventConnectionResult.UNAUTHORISED, "invalid_auth"), - (komfovent_api.KomfoventConnectionResult.INVALID_INPUT, "invalid_input"), - ], -) -async def test_flow_error_authenticating( - hass: HomeAssistant, - mock_setup_entry: AsyncMock, - error: komfovent_api.KomfoventConnectionResult, - expected_response: str, -) -> None: - """Test errors during flow authentication step are handled and dont affect final result.""" - result = await hass.config_entries.flow.async_init( - DOMAIN, context={"source": config_entries.SOURCE_USER} - ) - with patch( - "homeassistant.components.komfovent.config_flow.komfovent_api.get_credentials", - return_value=( - error, - None, - ), - ): - result2 = await hass.config_entries.flow.async_configure( - result["flow_id"], - { - CONF_HOST: "1.1.1.1", - CONF_USERNAME: "test-username", - CONF_PASSWORD: "test-password", - }, - ) - - assert result2["type"] == FlowResultType.FORM - assert result2["errors"] == {"base": expected_response} - - final_result = await __test_normal_flow(hass, result2["flow_id"]) - assert final_result["type"] == FlowResultType.CREATE_ENTRY - assert final_result["title"] == "test-name" - assert final_result["data"] == { - CONF_HOST: "1.1.1.1", - CONF_USERNAME: "test-username", - CONF_PASSWORD: "test-password", - } - assert len(mock_setup_entry.mock_calls) == 1 - - -@pytest.mark.parametrize( - ("error", "expected_response"), - [ - (komfovent_api.KomfoventConnectionResult.NOT_FOUND, "cannot_connect"), - (komfovent_api.KomfoventConnectionResult.UNAUTHORISED, "invalid_auth"), - (komfovent_api.KomfoventConnectionResult.INVALID_INPUT, "invalid_input"), - ], -) -async def test_flow_error_device_info( - hass: HomeAssistant, - mock_setup_entry: AsyncMock, - error: komfovent_api.KomfoventConnectionResult, - expected_response: str, -) -> None: - """Test errors during flow device info download step are handled and dont affect final result.""" - result = await hass.config_entries.flow.async_init( - DOMAIN, context={"source": config_entries.SOURCE_USER} - ) - with patch( - "homeassistant.components.komfovent.config_flow.komfovent_api.get_credentials", - return_value=( - komfovent_api.KomfoventConnectionResult.SUCCESS, - komfovent_api.KomfoventCredentials("1.1.1.1", "user", "pass"), - ), - ), patch( - "homeassistant.components.komfovent.config_flow.komfovent_api.get_settings", - return_value=( - error, - None, - ), - ): - result2 = await hass.config_entries.flow.async_configure( - result["flow_id"], - { - CONF_HOST: "1.1.1.1", - CONF_USERNAME: "test-username", - CONF_PASSWORD: "test-password", - }, - ) - - assert result2["type"] == FlowResultType.FORM - assert result2["errors"] == {"base": expected_response} - - final_result = await __test_normal_flow(hass, result2["flow_id"]) - assert final_result["type"] == FlowResultType.CREATE_ENTRY - assert final_result["title"] == "test-name" - assert final_result["data"] == { - CONF_HOST: "1.1.1.1", - CONF_USERNAME: "test-username", - CONF_PASSWORD: "test-password", - } - assert len(mock_setup_entry.mock_calls) == 1 - - -async def test_device_already_exists( - hass: HomeAssistant, mock_setup_entry: AsyncMock -) -> None: - """Test device is not added when it already exists.""" - entry = MockConfigEntry( - domain=DOMAIN, - data={ - CONF_HOST: "1.1.1.1", - CONF_USERNAME: "test-username", - CONF_PASSWORD: "test-password", - }, - unique_id="test-uid", - ) - entry.add_to_hass(hass) - - result = await hass.config_entries.flow.async_init( - DOMAIN, context={"source": config_entries.SOURCE_USER} - ) - assert result["type"] == FlowResultType.FORM - assert result["errors"] is None - - final_result = await __test_normal_flow(hass, result["flow_id"]) - assert final_result["type"] == FlowResultType.ABORT - assert final_result["reason"] == "already_configured" - - -async def __test_normal_flow(hass: HomeAssistant, flow_id: str) -> FlowResult: - """Test flow completing as expected, no matter what happened before.""" - - with patch( - "homeassistant.components.komfovent.config_flow.komfovent_api.get_credentials", - return_value=( - komfovent_api.KomfoventConnectionResult.SUCCESS, - komfovent_api.KomfoventCredentials("1.1.1.1", "user", "pass"), - ), - ), patch( - "homeassistant.components.komfovent.config_flow.komfovent_api.get_settings", - return_value=( - komfovent_api.KomfoventConnectionResult.SUCCESS, - komfovent_api.KomfoventSettings("test-name", None, None, "test-uid"), - ), - ): - final_result = await hass.config_entries.flow.async_configure( - flow_id, - { - CONF_HOST: "1.1.1.1", - CONF_USERNAME: "test-username", - CONF_PASSWORD: "test-password", - }, - ) - await hass.async_block_till_done() - - return final_result diff --git a/tests/components/light/test_init.py b/tests/components/light/test_init.py index 675057899b02..962c5500f062 100644 --- a/tests/components/light/test_init.py +++ b/tests/components/light/test_init.py @@ -1444,6 +1444,7 @@ async def test_light_service_call_color_conversion( platform.ENTITIES.append(platform.MockLight("Test_legacy", STATE_ON)) platform.ENTITIES.append(platform.MockLight("Test_rgbw", STATE_ON)) platform.ENTITIES.append(platform.MockLight("Test_rgbww", STATE_ON)) + platform.ENTITIES.append(platform.MockLight("Test_temperature", STATE_ON)) entity0 = platform.ENTITIES[0] entity0.supported_color_modes = {light.ColorMode.HS} @@ -1470,6 +1471,9 @@ async def test_light_service_call_color_conversion( entity6 = platform.ENTITIES[6] entity6.supported_color_modes = {light.ColorMode.RGBWW} + entity7 = platform.ENTITIES[7] + entity7.supported_color_modes = {light.ColorMode.COLOR_TEMP} + assert await async_setup_component(hass, "light", {"light": {"platform": "test"}}) await hass.async_block_till_done() @@ -1498,6 +1502,9 @@ async def test_light_service_call_color_conversion( state = hass.states.get(entity6.entity_id) assert state.attributes["supported_color_modes"] == [light.ColorMode.RGBWW] + state = hass.states.get(entity7.entity_id) + assert state.attributes["supported_color_modes"] == [light.ColorMode.COLOR_TEMP] + await hass.services.async_call( "light", "turn_on", @@ -1510,6 +1517,7 @@ async def test_light_service_call_color_conversion( entity4.entity_id, entity5.entity_id, entity6.entity_id, + entity7.entity_id, ], "brightness_pct": 100, "hs_color": (240, 100), @@ -1530,6 +1538,8 @@ async def test_light_service_call_color_conversion( assert data == {"brightness": 255, "rgbw_color": (0, 0, 255, 0)} _, data = entity6.last_call("turn_on") assert data == {"brightness": 255, "rgbww_color": (0, 0, 255, 0, 0)} + _, data = entity7.last_call("turn_on") + assert data == {"brightness": 255, "color_temp_kelvin": 1739, "color_temp": 575} await hass.services.async_call( "light", @@ -1543,6 +1553,7 @@ async def test_light_service_call_color_conversion( entity4.entity_id, entity5.entity_id, entity6.entity_id, + entity7.entity_id, ], "brightness_pct": 100, "hs_color": (240, 0), @@ -1564,6 +1575,8 @@ async def test_light_service_call_color_conversion( _, data = entity6.last_call("turn_on") # The midpoint of the the white channels is warm, compensated by adding green + blue assert data == {"brightness": 255, "rgbww_color": (0, 76, 141, 255, 255)} + _, data = entity7.last_call("turn_on") + assert data == {"brightness": 255, "color_temp_kelvin": 5962, "color_temp": 167} await hass.services.async_call( "light", @@ -1577,6 +1590,7 @@ async def test_light_service_call_color_conversion( entity4.entity_id, entity5.entity_id, entity6.entity_id, + entity7.entity_id, ], "brightness_pct": 50, "rgb_color": (128, 0, 0), @@ -1597,6 +1611,8 @@ async def test_light_service_call_color_conversion( assert data == {"brightness": 128, "rgbw_color": (128, 0, 0, 0)} _, data = entity6.last_call("turn_on") assert data == {"brightness": 128, "rgbww_color": (128, 0, 0, 0, 0)} + _, data = entity7.last_call("turn_on") + assert data == {"brightness": 128, "color_temp_kelvin": 6279, "color_temp": 159} await hass.services.async_call( "light", @@ -1610,6 +1626,7 @@ async def test_light_service_call_color_conversion( entity4.entity_id, entity5.entity_id, entity6.entity_id, + entity7.entity_id, ], "brightness_pct": 50, "rgb_color": (255, 255, 255), @@ -1631,6 +1648,8 @@ async def test_light_service_call_color_conversion( _, data = entity6.last_call("turn_on") # The midpoint the the white channels is warm, compensated by adding green + blue assert data == {"brightness": 128, "rgbww_color": (0, 76, 141, 255, 255)} + _, data = entity7.last_call("turn_on") + assert data == {"brightness": 128, "color_temp_kelvin": 5962, "color_temp": 167} await hass.services.async_call( "light", @@ -1644,6 +1663,7 @@ async def test_light_service_call_color_conversion( entity4.entity_id, entity5.entity_id, entity6.entity_id, + entity7.entity_id, ], "brightness_pct": 50, "xy_color": (0.1, 0.8), @@ -1664,6 +1684,8 @@ async def test_light_service_call_color_conversion( assert data == {"brightness": 128, "rgbw_color": (0, 255, 22, 0)} _, data = entity6.last_call("turn_on") assert data == {"brightness": 128, "rgbww_color": (0, 255, 22, 0, 0)} + _, data = entity7.last_call("turn_on") + assert data == {"brightness": 128, "color_temp_kelvin": 8645, "color_temp": 115} await hass.services.async_call( "light", @@ -1677,6 +1699,7 @@ async def test_light_service_call_color_conversion( entity4.entity_id, entity5.entity_id, entity6.entity_id, + entity7.entity_id, ], "brightness_pct": 50, "xy_color": (0.323, 0.329), @@ -1698,6 +1721,8 @@ async def test_light_service_call_color_conversion( _, data = entity6.last_call("turn_on") # The midpoint the the white channels is warm, compensated by adding green + blue assert data == {"brightness": 128, "rgbww_color": (0, 75, 140, 255, 255)} + _, data = entity7.last_call("turn_on") + assert data == {"brightness": 128, "color_temp_kelvin": 5962, "color_temp": 167} await hass.services.async_call( "light", @@ -1711,6 +1736,7 @@ async def test_light_service_call_color_conversion( entity4.entity_id, entity5.entity_id, entity6.entity_id, + entity7.entity_id, ], "brightness_pct": 50, "rgbw_color": (128, 0, 0, 64), @@ -1732,6 +1758,8 @@ async def test_light_service_call_color_conversion( _, data = entity6.last_call("turn_on") # The midpoint the the white channels is warm, compensated by adding green + blue assert data == {"brightness": 128, "rgbww_color": (128, 0, 30, 117, 117)} + _, data = entity7.last_call("turn_on") + assert data == {"brightness": 128, "color_temp_kelvin": 3011, "color_temp": 332} await hass.services.async_call( "light", @@ -1745,6 +1773,7 @@ async def test_light_service_call_color_conversion( entity4.entity_id, entity5.entity_id, entity6.entity_id, + entity7.entity_id, ], "brightness_pct": 50, "rgbw_color": (255, 255, 255, 255), @@ -1766,6 +1795,8 @@ async def test_light_service_call_color_conversion( _, data = entity6.last_call("turn_on") # The midpoint the the white channels is warm, compensated by adding green + blue assert data == {"brightness": 128, "rgbww_color": (0, 76, 141, 255, 255)} + _, data = entity7.last_call("turn_on") + assert data == {"brightness": 128, "color_temp_kelvin": 5962, "color_temp": 167} await hass.services.async_call( "light", @@ -1779,6 +1810,7 @@ async def test_light_service_call_color_conversion( entity4.entity_id, entity5.entity_id, entity6.entity_id, + entity7.entity_id, ], "brightness_pct": 50, "rgbww_color": (128, 0, 0, 64, 32), @@ -1799,6 +1831,8 @@ async def test_light_service_call_color_conversion( assert data == {"brightness": 128, "rgbw_color": (128, 9, 0, 33)} _, data = entity6.last_call("turn_on") assert data == {"brightness": 128, "rgbww_color": (128, 0, 0, 64, 32)} + _, data = entity7.last_call("turn_on") + assert data == {"brightness": 128, "color_temp_kelvin": 3845, "color_temp": 260} await hass.services.async_call( "light", @@ -1812,6 +1846,7 @@ async def test_light_service_call_color_conversion( entity4.entity_id, entity5.entity_id, entity6.entity_id, + entity7.entity_id, ], "brightness_pct": 50, "rgbww_color": (255, 255, 255, 255, 255), @@ -1833,6 +1868,8 @@ async def test_light_service_call_color_conversion( assert data == {"brightness": 128, "rgbw_color": (96, 44, 0, 255)} _, data = entity6.last_call("turn_on") assert data == {"brightness": 128, "rgbww_color": (255, 255, 255, 255, 255)} + _, data = entity7.last_call("turn_on") + assert data == {"brightness": 128, "color_temp_kelvin": 3451, "color_temp": 289} async def test_light_service_call_color_conversion_named_tuple( diff --git a/tests/components/matter/fixtures/config_entry_diagnostics.json b/tests/components/matter/fixtures/config_entry_diagnostics.json index 53477792e430..f591709fbdad 100644 --- a/tests/components/matter/fixtures/config_entry_diagnostics.json +++ b/tests/components/matter/fixtures/config_entry_diagnostics.json @@ -40,11 +40,11 @@ "0/29/65531": [0, 1, 2, 3, 65528, 65529, 65531, 65532, 65533], "0/31/0": [ { - "privilege": 5, - "authMode": 2, - "subjects": [112233], - "targets": null, - "fabricIndex": 1 + "1": 5, + "2": 2, + "3": [112233], + "4": null, + "254": 1 } ], "0/31/1": [], @@ -76,8 +76,8 @@ "0/40/17": true, "0/40/18": "869D5F986B588B29", "0/40/19": { - "caseSessionsPerFabric": 3, - "subscriptionsPerFabric": 3 + "0": 3, + "1": 3 }, "0/40/65532": 0, "0/40/65533": 1, @@ -122,8 +122,8 @@ "0/44/65531": [0, 1, 2, 65528, 65529, 65531, 65532, 65533], "0/48/0": 0, "0/48/1": { - "failSafeExpiryLengthSeconds": 60, - "maxCumulativeFailsafeSeconds": 900 + "0": 60, + "1": 900 }, "0/48/2": 0, "0/48/3": 0, @@ -155,14 +155,14 @@ "0/50/65531": [65528, 65529, 65531, 65532, 65533], "0/51/0": [ { - "name": "WIFI_STA_DEF", - "isOperational": true, - "offPremiseServicesReachableIPv4": null, - "offPremiseServicesReachableIPv6": null, - "hardwareAddress": "YFX5V0js", - "IPv4Addresses": ["wKgBIw=="], - "IPv6Addresses": ["/oAAAAAAAABiVfn//ldI7A=="], - "type": 1 + "0": "WIFI_STA_DEF", + "1": true, + "2": null, + "3": null, + "4": "YFX5V0js", + "5": ["wKgBIw=="], + "6": ["/oAAAAAAAABiVfn//ldI7A=="], + "7": 1 } ], "0/51/1": 3, @@ -503,19 +503,19 @@ "0/60/65531": [0, 1, 2, 65528, 65529, 65531, 65532, 65533], "0/62/0": [ { - "noc": "FTABAQEkAgE3AyQTAhgmBIAigScmBYAlTTo3BiQVASQRBRgkBwEkCAEwCUEEELwf3lni0ez0mRGa/z9gFtuTfn3Gpnsq/rBvQmpgjxqgC0RNcZmHfAm176H0j6ENQrnc1RhkKA5qiJtEgzQF4DcKNQEoARgkAgE2AwQCBAEYMAQURdGBtNYpheXbKDo2Od5OLDCytacwBRQc+rrVsNzRFL1V9i4OFnGKrwIajRgwC0AG9mdYqL5WJ0jKIBcEzeWQbo8xg6sFv0ANmq0KSpMbfqVvw8Y39XEOQ6B8v+JCXSGMpdPC0nbVQKuv/pKUvJoTGA==", - "icac": "FTABAQEkAgE3AyQUARgmBIAigScmBYAlTTo3BiQTAhgkBwEkCAEwCUEEWYzjmQq/3zCbWfMKR0asASVnOBOkNAzdwdW1X6sC0zA5m3DhGRMEff09ZqHDZi/o6CW+I+rEGNEyW+00/M84azcKNQEpARgkAmAwBBQc+rrVsNzRFL1V9i4OFnGKrwIajTAFFI6CuLTopCFiBYeGuUcP8Ak5Jo3gGDALQDYMHSAwxZPP4TFqIGot2vm5+Wir58quxbojkWwyT9l8eat6f9sJmjTZ0VLggTwAWvY+IVm82YuMzTPxmkNWxVIY", - "fabricIndex": 1 + "1": "FTABAQEkAgE3AyQTAhgmBIAigScmBYAlTTo3BiQVASQRBRgkBwEkCAEwCUEEELwf3lni0ez0mRGa/z9gFtuTfn3Gpnsq/rBvQmpgjxqgC0RNcZmHfAm176H0j6ENQrnc1RhkKA5qiJtEgzQF4DcKNQEoARgkAgE2AwQCBAEYMAQURdGBtNYpheXbKDo2Od5OLDCytacwBRQc+rrVsNzRFL1V9i4OFnGKrwIajRgwC0AG9mdYqL5WJ0jKIBcEzeWQbo8xg6sFv0ANmq0KSpMbfqVvw8Y39XEOQ6B8v+JCXSGMpdPC0nbVQKuv/pKUvJoTGA==", + "2": "FTABAQEkAgE3AyQUARgmBIAigScmBYAlTTo3BiQTAhgkBwEkCAEwCUEEWYzjmQq/3zCbWfMKR0asASVnOBOkNAzdwdW1X6sC0zA5m3DhGRMEff09ZqHDZi/o6CW+I+rEGNEyW+00/M84azcKNQEpARgkAmAwBBQc+rrVsNzRFL1V9i4OFnGKrwIajTAFFI6CuLTopCFiBYeGuUcP8Ak5Jo3gGDALQDYMHSAwxZPP4TFqIGot2vm5+Wir58quxbojkWwyT9l8eat6f9sJmjTZ0VLggTwAWvY+IVm82YuMzTPxmkNWxVIY", + "254": 1 } ], "0/62/1": [ { - "rootPublicKey": "BALNCzn2XOp1NrwszT+LOLYT+tM76+Pob8AIOFl9+0UWFsLp4ZHUainZZMJQIAHxv39srVUYW0+nacFcjHTzNHw=", - "vendorId": 65521, - "fabricId": 1, - "nodeId": 5, - "label": "", - "fabricIndex": 1 + "1": "BALNCzn2XOp1NrwszT+LOLYT+tM76+Pob8AIOFl9+0UWFsLp4ZHUainZZMJQIAHxv39srVUYW0+nacFcjHTzNHw=", + "2": 65521, + "3": 1, + "4": 5, + "5": "", + "254": 1 } ], "0/62/2": 5, @@ -540,20 +540,20 @@ "0/63/65531": [0, 1, 2, 3, 65528, 65529, 65531, 65532, 65533], "0/64/0": [ { - "label": "room", - "value": "bedroom 2" + "0": "room", + "1": "bedroom 2" }, { - "label": "orientation", - "value": "North" + "0": "orientation", + "1": "North" }, { - "label": "floor", - "value": "2" + "0": "floor", + "1": "2" }, { - "label": "direction", - "value": "up" + "0": "direction", + "1": "up" } ], "0/64/65532": 0, diff --git a/tests/components/matter/fixtures/config_entry_diagnostics_redacted.json b/tests/components/matter/fixtures/config_entry_diagnostics_redacted.json index 8a67ef0fb638..c85ee4d70e3c 100644 --- a/tests/components/matter/fixtures/config_entry_diagnostics_redacted.json +++ b/tests/components/matter/fixtures/config_entry_diagnostics_redacted.json @@ -42,11 +42,11 @@ "0/29/65531": [0, 1, 2, 3, 65528, 65529, 65531, 65532, 65533], "0/31/0": [ { - "privilege": 5, - "authMode": 2, - "subjects": [112233], - "targets": null, - "fabricIndex": 1 + "1": 5, + "2": 2, + "3": [112233], + "4": null, + "254": 1 } ], "0/31/1": [], @@ -78,8 +78,8 @@ "0/40/17": true, "0/40/18": "869D5F986B588B29", "0/40/19": { - "caseSessionsPerFabric": 3, - "subscriptionsPerFabric": 3 + "0": 3, + "1": 3 }, "0/40/65532": 0, "0/40/65533": 1, @@ -124,8 +124,8 @@ "0/44/65531": [0, 1, 2, 65528, 65529, 65531, 65532, 65533], "0/48/0": 0, "0/48/1": { - "failSafeExpiryLengthSeconds": 60, - "maxCumulativeFailsafeSeconds": 900 + "0": 60, + "1": 900 }, "0/48/2": 0, "0/48/3": 0, @@ -157,14 +157,14 @@ "0/50/65531": [65528, 65529, 65531, 65532, 65533], "0/51/0": [ { - "name": "WIFI_STA_DEF", - "isOperational": true, - "offPremiseServicesReachableIPv4": null, - "offPremiseServicesReachableIPv6": null, - "hardwareAddress": "YFX5V0js", - "IPv4Addresses": ["wKgBIw=="], - "IPv6Addresses": ["/oAAAAAAAABiVfn//ldI7A=="], - "type": 1 + "0": "WIFI_STA_DEF", + "1": true, + "2": null, + "3": null, + "4": "YFX5V0js", + "5": ["wKgBIw=="], + "6": ["/oAAAAAAAABiVfn//ldI7A=="], + "7": 1 } ], "0/51/1": 3, @@ -317,19 +317,19 @@ "0/60/65531": [0, 1, 2, 65528, 65529, 65531, 65532, 65533], "0/62/0": [ { - "noc": "FTABAQEkAgE3AyQTAhgmBIAigScmBYAlTTo3BiQVASQRBRgkBwEkCAEwCUEEELwf3lni0ez0mRGa/z9gFtuTfn3Gpnsq/rBvQmpgjxqgC0RNcZmHfAm176H0j6ENQrnc1RhkKA5qiJtEgzQF4DcKNQEoARgkAgE2AwQCBAEYMAQURdGBtNYpheXbKDo2Od5OLDCytacwBRQc+rrVsNzRFL1V9i4OFnGKrwIajRgwC0AG9mdYqL5WJ0jKIBcEzeWQbo8xg6sFv0ANmq0KSpMbfqVvw8Y39XEOQ6B8v+JCXSGMpdPC0nbVQKuv/pKUvJoTGA==", - "icac": "FTABAQEkAgE3AyQUARgmBIAigScmBYAlTTo3BiQTAhgkBwEkCAEwCUEEWYzjmQq/3zCbWfMKR0asASVnOBOkNAzdwdW1X6sC0zA5m3DhGRMEff09ZqHDZi/o6CW+I+rEGNEyW+00/M84azcKNQEpARgkAmAwBBQc+rrVsNzRFL1V9i4OFnGKrwIajTAFFI6CuLTopCFiBYeGuUcP8Ak5Jo3gGDALQDYMHSAwxZPP4TFqIGot2vm5+Wir58quxbojkWwyT9l8eat6f9sJmjTZ0VLggTwAWvY+IVm82YuMzTPxmkNWxVIY", - "fabricIndex": 1 + "1": "FTABAQEkAgE3AyQTAhgmBIAigScmBYAlTTo3BiQVASQRBRgkBwEkCAEwCUEEELwf3lni0ez0mRGa/z9gFtuTfn3Gpnsq/rBvQmpgjxqgC0RNcZmHfAm176H0j6ENQrnc1RhkKA5qiJtEgzQF4DcKNQEoARgkAgE2AwQCBAEYMAQURdGBtNYpheXbKDo2Od5OLDCytacwBRQc+rrVsNzRFL1V9i4OFnGKrwIajRgwC0AG9mdYqL5WJ0jKIBcEzeWQbo8xg6sFv0ANmq0KSpMbfqVvw8Y39XEOQ6B8v+JCXSGMpdPC0nbVQKuv/pKUvJoTGA==", + "2": "FTABAQEkAgE3AyQUARgmBIAigScmBYAlTTo3BiQTAhgkBwEkCAEwCUEEWYzjmQq/3zCbWfMKR0asASVnOBOkNAzdwdW1X6sC0zA5m3DhGRMEff09ZqHDZi/o6CW+I+rEGNEyW+00/M84azcKNQEpARgkAmAwBBQc+rrVsNzRFL1V9i4OFnGKrwIajTAFFI6CuLTopCFiBYeGuUcP8Ak5Jo3gGDALQDYMHSAwxZPP4TFqIGot2vm5+Wir58quxbojkWwyT9l8eat6f9sJmjTZ0VLggTwAWvY+IVm82YuMzTPxmkNWxVIY", + "254": 1 } ], "0/62/1": [ { - "rootPublicKey": "BALNCzn2XOp1NrwszT+LOLYT+tM76+Pob8AIOFl9+0UWFsLp4ZHUainZZMJQIAHxv39srVUYW0+nacFcjHTzNHw=", - "vendorId": 65521, - "fabricId": 1, - "nodeId": 5, - "label": "", - "fabricIndex": 1 + "1": "BALNCzn2XOp1NrwszT+LOLYT+tM76+Pob8AIOFl9+0UWFsLp4ZHUainZZMJQIAHxv39srVUYW0+nacFcjHTzNHw=", + "2": 65521, + "3": 1, + "4": 5, + "5": "", + "254": 1 } ], "0/62/2": 5, @@ -354,20 +354,20 @@ "0/63/65531": [0, 1, 2, 3, 65528, 65529, 65531, 65532, 65533], "0/64/0": [ { - "label": "room", - "value": "bedroom 2" + "0": "room", + "1": "bedroom 2" }, { - "label": "orientation", - "value": "North" + "0": "orientation", + "1": "North" }, { - "label": "floor", - "value": "2" + "0": "floor", + "1": "2" }, { - "label": "direction", - "value": "up" + "0": "direction", + "1": "up" } ], "0/64/65532": 0, diff --git a/tests/components/matter/fixtures/nodes/color-temperature-light.json b/tests/components/matter/fixtures/nodes/color-temperature-light.json index 7552fa833fb3..45d1c18635c8 100644 --- a/tests/components/matter/fixtures/nodes/color-temperature-light.json +++ b/tests/components/matter/fixtures/nodes/color-temperature-light.json @@ -6,8 +6,8 @@ "attributes": { "0/29/0": [ { - "deviceType": 22, - "revision": 1 + "0": 22, + "1": 1 } ], "0/29/1": [29, 31, 40, 48, 49, 51, 60, 62, 63], @@ -20,11 +20,11 @@ "0/29/65531": [0, 1, 2, 3, 65528, 65529, 65531, 65532, 65533], "0/31/0": [ { - "privilege": 5, - "authMode": 2, - "subjects": [112233], - "targets": null, - "fabricIndex": 52 + "1": 5, + "2": 2, + "3": [112233], + "4": null, + "254": 52 } ], "0/31/1": [], @@ -50,8 +50,8 @@ "0/40/17": true, "0/40/18": "mock-color-temperature-light", "0/40/19": { - "caseSessionsPerFabric": 3, - "subscriptionsPerFabric": 65535 + "0": 3, + "1": 65535 }, "0/40/65532": 0, "0/40/65533": 1, @@ -63,8 +63,8 @@ ], "0/48/0": 0, "0/48/1": { - "failSafeExpiryLengthSeconds": 60, - "maxCumulativeFailsafeSeconds": 900 + "0": 60, + "1": 900 }, "0/48/2": 2, "0/48/3": 2, @@ -77,8 +77,8 @@ "0/49/0": 1, "0/49/1": [ { - "networkID": "ZXRoMA==", - "connected": true + "0": "ZXRoMA==", + "1": true } ], "0/49/4": true, @@ -92,38 +92,38 @@ "0/49/65531": [0, 1, 4, 5, 6, 7, 65528, 65529, 65531, 65532, 65533], "0/51/0": [ { - "name": "eth1", - "isOperational": true, - "offPremiseServicesReachableIPv4": null, - "offPremiseServicesReachableIPv6": null, - "hardwareAddress": "ABeILIy4", - "IPv4Addresses": ["CjwBuw=="], - "IPv6Addresses": [ + "0": "eth1", + "1": true, + "2": null, + "3": null, + "4": "ABeILIy4", + "5": ["CjwBuw=="], + "6": [ "/VqgxiAxQiYCF4j//iyMuA==", "IAEEcLs7AAYCF4j//iyMuA==", "/oAAAAAAAAACF4j//iyMuA==" ], - "type": 0 + "7": 0 }, { - "name": "eth0", - "isOperational": false, - "offPremiseServicesReachableIPv4": null, - "offPremiseServicesReachableIPv6": null, - "hardwareAddress": "AAN/ESDO", - "IPv4Addresses": [], - "IPv6Addresses": [], - "type": 2 + "0": "eth0", + "1": false, + "2": null, + "3": null, + "4": "AAN/ESDO", + "5": [], + "6": [], + "7": 2 }, { - "name": "lo", - "isOperational": true, - "offPremiseServicesReachableIPv4": null, - "offPremiseServicesReachableIPv6": null, - "hardwareAddress": "AAAAAAAA", - "IPv4Addresses": ["fwAAAQ=="], - "IPv6Addresses": ["AAAAAAAAAAAAAAAAAAAAAQ=="], - "type": 0 + "0": "lo", + "1": true, + "2": null, + "3": null, + "4": "AAAAAAAA", + "5": ["fwAAAQ=="], + "6": ["AAAAAAAAAAAAAAAAAAAAAQ=="], + "7": 0 } ], "0/51/1": 4, @@ -151,19 +151,19 @@ "0/60/65531": [0, 1, 2, 65528, 65529, 65531, 65532, 65533], "0/62/0": [ { - "noc": "FTABAQEkAgE3AyQTAhgmBIAigScmBYAlTTo3BiQVASQRARgkBwEkCAEwCUEEYGMAhVV+Adasucgyi++1D7eyBIfHs9xLKJPVJqJdMAqt0S8lQs+6v/NAyAVXsN8jdGlNgZQENRnfqC2gXv3COzcKNQEoARgkAgE2AwQCBAEYMAQUTK/GvAzp9yCT0ihFRaEyW8KuO0IwBRQ5RmCO0h/Cd/uv6Pe62ZSLBzXOtBgwC0CaO1hqAR9PQJUkSx4MQyHEDQND/3j7m6EPRImPCA53dKI7e4w7xZEQEW95oMhuUobdy3WbMcggAMTX46ninwqUGA==", - "icac": "FTABAQEkAgE3AyQUARgmBIAigScmBYAlTTo3BiQTAhgkBwEkCAEwCUEEqboEMvSYpJHvrznp5AQ1fHW0AVUrTajBHZ/2uba7+FTyPb+fqgf6K1zbuMqTxTOA/FwjzAL7hQTwG+HNnmLwNTcKNQEpARgkAmAwBBQ5RmCO0h/Cd/uv6Pe62ZSLBzXOtDAFFG02YRl97W++GsAiEiBzIhO0hzA6GDALQBl+ZyFbSXu3oXVJGBjtDcpwOCRC30OaVjDhUT7NbohDLaKuwxMhAgE+uHtSLKRZPGlQGSzYdnDGj/dWolGE+n4Y", - "fabricIndex": 52 + "1": "FTABAQEkAgE3AyQTAhgmBIAigScmBYAlTTo3BiQVASQRARgkBwEkCAEwCUEEYGMAhVV+Adasucgyi++1D7eyBIfHs9xLKJPVJqJdMAqt0S8lQs+6v/NAyAVXsN8jdGlNgZQENRnfqC2gXv3COzcKNQEoARgkAgE2AwQCBAEYMAQUTK/GvAzp9yCT0ihFRaEyW8KuO0IwBRQ5RmCO0h/Cd/uv6Pe62ZSLBzXOtBgwC0CaO1hqAR9PQJUkSx4MQyHEDQND/3j7m6EPRImPCA53dKI7e4w7xZEQEW95oMhuUobdy3WbMcggAMTX46ninwqUGA==", + "2": "FTABAQEkAgE3AyQUARgmBIAigScmBYAlTTo3BiQTAhgkBwEkCAEwCUEEqboEMvSYpJHvrznp5AQ1fHW0AVUrTajBHZ/2uba7+FTyPb+fqgf6K1zbuMqTxTOA/FwjzAL7hQTwG+HNnmLwNTcKNQEpARgkAmAwBBQ5RmCO0h/Cd/uv6Pe62ZSLBzXOtDAFFG02YRl97W++GsAiEiBzIhO0hzA6GDALQBl+ZyFbSXu3oXVJGBjtDcpwOCRC30OaVjDhUT7NbohDLaKuwxMhAgE+uHtSLKRZPGlQGSzYdnDGj/dWolGE+n4Y", + "254": 52 } ], "0/62/1": [ { - "rootPublicKey": "BOI8+YJvCUh78+5WD4aHD7t1HQJS3WMrCEknk6n+5HXP2VRMB3SvK6+EEa8rR6UkHnCryIREeOmS0XYozzHjTQg=", - "vendorId": 65521, - "fabricId": 1, - "nodeId": 1, - "label": "", - "fabricIndex": 52 + "1": "BOI8+YJvCUh78+5WD4aHD7t1HQJS3WMrCEknk6n+5HXP2VRMB3SvK6+EEa8rR6UkHnCryIREeOmS0XYozzHjTQg=", + "2": 65521, + "3": 1, + "4": 1, + "5": "", + "254": 52 } ], "0/62/2": 16, @@ -202,8 +202,8 @@ ], "1/29/0": [ { - "deviceType": 268, - "revision": 1 + "0": 268, + "1": 1 } ], "1/29/1": [6, 29, 57, 768, 8, 80, 3, 4], @@ -277,19 +277,19 @@ "1/80/1": 0, "1/80/2": [ { - "label": "Dark", - "mode": 0, - "semanticTags": [] + "0": "Dark", + "1": 0, + "2": [] }, { - "label": "Medium", - "mode": 1, - "semanticTags": [] + "0": "Medium", + "1": 1, + "2": [] }, { - "label": "Light", - "mode": 2, - "semanticTags": [] + "0": "Light", + "1": 2, + "2": [] } ], "1/80/3": 0, diff --git a/tests/components/matter/fixtures/nodes/device_diagnostics.json b/tests/components/matter/fixtures/nodes/device_diagnostics.json index 3abecbdf66fb..d95fbe5efa96 100644 --- a/tests/components/matter/fixtures/nodes/device_diagnostics.json +++ b/tests/components/matter/fixtures/nodes/device_diagnostics.json @@ -13,8 +13,8 @@ "0/4/65531": [0, 65528, 65529, 65531, 65532, 65533], "0/29/0": [ { - "deviceType": 22, - "revision": 1 + "0": 22, + "1": 1 } ], "0/29/1": [ @@ -30,11 +30,11 @@ "0/29/65531": [0, 1, 2, 3, 65528, 65529, 65531, 65532, 65533], "0/31/0": [ { - "privilege": 5, - "authMode": 2, - "subjects": [112233], - "targets": null, - "fabricIndex": 1 + "1": 5, + "2": 2, + "3": [112233], + "4": null, + "254": 1 } ], "0/31/1": [], @@ -66,8 +66,8 @@ "0/40/17": true, "0/40/18": "869D5F986B588B29", "0/40/19": { - "caseSessionsPerFabric": 3, - "subscriptionsPerFabric": 3 + "0": 3, + "1": 3 }, "0/40/65532": 0, "0/40/65533": 1, @@ -112,8 +112,8 @@ "0/44/65531": [0, 1, 2, 65528, 65529, 65531, 65532, 65533], "0/48/0": 0, "0/48/1": { - "failSafeExpiryLengthSeconds": 60, - "maxCumulativeFailsafeSeconds": 900 + "0": 60, + "1": 900 }, "0/48/2": 0, "0/48/3": 0, @@ -143,14 +143,14 @@ "0/50/65531": [65528, 65529, 65531, 65532, 65533], "0/51/0": [ { - "name": "WIFI_STA_DEF", - "isOperational": true, - "offPremiseServicesReachableIPv4": null, - "offPremiseServicesReachableIPv6": null, - "hardwareAddress": "YFX5V0js", - "IPv4Addresses": ["wKgBIw=="], - "IPv6Addresses": ["/oAAAAAAAABiVfn//ldI7A=="], - "type": 1 + "0": "WIFI_STA_DEF", + "1": true, + "2": null, + "3": null, + "4": "YFX5V0js", + "5": ["wKgBIw=="], + "6": ["/oAAAAAAAABiVfn//ldI7A=="], + "7": 1 } ], "0/51/1": 3, @@ -302,19 +302,19 @@ "0/60/65531": [0, 1, 2, 65528, 65529, 65531, 65532, 65533], "0/62/0": [ { - "noc": "FTABAQEkAgE3AyQTAhgmBIAigScmBYAlTTo3BiQVASQRBRgkBwEkCAEwCUEEELwf3lni0ez0mRGa/z9gFtuTfn3Gpnsq/rBvQmpgjxqgC0RNcZmHfAm176H0j6ENQrnc1RhkKA5qiJtEgzQF4DcKNQEoARgkAgE2AwQCBAEYMAQURdGBtNYpheXbKDo2Od5OLDCytacwBRQc+rrVsNzRFL1V9i4OFnGKrwIajRgwC0AG9mdYqL5WJ0jKIBcEzeWQbo8xg6sFv0ANmq0KSpMbfqVvw8Y39XEOQ6B8v+JCXSGMpdPC0nbVQKuv/pKUvJoTGA==", - "icac": "FTABAQEkAgE3AyQUARgmBIAigScmBYAlTTo3BiQTAhgkBwEkCAEwCUEEWYzjmQq/3zCbWfMKR0asASVnOBOkNAzdwdW1X6sC0zA5m3DhGRMEff09ZqHDZi/o6CW+I+rEGNEyW+00/M84azcKNQEpARgkAmAwBBQc+rrVsNzRFL1V9i4OFnGKrwIajTAFFI6CuLTopCFiBYeGuUcP8Ak5Jo3gGDALQDYMHSAwxZPP4TFqIGot2vm5+Wir58quxbojkWwyT9l8eat6f9sJmjTZ0VLggTwAWvY+IVm82YuMzTPxmkNWxVIY", - "fabricIndex": 1 + "1": "FTABAQEkAgE3AyQTAhgmBIAigScmBYAlTTo3BiQVASQRBRgkBwEkCAEwCUEEELwf3lni0ez0mRGa/z9gFtuTfn3Gpnsq/rBvQmpgjxqgC0RNcZmHfAm176H0j6ENQrnc1RhkKA5qiJtEgzQF4DcKNQEoARgkAgE2AwQCBAEYMAQURdGBtNYpheXbKDo2Od5OLDCytacwBRQc+rrVsNzRFL1V9i4OFnGKrwIajRgwC0AG9mdYqL5WJ0jKIBcEzeWQbo8xg6sFv0ANmq0KSpMbfqVvw8Y39XEOQ6B8v+JCXSGMpdPC0nbVQKuv/pKUvJoTGA==", + "2": "FTABAQEkAgE3AyQUARgmBIAigScmBYAlTTo3BiQTAhgkBwEkCAEwCUEEWYzjmQq/3zCbWfMKR0asASVnOBOkNAzdwdW1X6sC0zA5m3DhGRMEff09ZqHDZi/o6CW+I+rEGNEyW+00/M84azcKNQEpARgkAmAwBBQc+rrVsNzRFL1V9i4OFnGKrwIajTAFFI6CuLTopCFiBYeGuUcP8Ak5Jo3gGDALQDYMHSAwxZPP4TFqIGot2vm5+Wir58quxbojkWwyT9l8eat6f9sJmjTZ0VLggTwAWvY+IVm82YuMzTPxmkNWxVIY", + "254": 1 } ], "0/62/1": [ { - "rootPublicKey": "BALNCzn2XOp1NrwszT+LOLYT+tM76+Pob8AIOFl9+0UWFsLp4ZHUainZZMJQIAHxv39srVUYW0+nacFcjHTzNHw=", - "vendorId": 65521, - "fabricId": 1, - "nodeId": 5, - "label": "", - "fabricIndex": 1 + "1": "BALNCzn2XOp1NrwszT+LOLYT+tM76+Pob8AIOFl9+0UWFsLp4ZHUainZZMJQIAHxv39srVUYW0+nacFcjHTzNHw=", + "2": 65521, + "3": 1, + "4": 5, + "5": "", + "254": 1 } ], "0/62/2": 5, @@ -339,20 +339,20 @@ "0/63/65531": [0, 1, 2, 3, 65528, 65529, 65531, 65532, 65533], "0/64/0": [ { - "label": "room", - "value": "bedroom 2" + "0": "room", + "1": "bedroom 2" }, { - "label": "orientation", - "value": "North" + "0": "orientation", + "1": "North" }, { - "label": "floor", - "value": "2" + "0": "floor", + "1": "2" }, { - "label": "direction", - "value": "up" + "0": "direction", + "1": "up" } ], "0/64/65532": 0, @@ -415,8 +415,8 @@ ], "1/29/0": [ { - "deviceType": 257, - "revision": 1 + "0": 257, + "1": 1 } ], "1/29/1": [3, 4, 6, 8, 29, 768, 1030], diff --git a/tests/components/matter/fixtures/nodes/dimmable-light.json b/tests/components/matter/fixtures/nodes/dimmable-light.json index e14c922857c9..7ccc3eef3af2 100644 --- a/tests/components/matter/fixtures/nodes/dimmable-light.json +++ b/tests/components/matter/fixtures/nodes/dimmable-light.json @@ -12,8 +12,8 @@ "0/4/65531": [0, 65528, 65529, 65531, 65532, 65533], "0/29/0": [ { - "deviceType": 22, - "revision": 1 + "0": 22, + "1": 1 } ], "0/29/1": [ @@ -29,11 +29,11 @@ "0/29/65531": [0, 1, 2, 3, 65528, 65529, 65531, 65532, 65533], "0/31/0": [ { - "privilege": 5, - "authMode": 2, - "subjects": [112233], - "targets": null, - "fabricIndex": 1 + "1": 5, + "2": 2, + "3": [112233], + "4": null, + "254": 1 } ], "0/31/1": [], @@ -65,8 +65,8 @@ "0/40/17": true, "0/40/18": "mock-dimmable-light", "0/40/19": { - "caseSessionsPerFabric": 3, - "subscriptionsPerFabric": 3 + "0": 3, + "1": 3 }, "0/40/65532": 0, "0/40/65533": 1, @@ -111,8 +111,8 @@ "0/44/65531": [0, 1, 2, 65528, 65529, 65531, 65532, 65533], "0/48/0": 0, "0/48/1": { - "failSafeExpiryLengthSeconds": 60, - "maxCumulativeFailsafeSeconds": 900 + "0": 60, + "1": 900 }, "0/48/2": 0, "0/48/3": 0, @@ -125,8 +125,8 @@ "0/49/0": 1, "0/49/1": [ { - "networkID": "", - "connected": true + "0": "", + "1": true } ], "0/49/2": 10, @@ -147,14 +147,14 @@ "0/50/65531": [65528, 65529, 65531, 65532, 65533], "0/51/0": [ { - "name": "WIFI_STA_DEF", - "isOperational": true, - "offPremiseServicesReachableIPv4": null, - "offPremiseServicesReachableIPv6": null, - "hardwareAddress": "", - "IPv4Addresses": [], - "IPv6Addresses": [], - "type": 1 + "0": "WIFI_STA_DEF", + "1": true, + "2": null, + "3": null, + "4": "", + "5": [], + "6": [], + "7": 1 } ], "0/51/1": 6, @@ -243,19 +243,19 @@ "0/60/65531": [0, 1, 2, 65528, 65529, 65531, 65532, 65533], "0/62/0": [ { - "noc": "", - "icac": "", - "fabricIndex": 1 + "1": "", + "2": "", + "254": 1 } ], "0/62/1": [ { - "rootPublicKey": "", - "vendorId": 65521, - "fabricId": 1, - "nodeId": 1, - "label": "", - "fabricIndex": 1 + "1": "", + "2": 65521, + "3": 1, + "4": 1, + "5": "", + "254": 1 } ], "0/62/2": 5, @@ -278,20 +278,20 @@ "0/63/65531": [0, 1, 2, 3, 65528, 65529, 65531, 65532, 65533], "0/64/0": [ { - "label": "room", - "value": "bedroom 2" + "0": "room", + "1": "bedroom 2" }, { - "label": "orientation", - "value": "North" + "0": "orientation", + "1": "North" }, { - "label": "floor", - "value": "2" + "0": "floor", + "1": "2" }, { - "label": "direction", - "value": "up" + "0": "direction", + "1": "up" } ], "0/64/65532": 0, @@ -354,8 +354,8 @@ ], "1/29/0": [ { - "deviceType": 257, - "revision": 1 + "0": 257, + "1": 1 } ], "1/29/1": [3, 4, 6, 8, 29, 768, 1030], diff --git a/tests/components/matter/fixtures/nodes/door-lock-with-unbolt.json b/tests/components/matter/fixtures/nodes/door-lock-with-unbolt.json index 6cbd75ab09c2..dfa7794f28be 100644 --- a/tests/components/matter/fixtures/nodes/door-lock-with-unbolt.json +++ b/tests/components/matter/fixtures/nodes/door-lock-with-unbolt.json @@ -7,8 +7,8 @@ "attributes": { "0/29/0": [ { - "deviceType": 22, - "revision": 1 + "0": 22, + "1": 1 } ], "0/29/1": [ @@ -24,11 +24,11 @@ "0/29/65531": [0, 1, 2, 3, 65528, 65529, 65530, 65531, 65532, 65533], "0/31/0": [ { - "privilege": 5, - "authMode": 2, - "subjects": [112233], - "targets": null, - "fabricIndex": 1 + "1": 5, + "2": 2, + "3": [112233], + "4": null, + "254": 1 } ], "0/31/1": [], @@ -60,8 +60,8 @@ "0/40/17": true, "0/40/18": "mock-door-lock", "0/40/19": { - "caseSessionsPerFabric": 3, - "subscriptionsPerFabric": 65535 + "0": 3, + "1": 65535 }, "0/40/65532": 0, "0/40/65533": 1, @@ -121,8 +121,8 @@ "0/47/65531": [0, 1, 2, 6, 65528, 65529, 65530, 65531, 65532, 65533], "0/48/0": 0, "0/48/1": { - "failSafeExpiryLengthSeconds": 60, - "maxCumulativeFailsafeSeconds": 900 + "0": 60, + "1": 900 }, "0/48/2": 0, "0/48/3": 2, @@ -154,28 +154,28 @@ "0/50/65531": [65528, 65529, 65530, 65531, 65532, 65533], "0/51/0": [ { - "name": "eth0", - "isOperational": true, - "offPremiseServicesReachableIPv4": null, - "offPremiseServicesReachableIPv6": null, - "hardwareAddress": "/mQDt/2Q", - "IPv4Addresses": ["CjwBaQ=="], - "IPv6Addresses": [ + "0": "eth0", + "1": true, + "2": null, + "3": null, + "4": "/mQDt/2Q", + "5": ["CjwBaQ=="], + "6": [ "/VqgxiAxQib8ZAP//rf9kA==", "IAEEcLs7AAb8ZAP//rf9kA==", "/oAAAAAAAAD8ZAP//rf9kA==" ], - "type": 2 + "7": 2 }, { - "name": "lo", - "isOperational": true, - "offPremiseServicesReachableIPv4": null, - "offPremiseServicesReachableIPv6": null, - "hardwareAddress": "AAAAAAAA", - "IPv4Addresses": ["fwAAAQ=="], - "IPv6Addresses": ["AAAAAAAAAAAAAAAAAAAAAQ=="], - "type": 0 + "0": "lo", + "1": true, + "2": null, + "3": null, + "4": "AAAAAAAA", + "5": ["fwAAAQ=="], + "6": ["AAAAAAAAAAAAAAAAAAAAAQ=="], + "7": 0 } ], "0/51/1": 1, @@ -195,39 +195,39 @@ ], "0/52/0": [ { - "id": 26957, - "name": "26957", - "stackFreeCurrent": null, - "stackFreeMinimum": null, - "stackSize": null + "0": 26957, + "1": "26957", + "2": null, + "3": null, + "4": null }, { - "id": 26956, - "name": "26956", - "stackFreeCurrent": null, - "stackFreeMinimum": null, - "stackSize": null + "0": 26956, + "1": "26956", + "2": null, + "3": null, + "4": null }, { - "id": 26955, - "name": "26955", - "stackFreeCurrent": null, - "stackFreeMinimum": null, - "stackSize": null + "0": 26955, + "1": "26955", + "2": null, + "3": null, + "4": null }, { - "id": 26953, - "name": "26953", - "stackFreeCurrent": null, - "stackFreeMinimum": null, - "stackSize": null + "0": 26953, + "1": "26953", + "2": null, + "3": null, + "4": null }, { - "id": 26952, - "name": "26952", - "stackFreeCurrent": null, - "stackFreeMinimum": null, - "stackSize": null + "0": 26952, + "1": "26952", + "2": null, + "3": null, + "4": null } ], "0/52/1": 351120, @@ -358,19 +358,19 @@ "0/60/65531": [0, 1, 2, 65528, 65529, 65530, 65531, 65532, 65533], "0/62/0": [ { - "noc": "FTABAQEkAgE3AyQTAhgmBIAigScmBYAlTTo3BiQVASQRARgkBwEkCAEwCUEE55h6CbNLPZH/uM3/rDdA+jeuuD2QSPN8gBeEB0bmGJqWz/gCT4/ySB77rK3XiwVWVAmJhJ/eMcTIA0XXWMqKPDcKNQEoARgkAgE2AwQCBAEYMAQUqnKiC76YFhcTHt4AQ/kAbtrZ2MowBRSL6EWyWm8+uC0Puc2/BncMqYbpmhgwC0AA05Z+y1mcyHUeOFJ5kyDJJMN/oNCwN5h8UpYN/868iuQArr180/fbaN1+db9lab4D2lf0HK7wgHIR3HsOa2w9GA==", - "icac": "FTABAQEkAgE3AyQUARgmBIAigScmBYAlTTo3BiQTAhgkBwEkCAEwCUEE5R1DrUQE/L8tx95WR1g1dZJf4d+6LEB7JAYZN/nw9ZBUg5VOHDrB1xIw5KguYJzt10K+0KqQBBEbuwW+wLLobTcKNQEpARgkAmAwBBSL6EWyWm8+uC0Puc2/BncMqYbpmjAFFM0I6fPFzfOv2IWbX1huxb3eW0fqGDALQHXLE0TgIDW6XOnvtsOJCyKoENts8d4TQWBgTKviv1LF/+MS9eFYi+kO+1Idq5mVgwN+lH7eyecShQR0iqq6WLUY", - "fabricIndex": 1 + "1": "FTABAQEkAgE3AyQTAhgmBIAigScmBYAlTTo3BiQVASQRARgkBwEkCAEwCUEE55h6CbNLPZH/uM3/rDdA+jeuuD2QSPN8gBeEB0bmGJqWz/gCT4/ySB77rK3XiwVWVAmJhJ/eMcTIA0XXWMqKPDcKNQEoARgkAgE2AwQCBAEYMAQUqnKiC76YFhcTHt4AQ/kAbtrZ2MowBRSL6EWyWm8+uC0Puc2/BncMqYbpmhgwC0AA05Z+y1mcyHUeOFJ5kyDJJMN/oNCwN5h8UpYN/868iuQArr180/fbaN1+db9lab4D2lf0HK7wgHIR3HsOa2w9GA==", + "2": "FTABAQEkAgE3AyQUARgmBIAigScmBYAlTTo3BiQTAhgkBwEkCAEwCUEE5R1DrUQE/L8tx95WR1g1dZJf4d+6LEB7JAYZN/nw9ZBUg5VOHDrB1xIw5KguYJzt10K+0KqQBBEbuwW+wLLobTcKNQEpARgkAmAwBBSL6EWyWm8+uC0Puc2/BncMqYbpmjAFFM0I6fPFzfOv2IWbX1huxb3eW0fqGDALQHXLE0TgIDW6XOnvtsOJCyKoENts8d4TQWBgTKviv1LF/+MS9eFYi+kO+1Idq5mVgwN+lH7eyecShQR0iqq6WLUY", + "254": 1 } ], "0/62/1": [ { - "rootPublicKey": "BJ/jL2MdDrdq9TahKSa5c/dBc166NRCU0W9l7hK2kcuVtN915DLqiS+RAJ2iPEvWK5FawZHF/QdKLZmTkZHudxY=", - "vendorId": 65521, - "fabricId": 1, - "nodeId": 1, - "label": "", - "fabricIndex": 1 + "1": "BJ/jL2MdDrdq9TahKSa5c/dBc166NRCU0W9l7hK2kcuVtN915DLqiS+RAJ2iPEvWK5FawZHF/QdKLZmTkZHudxY=", + "2": 65521, + "3": 1, + "4": 1, + "5": "", + "254": 1 } ], "0/62/2": 16, @@ -395,20 +395,20 @@ "0/63/65531": [0, 1, 2, 3, 65528, 65529, 65530, 65531, 65532, 65533], "0/64/0": [ { - "label": "room", - "value": "bedroom 2" + "0": "room", + "1": "bedroom 2" }, { - "label": "orientation", - "value": "North" + "0": "orientation", + "1": "North" }, { - "label": "floor", - "value": "2" + "0": "floor", + "1": "2" }, { - "label": "direction", - "value": "up" + "0": "direction", + "1": "up" } ], "0/64/65532": 0, @@ -443,8 +443,8 @@ ], "1/29/0": [ { - "deviceType": 10, - "revision": 1 + "0": 10, + "1": 1 } ], "1/29/1": [3, 6, 29, 47, 257], diff --git a/tests/components/matter/fixtures/nodes/door-lock.json b/tests/components/matter/fixtures/nodes/door-lock.json index 1477d78aa67e..8a3f0fd68ddf 100644 --- a/tests/components/matter/fixtures/nodes/door-lock.json +++ b/tests/components/matter/fixtures/nodes/door-lock.json @@ -7,8 +7,8 @@ "attributes": { "0/29/0": [ { - "deviceType": 22, - "revision": 1 + "0": 22, + "1": 1 } ], "0/29/1": [ @@ -24,11 +24,11 @@ "0/29/65531": [0, 1, 2, 3, 65528, 65529, 65530, 65531, 65532, 65533], "0/31/0": [ { - "privilege": 5, - "authMode": 2, - "subjects": [112233], - "targets": null, - "fabricIndex": 1 + "1": 5, + "2": 2, + "3": [112233], + "4": null, + "254": 1 } ], "0/31/1": [], @@ -60,8 +60,8 @@ "0/40/17": true, "0/40/18": "mock-door-lock", "0/40/19": { - "caseSessionsPerFabric": 3, - "subscriptionsPerFabric": 65535 + "0": 3, + "1": 65535 }, "0/40/65532": 0, "0/40/65533": 1, @@ -121,8 +121,8 @@ "0/47/65531": [0, 1, 2, 6, 65528, 65529, 65530, 65531, 65532, 65533], "0/48/0": 0, "0/48/1": { - "failSafeExpiryLengthSeconds": 60, - "maxCumulativeFailsafeSeconds": 900 + "0": 60, + "1": 900 }, "0/48/2": 0, "0/48/3": 2, @@ -154,28 +154,28 @@ "0/50/65531": [65528, 65529, 65530, 65531, 65532, 65533], "0/51/0": [ { - "name": "eth0", - "isOperational": true, - "offPremiseServicesReachableIPv4": null, - "offPremiseServicesReachableIPv6": null, - "hardwareAddress": "/mQDt/2Q", - "IPv4Addresses": ["CjwBaQ=="], - "IPv6Addresses": [ + "0": "eth0", + "1": true, + "2": null, + "3": null, + "4": "/mQDt/2Q", + "5": ["CjwBaQ=="], + "6": [ "/VqgxiAxQib8ZAP//rf9kA==", "IAEEcLs7AAb8ZAP//rf9kA==", "/oAAAAAAAAD8ZAP//rf9kA==" ], - "type": 2 + "7": 2 }, { - "name": "lo", - "isOperational": true, - "offPremiseServicesReachableIPv4": null, - "offPremiseServicesReachableIPv6": null, - "hardwareAddress": "AAAAAAAA", - "IPv4Addresses": ["fwAAAQ=="], - "IPv6Addresses": ["AAAAAAAAAAAAAAAAAAAAAQ=="], - "type": 0 + "0": "lo", + "1": true, + "2": null, + "3": null, + "4": "AAAAAAAA", + "5": ["fwAAAQ=="], + "6": ["AAAAAAAAAAAAAAAAAAAAAQ=="], + "7": 0 } ], "0/51/1": 1, @@ -195,39 +195,39 @@ ], "0/52/0": [ { - "id": 26957, - "name": "26957", - "stackFreeCurrent": null, - "stackFreeMinimum": null, - "stackSize": null + "0": 26957, + "1": "26957", + "2": null, + "3": null, + "4": null }, { - "id": 26956, - "name": "26956", - "stackFreeCurrent": null, - "stackFreeMinimum": null, - "stackSize": null + "0": 26956, + "1": "26956", + "2": null, + "3": null, + "4": null }, { - "id": 26955, - "name": "26955", - "stackFreeCurrent": null, - "stackFreeMinimum": null, - "stackSize": null + "0": 26955, + "1": "26955", + "2": null, + "3": null, + "4": null }, { - "id": 26953, - "name": "26953", - "stackFreeCurrent": null, - "stackFreeMinimum": null, - "stackSize": null + "0": 26953, + "1": "26953", + "2": null, + "3": null, + "4": null }, { - "id": 26952, - "name": "26952", - "stackFreeCurrent": null, - "stackFreeMinimum": null, - "stackSize": null + "0": 26952, + "1": "26952", + "2": null, + "3": null, + "4": null } ], "0/52/1": 351120, @@ -358,19 +358,19 @@ "0/60/65531": [0, 1, 2, 65528, 65529, 65530, 65531, 65532, 65533], "0/62/0": [ { - "noc": "FTABAQEkAgE3AyQTAhgmBIAigScmBYAlTTo3BiQVASQRARgkBwEkCAEwCUEE55h6CbNLPZH/uM3/rDdA+jeuuD2QSPN8gBeEB0bmGJqWz/gCT4/ySB77rK3XiwVWVAmJhJ/eMcTIA0XXWMqKPDcKNQEoARgkAgE2AwQCBAEYMAQUqnKiC76YFhcTHt4AQ/kAbtrZ2MowBRSL6EWyWm8+uC0Puc2/BncMqYbpmhgwC0AA05Z+y1mcyHUeOFJ5kyDJJMN/oNCwN5h8UpYN/868iuQArr180/fbaN1+db9lab4D2lf0HK7wgHIR3HsOa2w9GA==", - "icac": "FTABAQEkAgE3AyQUARgmBIAigScmBYAlTTo3BiQTAhgkBwEkCAEwCUEE5R1DrUQE/L8tx95WR1g1dZJf4d+6LEB7JAYZN/nw9ZBUg5VOHDrB1xIw5KguYJzt10K+0KqQBBEbuwW+wLLobTcKNQEpARgkAmAwBBSL6EWyWm8+uC0Puc2/BncMqYbpmjAFFM0I6fPFzfOv2IWbX1huxb3eW0fqGDALQHXLE0TgIDW6XOnvtsOJCyKoENts8d4TQWBgTKviv1LF/+MS9eFYi+kO+1Idq5mVgwN+lH7eyecShQR0iqq6WLUY", - "fabricIndex": 1 + "1": "FTABAQEkAgE3AyQTAhgmBIAigScmBYAlTTo3BiQVASQRARgkBwEkCAEwCUEE55h6CbNLPZH/uM3/rDdA+jeuuD2QSPN8gBeEB0bmGJqWz/gCT4/ySB77rK3XiwVWVAmJhJ/eMcTIA0XXWMqKPDcKNQEoARgkAgE2AwQCBAEYMAQUqnKiC76YFhcTHt4AQ/kAbtrZ2MowBRSL6EWyWm8+uC0Puc2/BncMqYbpmhgwC0AA05Z+y1mcyHUeOFJ5kyDJJMN/oNCwN5h8UpYN/868iuQArr180/fbaN1+db9lab4D2lf0HK7wgHIR3HsOa2w9GA==", + "2": "FTABAQEkAgE3AyQUARgmBIAigScmBYAlTTo3BiQTAhgkBwEkCAEwCUEE5R1DrUQE/L8tx95WR1g1dZJf4d+6LEB7JAYZN/nw9ZBUg5VOHDrB1xIw5KguYJzt10K+0KqQBBEbuwW+wLLobTcKNQEpARgkAmAwBBSL6EWyWm8+uC0Puc2/BncMqYbpmjAFFM0I6fPFzfOv2IWbX1huxb3eW0fqGDALQHXLE0TgIDW6XOnvtsOJCyKoENts8d4TQWBgTKviv1LF/+MS9eFYi+kO+1Idq5mVgwN+lH7eyecShQR0iqq6WLUY", + "254": 1 } ], "0/62/1": [ { - "rootPublicKey": "BJ/jL2MdDrdq9TahKSa5c/dBc166NRCU0W9l7hK2kcuVtN915DLqiS+RAJ2iPEvWK5FawZHF/QdKLZmTkZHudxY=", - "vendorId": 65521, - "fabricId": 1, - "nodeId": 1, - "label": "", - "fabricIndex": 1 + "1": "BJ/jL2MdDrdq9TahKSa5c/dBc166NRCU0W9l7hK2kcuVtN915DLqiS+RAJ2iPEvWK5FawZHF/QdKLZmTkZHudxY=", + "2": 65521, + "3": 1, + "4": 1, + "5": "", + "254": 1 } ], "0/62/2": 16, @@ -395,20 +395,20 @@ "0/63/65531": [0, 1, 2, 3, 65528, 65529, 65530, 65531, 65532, 65533], "0/64/0": [ { - "label": "room", - "value": "bedroom 2" + "0": "room", + "1": "bedroom 2" }, { - "label": "orientation", - "value": "North" + "0": "orientation", + "1": "North" }, { - "label": "floor", - "value": "2" + "0": "floor", + "1": "2" }, { - "label": "direction", - "value": "up" + "0": "direction", + "1": "up" } ], "0/64/65532": 0, @@ -443,8 +443,8 @@ ], "1/29/0": [ { - "deviceType": 10, - "revision": 1 + "0": 10, + "1": 1 } ], "1/29/1": [3, 6, 29, 47, 257], diff --git a/tests/components/matter/fixtures/nodes/eve-contact-sensor.json b/tests/components/matter/fixtures/nodes/eve-contact-sensor.json index b0eacfb621c1..a009796f9408 100644 --- a/tests/components/matter/fixtures/nodes/eve-contact-sensor.json +++ b/tests/components/matter/fixtures/nodes/eve-contact-sensor.json @@ -12,16 +12,16 @@ "0/53/47": 0, "0/53/8": [ { - "extAddress": 12872547289273451492, - "rloc16": 1024, - "routerId": 1, - "nextHop": 0, - "pathCost": 0, - "LQIIn": 3, - "LQIOut": 3, - "age": 142, - "allocated": true, - "linkEstablished": true + "0": 12872547289273451492, + "1": 1024, + "2": 1, + "3": 0, + "4": 0, + "5": 3, + "6": 3, + "7": 142, + "8": true, + "9": true } ], "0/53/29": 1556, @@ -30,20 +30,20 @@ "0/53/40": 519, "0/53/7": [ { - "extAddress": 12872547289273451492, - "age": 654, - "rloc16": 1024, - "linkFrameCounter": 738, - "mleFrameCounter": 418, - "lqi": 3, - "averageRssi": -50, - "lastRssi": -51, - "frameErrorRate": 5, - "messageErrorRate": 0, - "rxOnWhenIdle": true, - "fullThreadDevice": true, - "fullNetworkData": true, - "isChild": false + "0": 12872547289273451492, + "1": 654, + "2": 1024, + "3": 738, + "4": 418, + "5": 3, + "6": -50, + "7": -51, + "8": 5, + "9": 0, + "10": true, + "11": true, + "12": true, + "13": false } ], "0/53/33": 66, @@ -124,9 +124,9 @@ "0/53/16": 0, "0/42/0": [ { - "providerNodeID": 1773685588, - "endpoint": 0, - "fabricIndex": 1 + "1": 1773685588, + "2": 0, + "254": 1 } ], "0/42/65528": [], @@ -140,8 +140,8 @@ "0/48/65532": 0, "0/48/65528": [1, 3, 5], "0/48/1": { - "failSafeExpiryLengthSeconds": 60, - "maxCumulativeFailsafeSeconds": 900 + "0": 60, + "1": 900 }, "0/48/4": true, "0/48/65531": [0, 1, 2, 3, 4, 65528, 65529, 65531, 65532, 65533], @@ -158,25 +158,25 @@ "0/31/1": [], "0/31/0": [ { - "privilege": 0, - "authMode": 0, - "subjects": null, - "targets": null, - "fabricIndex": 1 + "1": 0, + "2": 0, + "3": null, + "4": null, + "254": 1 }, { - "privilege": 0, - "authMode": 0, - "subjects": null, - "targets": null, - "fabricIndex": 2 + "1": 0, + "2": 0, + "3": null, + "4": null, + "254": 2 }, { - "privilege": 5, - "authMode": 2, - "subjects": [112233], - "targets": null, - "fabricIndex": 3 + "1": 5, + "2": 2, + "3": [112233], + "4": null, + "254": 3 } ], "0/31/65532": 0, @@ -187,8 +187,8 @@ "0/49/65533": 1, "0/49/1": [ { - "networkID": "Uv50lWMtT7s=", - "connected": true + "0": "Uv50lWMtT7s=", + "1": true } ], "0/49/3": 20, @@ -217,8 +217,8 @@ "0/29/65533": 1, "0/29/0": [ { - "deviceType": 22, - "revision": 1 + "0": 22, + "1": 1 } ], "0/29/1": [29, 31, 40, 42, 46, 48, 49, 51, 53, 60, 62, 63], @@ -226,18 +226,18 @@ "0/51/65531": [0, 1, 2, 3, 5, 6, 7, 8, 65528, 65529, 65531, 65532, 65533], "0/51/0": [ { - "name": "ieee802154", - "isOperational": true, - "offPremiseServicesReachableIPv4": null, - "offPremiseServicesReachableIPv6": null, - "hardwareAddress": "YtmXHFJ/dhk=", - "IPv4Addresses": [], - "IPv6Addresses": [ + "0": "ieee802154", + "1": true, + "2": null, + "3": null, + "4": "YtmXHFJ/dhk=", + "5": [], + "6": [ "/RG+U41GAABynlpPU50e5g==", "/oAAAAAAAABg2ZccUn92GQ==", "/VL+dJVjAAB1cwmi02rvTA==" ], - "type": 4 + "7": 4 } ], "0/51/65529": [0], @@ -261,8 +261,8 @@ "0/40/6": "**REDACTED**", "0/40/3": "Eve Door", "0/40/19": { - "caseSessionsPerFabric": 3, - "subscriptionsPerFabric": 3 + "0": 3, + "1": 3 }, "0/40/2": 4874, "0/40/65532": 0, @@ -302,8 +302,8 @@ "1/29/65533": 1, "1/29/0": [ { - "deviceType": 21, - "revision": 1 + "0": 21, + "1": 1 } ], "1/29/65528": [], diff --git a/tests/components/matter/fixtures/nodes/extended-color-light.json b/tests/components/matter/fixtures/nodes/extended-color-light.json index f4d83239b6d8..d18b76768ca4 100644 --- a/tests/components/matter/fixtures/nodes/extended-color-light.json +++ b/tests/components/matter/fixtures/nodes/extended-color-light.json @@ -6,8 +6,8 @@ "attributes": { "0/29/0": [ { - "deviceType": 22, - "revision": 1 + "0": 22, + "1": 1 } ], "0/29/1": [29, 31, 40, 48, 49, 51, 60, 62, 63], @@ -20,11 +20,11 @@ "0/29/65531": [0, 1, 2, 3, 65528, 65529, 65531, 65532, 65533], "0/31/0": [ { - "privilege": 5, - "authMode": 2, - "subjects": [112233], - "targets": null, - "fabricIndex": 52 + "1": 5, + "2": 2, + "3": [112233], + "4": null, + "254": 52 } ], "0/31/1": [], @@ -50,8 +50,8 @@ "0/40/17": true, "0/40/18": "mock-extended-color-light", "0/40/19": { - "caseSessionsPerFabric": 3, - "subscriptionsPerFabric": 65535 + "0": 3, + "1": 65535 }, "0/40/65532": 0, "0/40/65533": 1, @@ -63,8 +63,8 @@ ], "0/48/0": 0, "0/48/1": { - "failSafeExpiryLengthSeconds": 60, - "maxCumulativeFailsafeSeconds": 900 + "0": 60, + "1": 900 }, "0/48/2": 2, "0/48/3": 2, @@ -77,8 +77,8 @@ "0/49/0": 1, "0/49/1": [ { - "networkID": "ZXRoMA==", - "connected": true + "0": "ZXRoMA==", + "1": true } ], "0/49/4": true, @@ -92,38 +92,38 @@ "0/49/65531": [0, 1, 4, 5, 6, 7, 65528, 65529, 65531, 65532, 65533], "0/51/0": [ { - "name": "eth1", - "isOperational": true, - "offPremiseServicesReachableIPv4": null, - "offPremiseServicesReachableIPv6": null, - "hardwareAddress": "ABeILIy4", - "IPv4Addresses": ["CjwBuw=="], - "IPv6Addresses": [ + "0": "eth1", + "1": true, + "2": null, + "3": null, + "4": "ABeILIy4", + "5": ["CjwBuw=="], + "6": [ "/VqgxiAxQiYCF4j//iyMuA==", "IAEEcLs7AAYCF4j//iyMuA==", "/oAAAAAAAAACF4j//iyMuA==" ], - "type": 0 + "7": 0 }, { - "name": "eth0", - "isOperational": false, - "offPremiseServicesReachableIPv4": null, - "offPremiseServicesReachableIPv6": null, - "hardwareAddress": "AAN/ESDO", - "IPv4Addresses": [], - "IPv6Addresses": [], - "type": 2 + "0": "eth0", + "1": false, + "2": null, + "3": null, + "4": "AAN/ESDO", + "5": [], + "6": [], + "7": 2 }, { - "name": "lo", - "isOperational": true, - "offPremiseServicesReachableIPv4": null, - "offPremiseServicesReachableIPv6": null, - "hardwareAddress": "AAAAAAAA", - "IPv4Addresses": ["fwAAAQ=="], - "IPv6Addresses": ["AAAAAAAAAAAAAAAAAAAAAQ=="], - "type": 0 + "0": "lo", + "1": true, + "2": null, + "3": null, + "4": "AAAAAAAA", + "5": ["fwAAAQ=="], + "6": ["AAAAAAAAAAAAAAAAAAAAAQ=="], + "7": 0 } ], "0/51/1": 4, @@ -151,19 +151,19 @@ "0/60/65531": [0, 1, 2, 65528, 65529, 65531, 65532, 65533], "0/62/0": [ { - "noc": "FTABAQEkAgE3AyQTAhgmBIAigScmBYAlTTo3BiQVASQRARgkBwEkCAEwCUEEYGMAhVV+Adasucgyi++1D7eyBIfHs9xLKJPVJqJdMAqt0S8lQs+6v/NAyAVXsN8jdGlNgZQENRnfqC2gXv3COzcKNQEoARgkAgE2AwQCBAEYMAQUTK/GvAzp9yCT0ihFRaEyW8KuO0IwBRQ5RmCO0h/Cd/uv6Pe62ZSLBzXOtBgwC0CaO1hqAR9PQJUkSx4MQyHEDQND/3j7m6EPRImPCA53dKI7e4w7xZEQEW95oMhuUobdy3WbMcggAMTX46ninwqUGA==", - "icac": "FTABAQEkAgE3AyQUARgmBIAigScmBYAlTTo3BiQTAhgkBwEkCAEwCUEEqboEMvSYpJHvrznp5AQ1fHW0AVUrTajBHZ/2uba7+FTyPb+fqgf6K1zbuMqTxTOA/FwjzAL7hQTwG+HNnmLwNTcKNQEpARgkAmAwBBQ5RmCO0h/Cd/uv6Pe62ZSLBzXOtDAFFG02YRl97W++GsAiEiBzIhO0hzA6GDALQBl+ZyFbSXu3oXVJGBjtDcpwOCRC30OaVjDhUT7NbohDLaKuwxMhAgE+uHtSLKRZPGlQGSzYdnDGj/dWolGE+n4Y", - "fabricIndex": 52 + "1": "FTABAQEkAgE3AyQTAhgmBIAigScmBYAlTTo3BiQVASQRARgkBwEkCAEwCUEEYGMAhVV+Adasucgyi++1D7eyBIfHs9xLKJPVJqJdMAqt0S8lQs+6v/NAyAVXsN8jdGlNgZQENRnfqC2gXv3COzcKNQEoARgkAgE2AwQCBAEYMAQUTK/GvAzp9yCT0ihFRaEyW8KuO0IwBRQ5RmCO0h/Cd/uv6Pe62ZSLBzXOtBgwC0CaO1hqAR9PQJUkSx4MQyHEDQND/3j7m6EPRImPCA53dKI7e4w7xZEQEW95oMhuUobdy3WbMcggAMTX46ninwqUGA==", + "2": "FTABAQEkAgE3AyQUARgmBIAigScmBYAlTTo3BiQTAhgkBwEkCAEwCUEEqboEMvSYpJHvrznp5AQ1fHW0AVUrTajBHZ/2uba7+FTyPb+fqgf6K1zbuMqTxTOA/FwjzAL7hQTwG+HNnmLwNTcKNQEpARgkAmAwBBQ5RmCO0h/Cd/uv6Pe62ZSLBzXOtDAFFG02YRl97W++GsAiEiBzIhO0hzA6GDALQBl+ZyFbSXu3oXVJGBjtDcpwOCRC30OaVjDhUT7NbohDLaKuwxMhAgE+uHtSLKRZPGlQGSzYdnDGj/dWolGE+n4Y", + "254": 52 } ], "0/62/1": [ { - "rootPublicKey": "BOI8+YJvCUh78+5WD4aHD7t1HQJS3WMrCEknk6n+5HXP2VRMB3SvK6+EEa8rR6UkHnCryIREeOmS0XYozzHjTQg=", - "vendorId": 65521, - "fabricId": 1, - "nodeId": 1, - "label": "", - "fabricIndex": 52 + "1": "BOI8+YJvCUh78+5WD4aHD7t1HQJS3WMrCEknk6n+5HXP2VRMB3SvK6+EEa8rR6UkHnCryIREeOmS0XYozzHjTQg=", + "2": 65521, + "3": 1, + "4": 1, + "5": "", + "254": 52 } ], "0/62/2": 16, @@ -202,8 +202,8 @@ ], "1/29/0": [ { - "deviceType": 269, - "revision": 1 + "0": 269, + "1": 1 } ], "1/29/1": [6, 29, 57, 768, 8, 80, 3, 4], @@ -277,19 +277,19 @@ "1/80/1": 0, "1/80/2": [ { - "label": "Dark", - "mode": 0, - "semanticTags": [] + "0": "Dark", + "1": 0, + "2": [] }, { - "label": "Medium", - "mode": 1, - "semanticTags": [] + "0": "Medium", + "1": 1, + "2": [] }, { - "label": "Light", - "mode": 2, - "semanticTags": [] + "0": "Light", + "1": 2, + "2": [] } ], "1/80/3": 0, diff --git a/tests/components/matter/fixtures/nodes/flow-sensor.json b/tests/components/matter/fixtures/nodes/flow-sensor.json index e1fc2a365850..a8dad202fa19 100644 --- a/tests/components/matter/fixtures/nodes/flow-sensor.json +++ b/tests/components/matter/fixtures/nodes/flow-sensor.json @@ -6,8 +6,8 @@ "attributes": { "0/29/0": [ { - "deviceType": 22, - "revision": 1 + "0": 22, + "1": 1 } ], "0/29/1": [ @@ -41,8 +41,8 @@ "0/40/17": true, "0/40/18": "mock-flow-sensor", "0/40/19": { - "caseSessionsPerFabric": 3, - "subscriptionsPerFabric": 3 + "0": 3, + "1": 3 }, "0/40/65532": 0, "0/40/65533": 1, @@ -56,8 +56,8 @@ "1/3/65531": [0, 1, 65528, 65529, 65531, 65532, 65533], "1/29/0": [ { - "deviceType": 774, - "revision": 1 + "0": 774, + "1": 1 } ], "1/29/1": [6, 29, 57, 768, 8, 40], diff --git a/tests/components/matter/fixtures/nodes/generic-switch-multi.json b/tests/components/matter/fixtures/nodes/generic-switch-multi.json index 15c938253072..f564e91a1ce8 100644 --- a/tests/components/matter/fixtures/nodes/generic-switch-multi.json +++ b/tests/components/matter/fixtures/nodes/generic-switch-multi.json @@ -6,8 +6,8 @@ "attributes": { "0/29/0": [ { - "deviceType": 22, - "revision": 1 + "0": 22, + "1": 1 } ], "0/29/1": [ @@ -41,8 +41,8 @@ "0/40/17": true, "0/40/18": "mock-generic-switch", "0/40/19": { - "caseSessionsPerFabric": 3, - "subscriptionsPerFabric": 3 + "0": 3, + "1": 3 }, "0/40/65532": 0, "0/40/65533": 1, @@ -56,8 +56,8 @@ "1/3/65531": [0, 1, 65528, 65529, 65531, 65532, 65533], "1/29/0": [ { - "deviceType": 15, - "revision": 1 + "0": 15, + "1": 1 } ], "1/29/1": [3, 29, 59], @@ -77,17 +77,16 @@ "1/59/65528": [], "1/64/0": [ { - "label": "Label", - "value": "1" + "0": "Label", + "1": "1" } ], - "2/3/65529": [0, 64], "2/3/65531": [0, 1, 65528, 65529, 65531, 65532, 65533], "2/29/0": [ { - "deviceType": 15, - "revision": 1 + "0": 15, + "1": 1 } ], "2/29/1": [3, 29, 59], @@ -107,8 +106,8 @@ "2/59/65528": [], "2/64/0": [ { - "label": "Label", - "value": "Fancy Button" + "0": "Label", + "1": "Fancy Button" } ] }, diff --git a/tests/components/matter/fixtures/nodes/generic-switch.json b/tests/components/matter/fixtures/nodes/generic-switch.json index 30763c88e5ba..80773915748a 100644 --- a/tests/components/matter/fixtures/nodes/generic-switch.json +++ b/tests/components/matter/fixtures/nodes/generic-switch.json @@ -6,8 +6,8 @@ "attributes": { "0/29/0": [ { - "deviceType": 22, - "revision": 1 + "0": 22, + "1": 1 } ], "0/29/1": [ @@ -41,8 +41,8 @@ "0/40/17": true, "0/40/18": "mock-generic-switch", "0/40/19": { - "caseSessionsPerFabric": 3, - "subscriptionsPerFabric": 3 + "0": 3, + "1": 3 }, "0/40/65532": 0, "0/40/65533": 1, @@ -56,8 +56,8 @@ "1/3/65531": [0, 1, 65528, 65529, 65531, 65532, 65533], "1/29/0": [ { - "deviceType": 15, - "revision": 1 + "0": 15, + "1": 1 } ], "1/29/1": [3, 29, 59], diff --git a/tests/components/matter/fixtures/nodes/humidity-sensor.json b/tests/components/matter/fixtures/nodes/humidity-sensor.json index a1940fc1857b..8220c9cf8f87 100644 --- a/tests/components/matter/fixtures/nodes/humidity-sensor.json +++ b/tests/components/matter/fixtures/nodes/humidity-sensor.json @@ -6,8 +6,8 @@ "attributes": { "0/29/0": [ { - "deviceType": 22, - "revision": 1 + "0": 22, + "1": 1 } ], "0/29/1": [ @@ -41,8 +41,8 @@ "0/40/17": true, "0/40/18": "mock-humidity-sensor", "0/40/19": { - "caseSessionsPerFabric": 3, - "subscriptionsPerFabric": 3 + "0": 3, + "1": 3 }, "0/40/65532": 0, "0/40/65533": 1, @@ -56,8 +56,8 @@ "1/3/65531": [0, 1, 65528, 65529, 65531, 65532, 65533], "1/29/0": [ { - "deviceType": 775, - "revision": 1 + "0": 775, + "1": 1 } ], "1/29/1": [6, 29, 57, 768, 8, 40], diff --git a/tests/components/matter/fixtures/nodes/light-sensor.json b/tests/components/matter/fixtures/nodes/light-sensor.json index 93583c342920..c4d84bc79236 100644 --- a/tests/components/matter/fixtures/nodes/light-sensor.json +++ b/tests/components/matter/fixtures/nodes/light-sensor.json @@ -6,8 +6,8 @@ "attributes": { "0/29/0": [ { - "deviceType": 22, - "revision": 1 + "0": 22, + "1": 1 } ], "0/29/1": [ @@ -41,8 +41,8 @@ "0/40/17": true, "0/40/18": "mock-light-sensor", "0/40/19": { - "caseSessionsPerFabric": 3, - "subscriptionsPerFabric": 3 + "0": 3, + "1": 3 }, "0/40/65532": 0, "0/40/65533": 1, @@ -56,8 +56,8 @@ "1/3/65531": [0, 1, 65528, 65529, 65531, 65532, 65533], "1/29/0": [ { - "deviceType": 262, - "revision": 1 + "0": 262, + "1": 1 } ], "1/29/1": [6, 29, 57, 768, 8, 40], diff --git a/tests/components/matter/fixtures/nodes/occupancy-sensor.json b/tests/components/matter/fixtures/nodes/occupancy-sensor.json index d8f2580c2b03..f63dd43362b1 100644 --- a/tests/components/matter/fixtures/nodes/occupancy-sensor.json +++ b/tests/components/matter/fixtures/nodes/occupancy-sensor.json @@ -6,8 +6,8 @@ "attributes": { "0/29/0": [ { - "deviceType": 22, - "revision": 1 + "0": 22, + "1": 1 } ], "0/29/1": [ @@ -41,8 +41,8 @@ "0/40/17": true, "0/40/18": "mock-temperature-sensor", "0/40/19": { - "caseSessionsPerFabric": 3, - "subscriptionsPerFabric": 3 + "0": 3, + "1": 3 }, "0/40/65532": 0, "0/40/65533": 1, @@ -61,8 +61,8 @@ "1/3/65531": [0, 1, 65528, 65529, 65531, 65532, 65533], "1/29/0": [ { - "deviceType": 263, - "revision": 1 + "0": 263, + "1": 1 } ], "1/29/1": [ diff --git a/tests/components/matter/fixtures/nodes/on-off-plugin-unit.json b/tests/components/matter/fixtures/nodes/on-off-plugin-unit.json index 43ba486bc291..8d523f5443a9 100644 --- a/tests/components/matter/fixtures/nodes/on-off-plugin-unit.json +++ b/tests/components/matter/fixtures/nodes/on-off-plugin-unit.json @@ -6,8 +6,8 @@ "attributes": { "0/29/0": [ { - "deviceType": 22, - "revision": 1 + "0": 22, + "1": 1 } ], "0/29/1": [ @@ -41,8 +41,8 @@ "0/40/17": true, "0/40/18": "mock-onoff-plugin-unit", "0/40/19": { - "caseSessionsPerFabric": 3, - "subscriptionsPerFabric": 3 + "0": 3, + "1": 3 }, "0/40/65532": 0, "0/40/65533": 1, @@ -118,8 +118,8 @@ ], "1/29/0": [ { - "deviceType": 266, - "revision": 1 + "0": 266, + "1": 1 } ], "1/29/1": [ diff --git a/tests/components/matter/fixtures/nodes/onoff-light-alt-name.json b/tests/components/matter/fixtures/nodes/onoff-light-alt-name.json index f29361da1281..3f6e83ca460b 100644 --- a/tests/components/matter/fixtures/nodes/onoff-light-alt-name.json +++ b/tests/components/matter/fixtures/nodes/onoff-light-alt-name.json @@ -29,11 +29,11 @@ "0/29/65531": [0, 1, 2, 3, 65528, 65529, 65531, 65532, 65533], "0/31/0": [ { - "privilege": 5, - "authMode": 2, - "subjects": [112233], - "targets": null, - "fabricIndex": 1 + "1": 5, + "2": 2, + "3": [112233], + "4": null, + "254": 1 } ], "0/31/1": [], @@ -65,8 +65,8 @@ "0/40/17": true, "0/40/18": "mock-onoff-light", "0/40/19": { - "caseSessionsPerFabric": 3, - "subscriptionsPerFabric": 3 + "0": 3, + "1": 3 }, "0/40/65532": 0, "0/40/65533": 1, @@ -111,8 +111,8 @@ "0/44/65531": [0, 1, 2, 65528, 65529, 65531, 65532, 65533], "0/48/0": 0, "0/48/1": { - "failSafeExpiryLengthSeconds": 60, - "maxCumulativeFailsafeSeconds": 900 + "0": 60, + "1": 900 }, "0/48/2": 0, "0/48/3": 0, @@ -125,8 +125,8 @@ "0/49/0": 1, "0/49/1": [ { - "networkID": "", - "connected": true + "0": "", + "1": true } ], "0/49/2": 10, @@ -147,14 +147,14 @@ "0/50/65531": [65528, 65529, 65531, 65532, 65533], "0/51/0": [ { - "name": "WIFI_STA_DEF", - "isOperational": true, - "offPremiseServicesReachableIPv4": null, - "offPremiseServicesReachableIPv6": null, - "hardwareAddress": "", - "IPv4Addresses": [""], - "IPv6Addresses": [], - "type": 1 + "0": "WIFI_STA_DEF", + "1": true, + "2": null, + "3": null, + "4": "", + "5": [""], + "6": [], + "7": 1 } ], "0/51/1": 6, @@ -243,19 +243,19 @@ "0/60/65531": [0, 1, 2, 65528, 65529, 65531, 65532, 65533], "0/62/0": [ { - "noc": "", - "icac": "", - "fabricIndex": 1 + "1": "", + "2": "", + "254": 1 } ], "0/62/1": [ { - "rootPublicKey": "", - "vendorId": 65521, - "fabricId": 1, - "nodeId": 1, - "label": "", - "fabricIndex": 1 + "1": "", + "2": 65521, + "3": 1, + "4": 1, + "5": "", + "254": 1 } ], "0/62/2": 5, @@ -278,20 +278,20 @@ "0/63/65531": [0, 1, 2, 3, 65528, 65529, 65531, 65532, 65533], "0/64/0": [ { - "label": "room", - "value": "bedroom 2" + "0": "room", + "1": "bedroom 2" }, { - "label": "orientation", - "value": "North" + "0": "orientation", + "1": "North" }, { - "label": "floor", - "value": "2" + "0": "floor", + "1": "2" }, { - "label": "direction", - "value": "up" + "0": "direction", + "1": "up" } ], "0/64/65532": 0, diff --git a/tests/components/matter/fixtures/nodes/onoff-light-no-name.json b/tests/components/matter/fixtures/nodes/onoff-light-no-name.json index 8a1134409a9a..18cb68c8926c 100644 --- a/tests/components/matter/fixtures/nodes/onoff-light-no-name.json +++ b/tests/components/matter/fixtures/nodes/onoff-light-no-name.json @@ -29,11 +29,11 @@ "0/29/65531": [0, 1, 2, 3, 65528, 65529, 65531, 65532, 65533], "0/31/0": [ { - "privilege": 5, - "authMode": 2, - "subjects": [112233], - "targets": null, - "fabricIndex": 1 + "1": 5, + "2": 2, + "3": [112233], + "4": null, + "254": 1 } ], "0/31/1": [], @@ -65,8 +65,8 @@ "0/40/17": true, "0/40/18": "mock-onoff-light", "0/40/19": { - "caseSessionsPerFabric": 3, - "subscriptionsPerFabric": 3 + "0": 3, + "1": 3 }, "0/40/65532": 0, "0/40/65533": 1, @@ -111,8 +111,8 @@ "0/44/65531": [0, 1, 2, 65528, 65529, 65531, 65532, 65533], "0/48/0": 0, "0/48/1": { - "failSafeExpiryLengthSeconds": 60, - "maxCumulativeFailsafeSeconds": 900 + "0": 60, + "1": 900 }, "0/48/2": 0, "0/48/3": 0, @@ -125,8 +125,8 @@ "0/49/0": 1, "0/49/1": [ { - "networkID": "", - "connected": true + "0": "", + "1": true } ], "0/49/2": 10, @@ -147,14 +147,14 @@ "0/50/65531": [65528, 65529, 65531, 65532, 65533], "0/51/0": [ { - "name": "WIFI_STA_DEF", - "isOperational": true, - "offPremiseServicesReachableIPv4": null, - "offPremiseServicesReachableIPv6": null, - "hardwareAddress": "", - "IPv4Addresses": [""], - "IPv6Addresses": [], - "type": 1 + "0": "WIFI_STA_DEF", + "1": true, + "2": null, + "3": null, + "4": "", + "5": [""], + "6": [], + "7": 1 } ], "0/51/1": 6, @@ -243,19 +243,19 @@ "0/60/65531": [0, 1, 2, 65528, 65529, 65531, 65532, 65533], "0/62/0": [ { - "noc": "", - "icac": "", - "fabricIndex": 1 + "1": "", + "2": "", + "254": 1 } ], "0/62/1": [ { - "rootPublicKey": "", - "vendorId": 65521, - "fabricId": 1, - "nodeId": 1, - "label": "", - "fabricIndex": 1 + "1": "", + "2": 65521, + "3": 1, + "4": 1, + "5": "", + "254": 1 } ], "0/62/2": 5, @@ -278,20 +278,20 @@ "0/63/65531": [0, 1, 2, 3, 65528, 65529, 65531, 65532, 65533], "0/64/0": [ { - "label": "room", - "value": "bedroom 2" + "0": "room", + "1": "bedroom 2" }, { - "label": "orientation", - "value": "North" + "0": "orientation", + "1": "North" }, { - "label": "floor", - "value": "2" + "0": "floor", + "1": "2" }, { - "label": "direction", - "value": "up" + "0": "direction", + "1": "up" } ], "0/64/65532": 0, diff --git a/tests/components/matter/fixtures/nodes/onoff-light.json b/tests/components/matter/fixtures/nodes/onoff-light.json index 65ef0be5c8e9..eed404ff85d2 100644 --- a/tests/components/matter/fixtures/nodes/onoff-light.json +++ b/tests/components/matter/fixtures/nodes/onoff-light.json @@ -12,8 +12,8 @@ "0/4/65531": [0, 65528, 65529, 65531, 65532, 65533], "0/29/0": [ { - "deviceType": 22, - "revision": 1 + "0": 22, + "1": 1 } ], "0/29/1": [ @@ -29,11 +29,11 @@ "0/29/65531": [0, 1, 2, 3, 65528, 65529, 65531, 65532, 65533], "0/31/0": [ { - "privilege": 5, - "authMode": 2, - "subjects": [112233], - "targets": null, - "fabricIndex": 1 + "1": 5, + "2": 2, + "3": [112233], + "4": null, + "254": 1 } ], "0/31/1": [], @@ -65,8 +65,8 @@ "0/40/17": true, "0/40/18": "mock-onoff-light", "0/40/19": { - "caseSessionsPerFabric": 3, - "subscriptionsPerFabric": 3 + "0": 3, + "1": 3 }, "0/40/65532": 0, "0/40/65533": 1, @@ -111,8 +111,8 @@ "0/44/65531": [0, 1, 2, 65528, 65529, 65531, 65532, 65533], "0/48/0": 0, "0/48/1": { - "failSafeExpiryLengthSeconds": 60, - "maxCumulativeFailsafeSeconds": 900 + "0": 60, + "1": 900 }, "0/48/2": 0, "0/48/3": 0, @@ -125,8 +125,8 @@ "0/49/0": 1, "0/49/1": [ { - "networkID": "", - "connected": true + "0": "", + "1": true } ], "0/49/2": 10, @@ -147,14 +147,14 @@ "0/50/65531": [65528, 65529, 65531, 65532, 65533], "0/51/0": [ { - "name": "WIFI_STA_DEF", - "isOperational": true, - "offPremiseServicesReachableIPv4": null, - "offPremiseServicesReachableIPv6": null, - "hardwareAddress": "", - "IPv4Addresses": [""], - "IPv6Addresses": [], - "type": 1 + "0": "WIFI_STA_DEF", + "1": true, + "2": null, + "3": null, + "4": "", + "5": [""], + "6": [], + "7": 1 } ], "0/51/1": 6, @@ -243,19 +243,19 @@ "0/60/65531": [0, 1, 2, 65528, 65529, 65531, 65532, 65533], "0/62/0": [ { - "noc": "", - "icac": "", - "fabricIndex": 1 + "1": "", + "2": "", + "254": 1 } ], "0/62/1": [ { - "rootPublicKey": "", - "vendorId": 65521, - "fabricId": 1, - "nodeId": 1, - "label": "", - "fabricIndex": 1 + "1": "", + "2": 65521, + "3": 1, + "4": 1, + "5": "", + "254": 1 } ], "0/62/2": 5, @@ -278,20 +278,20 @@ "0/63/65531": [0, 1, 2, 3, 65528, 65529, 65531, 65532, 65533], "0/64/0": [ { - "label": "room", - "value": "bedroom 2" + "0": "room", + "1": "bedroom 2" }, { - "label": "orientation", - "value": "North" + "0": "orientation", + "1": "North" }, { - "label": "floor", - "value": "2" + "0": "floor", + "1": "2" }, { - "label": "direction", - "value": "up" + "0": "direction", + "1": "up" } ], "0/64/65532": 0, @@ -354,8 +354,8 @@ ], "1/29/0": [ { - "deviceType": 257, - "revision": 1 + "0": 257, + "1": 1 } ], "1/29/1": [3, 4, 6, 8, 29, 768, 1030], diff --git a/tests/components/matter/fixtures/nodes/pressure-sensor.json b/tests/components/matter/fixtures/nodes/pressure-sensor.json index a47cda28056f..d38ac560ac51 100644 --- a/tests/components/matter/fixtures/nodes/pressure-sensor.json +++ b/tests/components/matter/fixtures/nodes/pressure-sensor.json @@ -6,8 +6,8 @@ "attributes": { "0/29/0": [ { - "deviceType": 22, - "revision": 1 + "0": 22, + "1": 1 } ], "0/29/1": [ @@ -41,8 +41,8 @@ "0/40/17": true, "0/40/18": "mock-pressure-sensor", "0/40/19": { - "caseSessionsPerFabric": 3, - "subscriptionsPerFabric": 3 + "0": 3, + "1": 3 }, "0/40/65532": 0, "0/40/65533": 1, @@ -56,8 +56,8 @@ "1/3/65531": [0, 1, 65528, 65529, 65531, 65532, 65533], "1/29/0": [ { - "deviceType": 773, - "revision": 1 + "0": 773, + "1": 1 } ], "1/29/1": [6, 29, 57, 768, 8, 40], diff --git a/tests/components/matter/fixtures/nodes/switch-unit.json b/tests/components/matter/fixtures/nodes/switch-unit.json index ceed22d2524c..e16f1e406ec9 100644 --- a/tests/components/matter/fixtures/nodes/switch-unit.json +++ b/tests/components/matter/fixtures/nodes/switch-unit.json @@ -6,8 +6,8 @@ "attributes": { "0/29/0": [ { - "deviceType": 99999, - "revision": 1 + "0": 99999, + "1": 1 } ], "0/29/1": [ @@ -41,8 +41,8 @@ "0/40/17": true, "0/40/18": "mock-switch-unit", "0/40/19": { - "caseSessionsPerFabric": 3, - "subscriptionsPerFabric": 3 + "0": 3, + "1": 3 }, "0/40/65532": 0, "0/40/65533": 1, @@ -96,8 +96,8 @@ "1/7/65531": [0, 16, 65528, 65529, 65531, 65532, 65533], "1/29/0": [ { - "deviceType": 9999999, - "revision": 1 + "0": 9999999, + "1": 1 } ], "1/29/1": [ diff --git a/tests/components/matter/fixtures/nodes/temperature-sensor.json b/tests/components/matter/fixtures/nodes/temperature-sensor.json index c7d372ac2d74..0abb366f81b0 100644 --- a/tests/components/matter/fixtures/nodes/temperature-sensor.json +++ b/tests/components/matter/fixtures/nodes/temperature-sensor.json @@ -6,8 +6,8 @@ "attributes": { "0/29/0": [ { - "deviceType": 22, - "revision": 1 + "0": 22, + "1": 1 } ], "0/29/1": [ @@ -41,8 +41,8 @@ "0/40/17": true, "0/40/18": "mock-temperature-sensor", "0/40/19": { - "caseSessionsPerFabric": 3, - "subscriptionsPerFabric": 3 + "0": 3, + "1": 3 }, "0/40/65532": 0, "0/40/65533": 1, @@ -61,8 +61,8 @@ "1/3/65531": [0, 1, 65528, 65529, 65531, 65532, 65533], "1/29/0": [ { - "deviceType": 770, - "revision": 1 + "0": 770, + "1": 1 } ], "1/29/1": [6, 29, 57, 768, 8, 40], diff --git a/tests/components/matter/fixtures/nodes/thermostat.json b/tests/components/matter/fixtures/nodes/thermostat.json index 85ac42e54291..a7abff41331b 100644 --- a/tests/components/matter/fixtures/nodes/thermostat.json +++ b/tests/components/matter/fixtures/nodes/thermostat.json @@ -8,8 +8,8 @@ "attributes": { "0/29/0": [ { - "deviceType": 22, - "revision": 1 + "0": 22, + "1": 1 } ], "0/29/1": [29, 31, 40, 42, 48, 49, 50, 51, 54, 60, 62, 63, 64], @@ -22,18 +22,18 @@ "0/29/65531": [0, 1, 2, 3, 65528, 65529, 65531, 65532, 65533], "0/31/0": [ { - "privilege": 0, - "authMode": 0, - "subjects": null, - "targets": null, - "fabricIndex": 1 + "1": 0, + "2": 0, + "3": null, + "4": null, + "254": 1 }, { - "privilege": 5, - "authMode": 2, - "subjects": [112233], - "targets": null, - "fabricIndex": 2 + "1": 5, + "2": 2, + "3": [112233], + "4": null, + "254": 2 } ], "0/31/1": [], @@ -64,8 +64,8 @@ "0/40/17": true, "0/40/18": "3D06D025F9E026A0", "0/40/19": { - "caseSessionsPerFabric": 3, - "subscriptionsPerFabric": 3 + "0": 3, + "1": 3 }, "0/40/65532": 0, "0/40/65533": 1, @@ -86,8 +86,8 @@ "0/42/65531": [0, 1, 2, 3, 65528, 65529, 65531, 65532, 65533], "0/48/0": 0, "0/48/1": { - "failSafeExpiryLengthSeconds": 60, - "maxCumulativeFailsafeSeconds": 900 + "0": 60, + "1": 900 }, "0/48/2": 0, "0/48/3": 0, @@ -100,8 +100,8 @@ "0/49/0": 1, "0/49/1": [ { - "networkID": "TE9OR0FOLUlPVA==", - "connected": true + "0": "TE9OR0FOLUlPVA==", + "1": true } ], "0/49/2": 10, @@ -122,18 +122,18 @@ "0/50/65531": [65528, 65529, 65531, 65532, 65533], "0/51/0": [ { - "name": "WIFI_STA_DEF", - "isOperational": true, - "offPremiseServicesReachableIPv4": null, - "offPremiseServicesReachableIPv6": null, - "hardwareAddress": "3FR1X7qs", - "IPv4Addresses": ["wKgI7g=="], - "IPv6Addresses": [ + "0": "WIFI_STA_DEF", + "1": true, + "2": null, + "3": null, + "4": "3FR1X7qs", + "5": ["wKgI7g=="], + "6": [ "/oAAAAAAAADeVHX//l+6rA==", "JA4DsgZ9jUDeVHX//l+6rA==", "/UgvJAe/AADeVHX//l+6rA==" ], - "type": 1 + "7": 1 } ], "0/51/1": 4, @@ -182,32 +182,32 @@ "0/60/65531": [0, 1, 2, 65528, 65529, 65531, 65532, 65533], "0/62/0": [ { - "noc": "", - "icac": null, - "fabricIndex": 1 + "1": "", + "2": null, + "254": 1 }, { - "noc": "FTABAQEkAgE3AyQTAhgmBIAigScmBYAlTTo3BiQVASQRBBgkBwEkCAEwCUEETaqdhs6MRkbh8fdh4EEImZaziiE6anaVp6Mu3P/zIJUB0fHUMxydKRTAC8bIn7vUhBCM47OYlYTkX0zFhoKYrzcKNQEoARgkAgE2AwQCBAEYMAQUrouBLuksQTkLrFhNVAbTHkNvMSEwBRTPlgMACvPdpqPOzuvR0OfPgfUcxBgwC0AcUInETXp/2gIFGDQF2+u+9WtYtvIfo6C3MhoOIV1SrRBZWYxY3CVjPGK7edTibQrVA4GccZKnHhNSBjxktrPiGA==", - "icac": "FTABAQEkAgE3AyQUARgmBIAigScmBYAlTTo3BiQTAhgkBwEkCAEwCUEE+rI5XQyifTZbZRK1Z2DOuXdQkmdUkWklTv+G1x4ZfbSupbUDo4l7i/iFdyu//uJThAw1GPEkWe6i98IFKCOQpzcKNQEpARgkAmAwBBTPlgMACvPdpqPOzuvR0OfPgfUcxDAFFJQo6UEBWTLtZVYFZwRBgn+qstpTGDALQK3jYiaxwnYJMwTBQlcVNrGxPtuVTZrp5foZtQCp/JEX2ZWqVxKypilx0ES/CfMHZ0Lllv9QsLs8xV/HNLidllkY", - "fabricIndex": 2 + "1": "FTABAQEkAgE3AyQTAhgmBIAigScmBYAlTTo3BiQVASQRBBgkBwEkCAEwCUEETaqdhs6MRkbh8fdh4EEImZaziiE6anaVp6Mu3P/zIJUB0fHUMxydKRTAC8bIn7vUhBCM47OYlYTkX0zFhoKYrzcKNQEoARgkAgE2AwQCBAEYMAQUrouBLuksQTkLrFhNVAbTHkNvMSEwBRTPlgMACvPdpqPOzuvR0OfPgfUcxBgwC0AcUInETXp/2gIFGDQF2+u+9WtYtvIfo6C3MhoOIV1SrRBZWYxY3CVjPGK7edTibQrVA4GccZKnHhNSBjxktrPiGA==", + "2": "FTABAQEkAgE3AyQUARgmBIAigScmBYAlTTo3BiQTAhgkBwEkCAEwCUEE+rI5XQyifTZbZRK1Z2DOuXdQkmdUkWklTv+G1x4ZfbSupbUDo4l7i/iFdyu//uJThAw1GPEkWe6i98IFKCOQpzcKNQEpARgkAmAwBBTPlgMACvPdpqPOzuvR0OfPgfUcxDAFFJQo6UEBWTLtZVYFZwRBgn+qstpTGDALQK3jYiaxwnYJMwTBQlcVNrGxPtuVTZrp5foZtQCp/JEX2ZWqVxKypilx0ES/CfMHZ0Lllv9QsLs8xV/HNLidllkY", + "254": 2 } ], "0/62/1": [ { - "rootPublicKey": "BAP9BJt5aQ9N98ClPTdNxpMZ1/Vh8r9usw6C8Ygi79AImsJq4UjAaYad0UI9Lh0OmRA9sWE2aSPbHjf409i/970=", - "vendorID": 4996, - "fabricID": 1, - "nodeID": 1425709672, - "label": "", - "fabricIndex": 1 + "1": "BAP9BJt5aQ9N98ClPTdNxpMZ1/Vh8r9usw6C8Ygi79AImsJq4UjAaYad0UI9Lh0OmRA9sWE2aSPbHjf409i/970=", + "2": 4996, + "3": 1, + "4": 1425709672, + "5": "", + "254": 1 }, { - "rootPublicKey": "BJXfyipMp+Jx4pkoTnvYoAYODis4xJktKdQXu8MSpBLIwII58BD0KkIG9NmuHcp0xUQKzqlfyB/bkAanevO73ZI=", - "vendorID": 65521, - "fabricID": 1, - "nodeID": 4, - "label": "", - "fabricIndex": 2 + "1": "BJXfyipMp+Jx4pkoTnvYoAYODis4xJktKdQXu8MSpBLIwII58BD0KkIG9NmuHcp0xUQKzqlfyB/bkAanevO73ZI=", + "2": 65521, + "3": 1, + "4": 4, + "5": "", + "254": 2 } ], "0/62/2": 5, @@ -233,20 +233,20 @@ "0/63/65531": [0, 1, 2, 3, 65528, 65529, 65531, 65532, 65533], "0/64/0": [ { - "label": "room", - "value": "bedroom 2" + "0": "room", + "1": "bedroom 2" }, { - "label": "orientation", - "value": "North" + "0": "orientation", + "1": "North" }, { - "label": "floor", - "value": "2" + "0": "floor", + "1": "2" }, { - "label": "direction", - "value": "up" + "0": "direction", + "1": "up" } ], "0/64/65532": 0, @@ -275,8 +275,8 @@ "1/6/65531": [0, 65528, 65529, 65531, 65532, 65533], "1/29/0": [ { - "deviceType": 769, - "revision": 1 + "0": 769, + "1": 1 } ], "1/29/1": [3, 4, 6, 29, 30, 64, 513, 514, 516], @@ -295,20 +295,20 @@ "1/30/65531": [0, 65528, 65529, 65531, 65532, 65533], "1/64/0": [ { - "label": "room", - "value": "bedroom 2" + "0": "room", + "1": "bedroom 2" }, { - "label": "orientation", - "value": "North" + "0": "orientation", + "1": "North" }, { - "label": "floor", - "value": "2" + "0": "floor", + "1": "2" }, { - "label": "direction", - "value": "up" + "0": "direction", + "1": "up" } ], "1/64/65532": 0, diff --git a/tests/components/matter/fixtures/nodes/window-covering_full.json b/tests/components/matter/fixtures/nodes/window-covering_full.json index feb75409526f..fc6efe2077c0 100644 --- a/tests/components/matter/fixtures/nodes/window-covering_full.json +++ b/tests/components/matter/fixtures/nodes/window-covering_full.json @@ -8,8 +8,8 @@ "0/29/65533": 1, "0/29/0": [ { - "deviceType": 22, - "revision": 1 + "0": 22, + "1": 1 } ], "0/29/1": [29, 31, 40, 48, 49, 51, 60, 62, 63, 54], @@ -22,25 +22,25 @@ "0/31/65533": 1, "0/31/0": [ { - "privilege": 0, - "authMode": 0, - "subjects": null, - "targets": null, - "fabricIndex": 1 + "1": 0, + "2": 0, + "3": null, + "4": null, + "254": 1 }, { - "privilege": 0, - "authMode": 0, - "subjects": null, - "targets": null, - "fabricIndex": 2 + "1": 0, + "2": 0, + "3": null, + "4": null, + "254": 2 }, { - "privilege": 5, - "authMode": 2, - "subjects": [112233], - "targets": null, - "fabricIndex": 3 + "1": 5, + "2": 2, + "3": [112233], + "4": null, + "254": 3 } ], "0/31/2": 4, @@ -71,8 +71,8 @@ "0/40/17": true, "0/40/18": "mock-full-window-covering", "0/40/19": { - "caseSessionsPerFabric": 3, - "subscriptionsPerFabric": 3 + "0": 3, + "1": 3 }, "0/40/65533": 1, "0/40/65528": [], @@ -84,8 +84,8 @@ "0/48/2": 0, "0/48/3": 0, "0/48/1": { - "failSafeExpiryLengthSeconds": 60, - "maxCumulativeFailsafeSeconds": 900 + "0": 60, + "1": 900 }, "0/48/4": true, "0/48/65533": 1, @@ -96,8 +96,8 @@ "0/49/0": 1, "0/49/1": [ { - "networkID": "MTI2MDk5", - "connected": true + "0": "MTI2MDk5", + "1": true } ], "0/49/2": 10, @@ -113,14 +113,14 @@ "0/49/65531": [0, 1, 2, 3, 4, 5, 6, 7, 65528, 65529, 65531, 65532, 65533], "0/51/0": [ { - "name": "WIFI_STA_DEF", - "isOperational": true, - "offPremiseServicesReachableIPv4": null, - "offPremiseServicesReachableIPv6": null, - "hardwareAddress": "JG8olrDo", - "IPv4Addresses": ["wKgBFw=="], - "IPv6Addresses": ["/oAAAAAAAAAmbyj//paw6A=="], - "type": 1 + "0": "WIFI_STA_DEF", + "1": true, + "2": null, + "3": null, + "4": "JG8olrDo", + "5": ["wKgBFw=="], + "6": ["/oAAAAAAAAAmbyj//paw6A=="], + "7": 1 } ], "0/51/1": 1, @@ -141,47 +141,47 @@ "0/62/65532": 0, "0/62/0": [ { - "noc": "", - "icac": null, - "fabricIndex": 1 + "1": "", + "2": null, + "254": 1 }, { - "noc": "", - "icac": null, - "fabricIndex": 2 + "1": "", + "2": null, + "254": 2 }, { - "noc": "FTABAQEkAgE3AyQTAhgmBIAigScmBYAlTTo3BiQVAiQRMhgkBwEkCAEwCUEE+5TLtucQZ8l7Y5r8nKhYB0mia0RMn+RJa5AtRIPb2R9ixMcQXfQBANdHPCwsfTGWyjBYzPXG1yDUTUz+Z1J9aTcKNQEoARgkAgE2AwQCBAEYMAQUh/lTccn18xJ1JqA9VRHdr2+IhscwBRTPeGj+EyBBTsdlJC4zNSP/tIcpFhgwC0AoRjZKvJRkg+Cz77N6+IIQBt0i1Oco92N/XzoDWtgUVIOW5qvPcUUI/tiYAEDdefy2/6XpjU1Y7ecN3vgoTdNUGA==", - "icac": "FTABAQEkAgE3AyQUARgmBIAigScmBYAlTTo3BiQTAhgkBwEkCAEwCUEEL6dfjjyZxKHsFjZvYUOhWsOCI/2ucOxcCZGFaJwG0vXhL5/aDhR/AF907lF93LR1Huvp3NJsB0oxqsNnbEz8jjcKNQEpARgkAmAwBBTPeGj+EyBBTsdlJC4zNSP/tIcpFjAFFC8Br9IClyBL3e7po3G+QXNGsBoYGDALQIHEwwdIaYHnFzpYngW9g+7Cn3gl0qKnetK5gWUVVTdVtpx6dYBblvPnOU+5K3Ow85llzcRxU1yXgPAM77s7t8gY", - "fabricIndex": 3 + "1": "FTABAQEkAgE3AyQTAhgmBIAigScmBYAlTTo3BiQVAiQRMhgkBwEkCAEwCUEE+5TLtucQZ8l7Y5r8nKhYB0mia0RMn+RJa5AtRIPb2R9ixMcQXfQBANdHPCwsfTGWyjBYzPXG1yDUTUz+Z1J9aTcKNQEoARgkAgE2AwQCBAEYMAQUh/lTccn18xJ1JqA9VRHdr2+IhscwBRTPeGj+EyBBTsdlJC4zNSP/tIcpFhgwC0AoRjZKvJRkg+Cz77N6+IIQBt0i1Oco92N/XzoDWtgUVIOW5qvPcUUI/tiYAEDdefy2/6XpjU1Y7ecN3vgoTdNUGA==", + "2": "FTABAQEkAgE3AyQUARgmBIAigScmBYAlTTo3BiQTAhgkBwEkCAEwCUEEL6dfjjyZxKHsFjZvYUOhWsOCI/2ucOxcCZGFaJwG0vXhL5/aDhR/AF907lF93LR1Huvp3NJsB0oxqsNnbEz8jjcKNQEpARgkAmAwBBTPeGj+EyBBTsdlJC4zNSP/tIcpFjAFFC8Br9IClyBL3e7po3G+QXNGsBoYGDALQIHEwwdIaYHnFzpYngW9g+7Cn3gl0qKnetK5gWUVVTdVtpx6dYBblvPnOU+5K3Ow85llzcRxU1yXgPAM77s7t8gY", + "254": 3 } ], "0/62/2": 5, "0/62/3": 3, "0/62/1": [ { - "rootPublicKey": "BFs332VJwg3I1yKmuKy2YKinZM57r2xsIk9+6ENJaErX2An/ZQAz0VJ9zx+6rGqcOti0HtrJCfe1x2D9VCyJI3U=", - "vendorId": 24582, - "fabricId": 7331465149450221740, - "nodeId": 3429688654, - "label": "", - "fabricIndex": 1 + "1": "BFs332VJwg3I1yKmuKy2YKinZM57r2xsIk9+6ENJaErX2An/ZQAz0VJ9zx+6rGqcOti0HtrJCfe1x2D9VCyJI3U=", + "2": 24582, + "3": 7331465149450221740, + "4": 3429688654, + "5": "", + "254": 1 }, { - "rootPublicKey": "BJyJ1DODbJ+HellxuG3J/EstNpyw/i5h1x5qjNLQjwnPZoEaLLMZ8KKN7/rxQy3JUIkfuQydJz7JXeF80mES8q8=", - "vendorId": 4362, - "fabricId": 8516517930550670493, - "nodeId": 1443093566726981311, - "label": "", - "fabricIndex": 2 + "1": "BJyJ1DODbJ+HellxuG3J/EstNpyw/i5h1x5qjNLQjwnPZoEaLLMZ8KKN7/rxQy3JUIkfuQydJz7JXeF80mES8q8=", + "2": 4362, + "3": 8516517930550670493, + "4": 1443093566726981311, + "5": "", + "254": 2 }, { - "rootPublicKey": "BFOpRqEk+HJ6n/NtUtaWTQVVwstz9QRDK2xvRP6qKZKX3Rk05Zie5Ux9PdjgE1K5zE9NIP2jHHcVJjRBVZxNFz0=", - "vendorId": 4939, - "fabricId": 2, - "nodeId": 50, - "label": "", - "fabricIndex": 3 + "1": "BFOpRqEk+HJ6n/NtUtaWTQVVwstz9QRDK2xvRP6qKZKX3Rk05Zie5Ux9PdjgE1K5zE9NIP2jHHcVJjRBVZxNFz0=", + "2": 4939, + "3": 2, + "4": 50, + "5": "", + "254": 3 } ], "0/62/4": [ @@ -216,8 +216,8 @@ "1/29/65533": 1, "1/29/0": [ { - "deviceType": 514, - "revision": 2 + "0": 514, + "1": 2 } ], "1/29/1": [29, 3, 258], diff --git a/tests/components/matter/fixtures/nodes/window-covering_lift.json b/tests/components/matter/fixtures/nodes/window-covering_lift.json index afc2a2f734f6..9c58869e988c 100644 --- a/tests/components/matter/fixtures/nodes/window-covering_lift.json +++ b/tests/components/matter/fixtures/nodes/window-covering_lift.json @@ -8,8 +8,8 @@ "0/29/65533": 1, "0/29/0": [ { - "deviceType": 22, - "revision": 1 + "0": 22, + "1": 1 } ], "0/29/1": [29, 31, 40, 48, 49, 51, 60, 62, 63, 54], @@ -22,25 +22,25 @@ "0/31/65533": 1, "0/31/0": [ { - "privilege": 0, - "authMode": 0, - "subjects": null, - "targets": null, - "fabricIndex": 1 + "1": 0, + "2": 0, + "3": null, + "4": null, + "254": 1 }, { - "privilege": 0, - "authMode": 0, - "subjects": null, - "targets": null, - "fabricIndex": 2 + "1": 0, + "2": 0, + "3": null, + "4": null, + "254": 2 }, { - "privilege": 5, - "authMode": 2, - "subjects": [112233], - "targets": null, - "fabricIndex": 3 + "1": 5, + "2": 2, + "3": [112233], + "4": null, + "254": 3 } ], "0/31/2": 4, @@ -71,8 +71,8 @@ "0/40/17": true, "0/40/18": "mock-lift-window-covering", "0/40/19": { - "caseSessionsPerFabric": 3, - "subscriptionsPerFabric": 3 + "0": 3, + "1": 3 }, "0/40/65533": 1, "0/40/65528": [], @@ -84,8 +84,8 @@ "0/48/2": 0, "0/48/3": 0, "0/48/1": { - "failSafeExpiryLengthSeconds": 60, - "maxCumulativeFailsafeSeconds": 900 + "0": 60, + "1": 900 }, "0/48/4": true, "0/48/65533": 1, @@ -96,8 +96,8 @@ "0/49/0": 1, "0/49/1": [ { - "networkID": "MTI2MDk5", - "connected": true + "0": "MTI2MDk5", + "1": true } ], "0/49/2": 10, @@ -113,14 +113,14 @@ "0/49/65531": [0, 1, 2, 3, 4, 5, 6, 7, 65528, 65529, 65531, 65532, 65533], "0/51/0": [ { - "name": "WIFI_STA_DEF", - "isOperational": true, - "offPremiseServicesReachableIPv4": null, - "offPremiseServicesReachableIPv6": null, - "hardwareAddress": "JG8olrDo", - "IPv4Addresses": ["wKgBFw=="], - "IPv6Addresses": ["/oAAAAAAAAAmbyj//paw6A=="], - "type": 1 + "0": "WIFI_STA_DEF", + "1": true, + "2": null, + "3": null, + "4": "JG8olrDo", + "5": ["wKgBFw=="], + "6": ["/oAAAAAAAAAmbyj//paw6A=="], + "7": 1 } ], "0/51/1": 1, @@ -141,47 +141,47 @@ "0/62/65532": 0, "0/62/0": [ { - "noc": "", - "icac": null, - "fabricIndex": 1 + "1": "", + "2": null, + "254": 1 }, { - "noc": "", - "icac": null, - "fabricIndex": 2 + "1": "", + "2": null, + "254": 2 }, { - "noc": "FTABAQEkAgE3AyQTAhgmBIAigScmBYAlTTo3BiQVAiQRMhgkBwEkCAEwCUEE+5TLtucQZ8l7Y5r8nKhYB0mia0RMn+RJa5AtRIPb2R9ixMcQXfQBANdHPCwsfTGWyjBYzPXG1yDUTUz+Z1J9aTcKNQEoARgkAgE2AwQCBAEYMAQUh/lTccn18xJ1JqA9VRHdr2+IhscwBRTPeGj+EyBBTsdlJC4zNSP/tIcpFhgwC0AoRjZKvJRkg+Cz77N6+IIQBt0i1Oco92N/XzoDWtgUVIOW5qvPcUUI/tiYAEDdefy2/6XpjU1Y7ecN3vgoTdNUGA==", - "icac": "FTABAQEkAgE3AyQUARgmBIAigScmBYAlTTo3BiQTAhgkBwEkCAEwCUEEL6dfjjyZxKHsFjZvYUOhWsOCI/2ucOxcCZGFaJwG0vXhL5/aDhR/AF907lF93LR1Huvp3NJsB0oxqsNnbEz8jjcKNQEpARgkAmAwBBTPeGj+EyBBTsdlJC4zNSP/tIcpFjAFFC8Br9IClyBL3e7po3G+QXNGsBoYGDALQIHEwwdIaYHnFzpYngW9g+7Cn3gl0qKnetK5gWUVVTdVtpx6dYBblvPnOU+5K3Ow85llzcRxU1yXgPAM77s7t8gY", - "fabricIndex": 3 + "1": "FTABAQEkAgE3AyQTAhgmBIAigScmBYAlTTo3BiQVAiQRMhgkBwEkCAEwCUEE+5TLtucQZ8l7Y5r8nKhYB0mia0RMn+RJa5AtRIPb2R9ixMcQXfQBANdHPCwsfTGWyjBYzPXG1yDUTUz+Z1J9aTcKNQEoARgkAgE2AwQCBAEYMAQUh/lTccn18xJ1JqA9VRHdr2+IhscwBRTPeGj+EyBBTsdlJC4zNSP/tIcpFhgwC0AoRjZKvJRkg+Cz77N6+IIQBt0i1Oco92N/XzoDWtgUVIOW5qvPcUUI/tiYAEDdefy2/6XpjU1Y7ecN3vgoTdNUGA==", + "2": "FTABAQEkAgE3AyQUARgmBIAigScmBYAlTTo3BiQTAhgkBwEkCAEwCUEEL6dfjjyZxKHsFjZvYUOhWsOCI/2ucOxcCZGFaJwG0vXhL5/aDhR/AF907lF93LR1Huvp3NJsB0oxqsNnbEz8jjcKNQEpARgkAmAwBBTPeGj+EyBBTsdlJC4zNSP/tIcpFjAFFC8Br9IClyBL3e7po3G+QXNGsBoYGDALQIHEwwdIaYHnFzpYngW9g+7Cn3gl0qKnetK5gWUVVTdVtpx6dYBblvPnOU+5K3Ow85llzcRxU1yXgPAM77s7t8gY", + "254": 3 } ], "0/62/2": 5, "0/62/3": 3, "0/62/1": [ { - "rootPublicKey": "BFs332VJwg3I1yKmuKy2YKinZM57r2xsIk9+6ENJaErX2An/ZQAz0VJ9zx+6rGqcOti0HtrJCfe1x2D9VCyJI3U=", - "vendorId": 24582, - "fabricId": 7331465149450221740, - "nodeId": 3429688654, - "label": "", - "fabricIndex": 1 + "1": "BFs332VJwg3I1yKmuKy2YKinZM57r2xsIk9+6ENJaErX2An/ZQAz0VJ9zx+6rGqcOti0HtrJCfe1x2D9VCyJI3U=", + "2": 24582, + "3": 7331465149450221740, + "4": 3429688654, + "5": "", + "254": 1 }, { - "rootPublicKey": "BJyJ1DODbJ+HellxuG3J/EstNpyw/i5h1x5qjNLQjwnPZoEaLLMZ8KKN7/rxQy3JUIkfuQydJz7JXeF80mES8q8=", - "vendorId": 4362, - "fabricId": 8516517930550670493, - "nodeId": 1443093566726981311, - "label": "", - "fabricIndex": 2 + "1": "BJyJ1DODbJ+HellxuG3J/EstNpyw/i5h1x5qjNLQjwnPZoEaLLMZ8KKN7/rxQy3JUIkfuQydJz7JXeF80mES8q8=", + "2": 4362, + "3": 8516517930550670493, + "4": 1443093566726981311, + "5": "", + "254": 2 }, { - "rootPublicKey": "BFOpRqEk+HJ6n/NtUtaWTQVVwstz9QRDK2xvRP6qKZKX3Rk05Zie5Ux9PdjgE1K5zE9NIP2jHHcVJjRBVZxNFz0=", - "vendorId": 4939, - "fabricId": 2, - "nodeId": 50, - "label": "", - "fabricIndex": 3 + "1": "BFOpRqEk+HJ6n/NtUtaWTQVVwstz9QRDK2xvRP6qKZKX3Rk05Zie5Ux9PdjgE1K5zE9NIP2jHHcVJjRBVZxNFz0=", + "2": 4939, + "3": 2, + "4": 50, + "5": "", + "254": 3 } ], "0/62/4": [ @@ -216,8 +216,8 @@ "1/29/65533": 1, "1/29/0": [ { - "deviceType": 514, - "revision": 2 + "0": 514, + "1": 2 } ], "1/29/1": [29, 3, 258], diff --git a/tests/components/matter/fixtures/nodes/window-covering_pa-lift.json b/tests/components/matter/fixtures/nodes/window-covering_pa-lift.json index 8d3335bbd6c4..fe970b6ed6be 100644 --- a/tests/components/matter/fixtures/nodes/window-covering_pa-lift.json +++ b/tests/components/matter/fixtures/nodes/window-covering_pa-lift.json @@ -7,8 +7,8 @@ "attributes": { "0/29/0": [ { - "deviceType": 22, - "revision": 1 + "0": 22, + "1": 1 } ], "0/29/1": [ @@ -29,11 +29,11 @@ "0/30/65531": [0, 65528, 65529, 65531, 65532, 65533], "0/31/0": [ { - "privilege": 5, - "authMode": 2, - "subjects": [112233], - "targets": null, - "fabricIndex": 2 + "1": 5, + "2": 2, + "3": [112233], + "4": null, + "254": 2 } ], "0/31/1": [], @@ -65,8 +65,8 @@ "0/40/17": true, "0/40/18": "7630EF9998EDF03C", "0/40/19": { - "caseSessionsPerFabric": 3, - "subscriptionsPerFabric": 3 + "0": 3, + "1": 3 }, "0/40/65532": 0, "0/40/65533": 1, @@ -117,8 +117,8 @@ "0/45/65531": [0, 65528, 65529, 65531, 65532, 65533], "0/48/0": 0, "0/48/1": { - "failSafeExpiryLengthSeconds": 60, - "maxCumulativeFailsafeSeconds": 900 + "0": 60, + "1": 900 }, "0/48/2": 0, "0/48/3": 0, @@ -131,8 +131,8 @@ "0/49/0": 1, "0/49/1": [ { - "networkID": "TE9OR0FOLUlPVA==", - "connected": true + "0": "TE9OR0FOLUlPVA==", + "1": true } ], "0/49/2": 10, @@ -153,17 +153,14 @@ "0/50/65531": [65528, 65529, 65531, 65532, 65533], "0/51/0": [ { - "name": "WIFI_STA_DEF", - "isOperational": true, - "offPremiseServicesReachableIPv4": null, - "offPremiseServicesReachableIPv6": null, - "hardwareAddress": "hPcDB5/k", - "IPv4Addresses": ["wKgIhg=="], - "IPv6Addresses": [ - "/oAAAAAAAACG9wP//gef5A==", - "JA4DsgZ+bsCG9wP//gef5A==" - ], - "type": 1 + "0": "WIFI_STA_DEF", + "1": true, + "2": null, + "3": null, + "4": "hPcDB5/k", + "5": ["wKgIhg=="], + "6": ["/oAAAAAAAACG9wP//gef5A==", "JA4DsgZ+bsCG9wP//gef5A=="], + "7": 1 } ], "0/51/1": 35, @@ -201,19 +198,19 @@ "0/60/65531": [0, 1, 2, 65528, 65529, 65531, 65532, 65533], "0/62/0": [ { - "noc": "FTABAQEkAgE3AyQTAhgmBIAigScmBYAlTTo3BiQVASQRARgkBwEkCAEwCUEE5Rw88GvXEUXr+cPYgKd00rIWyiHM8eu4Bhrzf1v83yBI2Qa+pwfOsKyvzxiuHLMfzhdC3gre4najpimi8AsX+TcKNQEoARgkAgE2AwQCBAEYMAQUWh6NlHAMbG5gz+vqlF51fulr3z8wBRR+D1hE33RhFC/mJWrhhZs6SVStQBgwC0DD5IxVgOrftUA47K1bQHaCNuWqIxf/8oMfcI0nMvTtXApwbBAJI/LjjCwMZJVFBE3W/FC6dQWSEuF8ES745tLBGA==", - "icac": "FTABAQEkAgE3AyQUARgmBIAigScmBYAlTTo3BiQTAhgkBwEkCAEwCUEEzpstYxy3lXF69g6H2vQ6uoqkdUsppJ4NcSyQcXQ8sQrF5HuzoVnDpevHfy0GAWHbXfE4VI0laTHvm/Wkj037ZjcKNQEpARgkAmAwBBR+D1hE33RhFC/mJWrhhZs6SVStQDAFFFCCK5NYv6CrD5/0S26zXBUwG0WBGDALQI5YKo3C3xvdqCrho2yZIJVJpJY2n9V/tmh7ESBBOHrY0b+K8Pf7hKhd5V0vzbCCbkhv1BNEne+lhcS2N6qhMNgY", - "fabricIndex": 2 + "1": "FTABAQEkAgE3AyQTAhgmBIAigScmBYAlTTo3BiQVASQRARgkBwEkCAEwCUEE5Rw88GvXEUXr+cPYgKd00rIWyiHM8eu4Bhrzf1v83yBI2Qa+pwfOsKyvzxiuHLMfzhdC3gre4najpimi8AsX+TcKNQEoARgkAgE2AwQCBAEYMAQUWh6NlHAMbG5gz+vqlF51fulr3z8wBRR+D1hE33RhFC/mJWrhhZs6SVStQBgwC0DD5IxVgOrftUA47K1bQHaCNuWqIxf/8oMfcI0nMvTtXApwbBAJI/LjjCwMZJVFBE3W/FC6dQWSEuF8ES745tLBGA==", + "2": "FTABAQEkAgE3AyQUARgmBIAigScmBYAlTTo3BiQTAhgkBwEkCAEwCUEEzpstYxy3lXF69g6H2vQ6uoqkdUsppJ4NcSyQcXQ8sQrF5HuzoVnDpevHfy0GAWHbXfE4VI0laTHvm/Wkj037ZjcKNQEpARgkAmAwBBR+D1hE33RhFC/mJWrhhZs6SVStQDAFFFCCK5NYv6CrD5/0S26zXBUwG0WBGDALQI5YKo3C3xvdqCrho2yZIJVJpJY2n9V/tmh7ESBBOHrY0b+K8Pf7hKhd5V0vzbCCbkhv1BNEne+lhcS2N6qhMNgY", + "254": 2 } ], "0/62/1": [ { - "rootPublicKey": "BFLMrM1satBpU0DN4sri/S4AVo/ugmZCndBfPO33Q+ZCKDZzNhMOB014+hZs0KL7vPssavT7Tb9nt0W+kpeAe0U=", - "vendorId": 65521, - "fabricId": 1, - "nodeId": 1, - "label": "", - "fabricIndex": 2 + "1": "BFLMrM1satBpU0DN4sri/S4AVo/ugmZCndBfPO33Q+ZCKDZzNhMOB014+hZs0KL7vPssavT7Tb9nt0W+kpeAe0U=", + "2": 65521, + "3": 1, + "4": 1, + "5": "", + "254": 2 } ], "0/62/2": 5, @@ -239,20 +236,20 @@ "0/63/65531": [0, 1, 2, 3, 65528, 65529, 65531, 65532, 65533], "0/64/0": [ { - "label": "room", - "value": "bedroom 2" + "0": "room", + "1": "bedroom 2" }, { - "label": "orientation", - "value": "North" + "0": "orientation", + "1": "North" }, { - "label": "floor", - "value": "2" + "0": "floor", + "1": "2" }, { - "label": "direction", - "value": "up" + "0": "direction", + "1": "up" } ], "0/64/65532": 0, @@ -281,8 +278,8 @@ "1/4/65531": [0, 65528, 65529, 65531, 65532, 65533], "1/29/0": [ { - "deviceType": 514, - "revision": 1 + "0": 514, + "1": 1 } ], "1/29/1": [3, 4, 29, 30, 64, 65, 258], @@ -301,20 +298,20 @@ "1/30/65531": [0, 65528, 65529, 65531, 65532, 65533], "1/64/0": [ { - "label": "room", - "value": "bedroom 2" + "0": "room", + "1": "bedroom 2" }, { - "label": "orientation", - "value": "North" + "0": "orientation", + "1": "North" }, { - "label": "floor", - "value": "2" + "0": "floor", + "1": "2" }, { - "label": "direction", - "value": "up" + "0": "direction", + "1": "up" } ], "1/64/65532": 0, diff --git a/tests/components/matter/fixtures/nodes/window-covering_pa-tilt.json b/tests/components/matter/fixtures/nodes/window-covering_pa-tilt.json index 44347dbd9645..92a1d820d2e3 100644 --- a/tests/components/matter/fixtures/nodes/window-covering_pa-tilt.json +++ b/tests/components/matter/fixtures/nodes/window-covering_pa-tilt.json @@ -8,8 +8,8 @@ "0/29/65533": 1, "0/29/0": [ { - "deviceType": 22, - "revision": 1 + "0": 22, + "1": 1 } ], "0/29/1": [29, 31, 40, 48, 49, 51, 60, 62, 63, 54], @@ -22,25 +22,25 @@ "0/31/65533": 1, "0/31/0": [ { - "privilege": 0, - "authMode": 0, - "subjects": null, - "targets": null, - "fabricIndex": 1 + "1": 0, + "2": 0, + "3": null, + "4": null, + "254": 1 }, { - "privilege": 0, - "authMode": 0, - "subjects": null, - "targets": null, - "fabricIndex": 2 + "1": 0, + "2": 0, + "3": null, + "4": null, + "254": 2 }, { - "privilege": 5, - "authMode": 2, - "subjects": [112233], - "targets": null, - "fabricIndex": 3 + "1": 5, + "2": 2, + "3": [112233], + "4": null, + "254": 3 } ], "0/31/2": 4, @@ -71,8 +71,8 @@ "0/40/17": true, "0/40/18": "mock_pa_tilt_window_covering", "0/40/19": { - "caseSessionsPerFabric": 3, - "subscriptionsPerFabric": 3 + "0": 3, + "1": 3 }, "0/40/65533": 1, "0/40/65528": [], @@ -84,8 +84,8 @@ "0/48/2": 0, "0/48/3": 0, "0/48/1": { - "failSafeExpiryLengthSeconds": 60, - "maxCumulativeFailsafeSeconds": 900 + "0": 60, + "1": 900 }, "0/48/4": true, "0/48/65533": 1, @@ -96,8 +96,8 @@ "0/49/0": 1, "0/49/1": [ { - "networkID": "MTI2MDk5", - "connected": true + "0": "MTI2MDk5", + "1": true } ], "0/49/2": 10, @@ -113,14 +113,14 @@ "0/49/65531": [0, 1, 2, 3, 4, 5, 6, 7, 65528, 65529, 65531, 65532, 65533], "0/51/0": [ { - "name": "WIFI_STA_DEF", - "isOperational": true, - "offPremiseServicesReachableIPv4": null, - "offPremiseServicesReachableIPv6": null, - "hardwareAddress": "JG8olrDo", - "IPv4Addresses": ["wKgBFw=="], - "IPv6Addresses": ["/oAAAAAAAAAmbyj//paw6A=="], - "type": 1 + "0": "WIFI_STA_DEF", + "1": true, + "2": null, + "3": null, + "4": "JG8olrDo", + "5": ["wKgBFw=="], + "6": ["/oAAAAAAAAAmbyj//paw6A=="], + "7": 1 } ], "0/51/1": 1, @@ -141,47 +141,47 @@ "0/62/65532": 0, "0/62/0": [ { - "noc": "", - "icac": null, - "fabricIndex": 1 + "1": "", + "2": null, + "254": 1 }, { - "noc": "", - "icac": null, - "fabricIndex": 2 + "1": "", + "2": null, + "254": 2 }, { - "noc": "FTABAQEkAgE3AyQTAhgmBIAigScmBYAlTTo3BiQVAiQRMhgkBwEkCAEwCUEE+5TLtucQZ8l7Y5r8nKhYB0mia0RMn+RJa5AtRIPb2R9ixMcQXfQBANdHPCwsfTGWyjBYzPXG1yDUTUz+Z1J9aTcKNQEoARgkAgE2AwQCBAEYMAQUh/lTccn18xJ1JqA9VRHdr2+IhscwBRTPeGj+EyBBTsdlJC4zNSP/tIcpFhgwC0AoRjZKvJRkg+Cz77N6+IIQBt0i1Oco92N/XzoDWtgUVIOW5qvPcUUI/tiYAEDdefy2/6XpjU1Y7ecN3vgoTdNUGA==", - "icac": "FTABAQEkAgE3AyQUARgmBIAigScmBYAlTTo3BiQTAhgkBwEkCAEwCUEEL6dfjjyZxKHsFjZvYUOhWsOCI/2ucOxcCZGFaJwG0vXhL5/aDhR/AF907lF93LR1Huvp3NJsB0oxqsNnbEz8jjcKNQEpARgkAmAwBBTPeGj+EyBBTsdlJC4zNSP/tIcpFjAFFC8Br9IClyBL3e7po3G+QXNGsBoYGDALQIHEwwdIaYHnFzpYngW9g+7Cn3gl0qKnetK5gWUVVTdVtpx6dYBblvPnOU+5K3Ow85llzcRxU1yXgPAM77s7t8gY", - "fabricIndex": 3 + "1": "FTABAQEkAgE3AyQTAhgmBIAigScmBYAlTTo3BiQVAiQRMhgkBwEkCAEwCUEE+5TLtucQZ8l7Y5r8nKhYB0mia0RMn+RJa5AtRIPb2R9ixMcQXfQBANdHPCwsfTGWyjBYzPXG1yDUTUz+Z1J9aTcKNQEoARgkAgE2AwQCBAEYMAQUh/lTccn18xJ1JqA9VRHdr2+IhscwBRTPeGj+EyBBTsdlJC4zNSP/tIcpFhgwC0AoRjZKvJRkg+Cz77N6+IIQBt0i1Oco92N/XzoDWtgUVIOW5qvPcUUI/tiYAEDdefy2/6XpjU1Y7ecN3vgoTdNUGA==", + "2": "FTABAQEkAgE3AyQUARgmBIAigScmBYAlTTo3BiQTAhgkBwEkCAEwCUEEL6dfjjyZxKHsFjZvYUOhWsOCI/2ucOxcCZGFaJwG0vXhL5/aDhR/AF907lF93LR1Huvp3NJsB0oxqsNnbEz8jjcKNQEpARgkAmAwBBTPeGj+EyBBTsdlJC4zNSP/tIcpFjAFFC8Br9IClyBL3e7po3G+QXNGsBoYGDALQIHEwwdIaYHnFzpYngW9g+7Cn3gl0qKnetK5gWUVVTdVtpx6dYBblvPnOU+5K3Ow85llzcRxU1yXgPAM77s7t8gY", + "254": 3 } ], "0/62/2": 5, "0/62/3": 3, "0/62/1": [ { - "rootPublicKey": "BFs332VJwg3I1yKmuKy2YKinZM57r2xsIk9+6ENJaErX2An/ZQAz0VJ9zx+6rGqcOti0HtrJCfe1x2D9VCyJI3U=", - "vendorId": 24582, - "fabricId": 7331465149450221740, - "nodeId": 3429688654, - "label": "", - "fabricIndex": 1 + "1": "BFs332VJwg3I1yKmuKy2YKinZM57r2xsIk9+6ENJaErX2An/ZQAz0VJ9zx+6rGqcOti0HtrJCfe1x2D9VCyJI3U=", + "2": 24582, + "3": 7331465149450221740, + "4": 3429688654, + "5": "", + "254": 1 }, { - "rootPublicKey": "BJyJ1DODbJ+HellxuG3J/EstNpyw/i5h1x5qjNLQjwnPZoEaLLMZ8KKN7/rxQy3JUIkfuQydJz7JXeF80mES8q8=", - "vendorId": 4362, - "fabricId": 8516517930550670493, - "nodeId": 1443093566726981311, - "label": "", - "fabricIndex": 2 + "1": "BJyJ1DODbJ+HellxuG3J/EstNpyw/i5h1x5qjNLQjwnPZoEaLLMZ8KKN7/rxQy3JUIkfuQydJz7JXeF80mES8q8=", + "2": 4362, + "3": 8516517930550670493, + "4": 1443093566726981311, + "5": "", + "254": 2 }, { - "rootPublicKey": "BFOpRqEk+HJ6n/NtUtaWTQVVwstz9QRDK2xvRP6qKZKX3Rk05Zie5Ux9PdjgE1K5zE9NIP2jHHcVJjRBVZxNFz0=", - "vendorId": 4939, - "fabricId": 2, - "nodeId": 50, - "label": "", - "fabricIndex": 3 + "1": "BFOpRqEk+HJ6n/NtUtaWTQVVwstz9QRDK2xvRP6qKZKX3Rk05Zie5Ux9PdjgE1K5zE9NIP2jHHcVJjRBVZxNFz0=", + "2": 4939, + "3": 2, + "4": 50, + "5": "", + "254": 3 } ], "0/62/4": [ @@ -216,8 +216,8 @@ "1/29/65533": 1, "1/29/0": [ { - "deviceType": 514, - "revision": 2 + "0": 514, + "1": 2 } ], "1/29/1": [29, 3, 258], diff --git a/tests/components/matter/fixtures/nodes/window-covering_tilt.json b/tests/components/matter/fixtures/nodes/window-covering_tilt.json index a33e0f24c3f3..144348b5c768 100644 --- a/tests/components/matter/fixtures/nodes/window-covering_tilt.json +++ b/tests/components/matter/fixtures/nodes/window-covering_tilt.json @@ -8,8 +8,8 @@ "0/29/65533": 1, "0/29/0": [ { - "deviceType": 22, - "revision": 1 + "0": 22, + "1": 1 } ], "0/29/1": [29, 31, 40, 48, 49, 51, 60, 62, 63, 54], @@ -22,25 +22,25 @@ "0/31/65533": 1, "0/31/0": [ { - "privilege": 0, - "authMode": 0, - "subjects": null, - "targets": null, - "fabricIndex": 1 + "1": 0, + "2": 0, + "3": null, + "4": null, + "254": 1 }, { - "privilege": 0, - "authMode": 0, - "subjects": null, - "targets": null, - "fabricIndex": 2 + "1": 0, + "2": 0, + "3": null, + "4": null, + "254": 2 }, { - "privilege": 5, - "authMode": 2, - "subjects": [112233], - "targets": null, - "fabricIndex": 3 + "1": 5, + "2": 2, + "3": [112233], + "4": null, + "254": 3 } ], "0/31/2": 4, @@ -71,8 +71,8 @@ "0/40/17": true, "0/40/18": "mock-tilt-window-covering", "0/40/19": { - "caseSessionsPerFabric": 3, - "subscriptionsPerFabric": 3 + "0": 3, + "1": 3 }, "0/40/65533": 1, "0/40/65528": [], @@ -84,8 +84,8 @@ "0/48/2": 0, "0/48/3": 0, "0/48/1": { - "failSafeExpiryLengthSeconds": 60, - "maxCumulativeFailsafeSeconds": 900 + "0": 60, + "1": 900 }, "0/48/4": true, "0/48/65533": 1, @@ -96,8 +96,8 @@ "0/49/0": 1, "0/49/1": [ { - "networkID": "MTI2MDk5", - "connected": true + "0": "MTI2MDk5", + "1": true } ], "0/49/2": 10, @@ -113,14 +113,14 @@ "0/49/65531": [0, 1, 2, 3, 4, 5, 6, 7, 65528, 65529, 65531, 65532, 65533], "0/51/0": [ { - "name": "WIFI_STA_DEF", - "isOperational": true, - "offPremiseServicesReachableIPv4": null, - "offPremiseServicesReachableIPv6": null, - "hardwareAddress": "JG8olrDo", - "IPv4Addresses": ["wKgBFw=="], - "IPv6Addresses": ["/oAAAAAAAAAmbyj//paw6A=="], - "type": 1 + "0": "WIFI_STA_DEF", + "1": true, + "2": null, + "3": null, + "4": "JG8olrDo", + "5": ["wKgBFw=="], + "6": ["/oAAAAAAAAAmbyj//paw6A=="], + "7": 1 } ], "0/51/1": 1, @@ -141,47 +141,47 @@ "0/62/65532": 0, "0/62/0": [ { - "noc": "", - "icac": null, - "fabricIndex": 1 + "1": "", + "2": null, + "254": 1 }, { - "noc": "", - "icac": null, - "fabricIndex": 2 + "1": "", + "2": null, + "254": 2 }, { - "noc": "FTABAQEkAgE3AyQTAhgmBIAigScmBYAlTTo3BiQVAiQRMhgkBwEkCAEwCUEE+5TLtucQZ8l7Y5r8nKhYB0mia0RMn+RJa5AtRIPb2R9ixMcQXfQBANdHPCwsfTGWyjBYzPXG1yDUTUz+Z1J9aTcKNQEoARgkAgE2AwQCBAEYMAQUh/lTccn18xJ1JqA9VRHdr2+IhscwBRTPeGj+EyBBTsdlJC4zNSP/tIcpFhgwC0AoRjZKvJRkg+Cz77N6+IIQBt0i1Oco92N/XzoDWtgUVIOW5qvPcUUI/tiYAEDdefy2/6XpjU1Y7ecN3vgoTdNUGA==", - "icac": "FTABAQEkAgE3AyQUARgmBIAigScmBYAlTTo3BiQTAhgkBwEkCAEwCUEEL6dfjjyZxKHsFjZvYUOhWsOCI/2ucOxcCZGFaJwG0vXhL5/aDhR/AF907lF93LR1Huvp3NJsB0oxqsNnbEz8jjcKNQEpARgkAmAwBBTPeGj+EyBBTsdlJC4zNSP/tIcpFjAFFC8Br9IClyBL3e7po3G+QXNGsBoYGDALQIHEwwdIaYHnFzpYngW9g+7Cn3gl0qKnetK5gWUVVTdVtpx6dYBblvPnOU+5K3Ow85llzcRxU1yXgPAM77s7t8gY", - "fabricIndex": 3 + "1": "FTABAQEkAgE3AyQTAhgmBIAigScmBYAlTTo3BiQVAiQRMhgkBwEkCAEwCUEE+5TLtucQZ8l7Y5r8nKhYB0mia0RMn+RJa5AtRIPb2R9ixMcQXfQBANdHPCwsfTGWyjBYzPXG1yDUTUz+Z1J9aTcKNQEoARgkAgE2AwQCBAEYMAQUh/lTccn18xJ1JqA9VRHdr2+IhscwBRTPeGj+EyBBTsdlJC4zNSP/tIcpFhgwC0AoRjZKvJRkg+Cz77N6+IIQBt0i1Oco92N/XzoDWtgUVIOW5qvPcUUI/tiYAEDdefy2/6XpjU1Y7ecN3vgoTdNUGA==", + "2": "FTABAQEkAgE3AyQUARgmBIAigScmBYAlTTo3BiQTAhgkBwEkCAEwCUEEL6dfjjyZxKHsFjZvYUOhWsOCI/2ucOxcCZGFaJwG0vXhL5/aDhR/AF907lF93LR1Huvp3NJsB0oxqsNnbEz8jjcKNQEpARgkAmAwBBTPeGj+EyBBTsdlJC4zNSP/tIcpFjAFFC8Br9IClyBL3e7po3G+QXNGsBoYGDALQIHEwwdIaYHnFzpYngW9g+7Cn3gl0qKnetK5gWUVVTdVtpx6dYBblvPnOU+5K3Ow85llzcRxU1yXgPAM77s7t8gY", + "254": 3 } ], "0/62/2": 5, "0/62/3": 3, "0/62/1": [ { - "rootPublicKey": "BFs332VJwg3I1yKmuKy2YKinZM57r2xsIk9+6ENJaErX2An/ZQAz0VJ9zx+6rGqcOti0HtrJCfe1x2D9VCyJI3U=", - "vendorId": 24582, - "fabricId": 7331465149450221740, - "nodeId": 3429688654, - "label": "", - "fabricIndex": 1 + "1": "BFs332VJwg3I1yKmuKy2YKinZM57r2xsIk9+6ENJaErX2An/ZQAz0VJ9zx+6rGqcOti0HtrJCfe1x2D9VCyJI3U=", + "2": 24582, + "3": 7331465149450221740, + "4": 3429688654, + "5": "", + "254": 1 }, { - "rootPublicKey": "BJyJ1DODbJ+HellxuG3J/EstNpyw/i5h1x5qjNLQjwnPZoEaLLMZ8KKN7/rxQy3JUIkfuQydJz7JXeF80mES8q8=", - "vendorId": 4362, - "fabricId": 8516517930550670493, - "nodeId": 1443093566726981311, - "label": "", - "fabricIndex": 2 + "1": "BJyJ1DODbJ+HellxuG3J/EstNpyw/i5h1x5qjNLQjwnPZoEaLLMZ8KKN7/rxQy3JUIkfuQydJz7JXeF80mES8q8=", + "2": 4362, + "3": 8516517930550670493, + "4": 1443093566726981311, + "5": "", + "254": 2 }, { - "rootPublicKey": "BFOpRqEk+HJ6n/NtUtaWTQVVwstz9QRDK2xvRP6qKZKX3Rk05Zie5Ux9PdjgE1K5zE9NIP2jHHcVJjRBVZxNFz0=", - "vendorId": 4939, - "fabricId": 2, - "nodeId": 50, - "label": "", - "fabricIndex": 3 + "1": "BFOpRqEk+HJ6n/NtUtaWTQVVwstz9QRDK2xvRP6qKZKX3Rk05Zie5Ux9PdjgE1K5zE9NIP2jHHcVJjRBVZxNFz0=", + "2": 4939, + "3": 2, + "4": 50, + "5": "", + "254": 3 } ], "0/62/4": [ @@ -216,8 +216,8 @@ "1/29/65533": 1, "1/29/0": [ { - "deviceType": 514, - "revision": 2 + "0": 514, + "1": 2 } ], "1/29/1": [29, 3, 258], diff --git a/tests/components/matter/test_helpers.py b/tests/components/matter/test_helpers.py index c7a0ed0d8a3e..61988a371226 100644 --- a/tests/components/matter/test_helpers.py +++ b/tests/components/matter/test_helpers.py @@ -60,16 +60,13 @@ async def test_get_node_from_device_entry( assert node_from_device_entry is node - with pytest.raises(ValueError) as value_error: - await get_node_from_device_entry(hass, other_device_entry) - - assert f"Device {other_device_entry.id} is not a Matter device" in str( - value_error.value - ) + # test non-Matter device returns None + assert get_node_from_device_entry(hass, other_device_entry) is None matter_client.server_info = None + # test non-initialized server raises RuntimeError with pytest.raises(RuntimeError) as runtime_error: - node_from_device_entry = await get_node_from_device_entry(hass, device_entry) + node_from_device_entry = get_node_from_device_entry(hass, device_entry) assert "Matter server information is not available" in str(runtime_error.value) diff --git a/tests/components/remote/test_significant_change.py b/tests/components/remote/test_significant_change.py new file mode 100644 index 000000000000..dcbfce213d65 --- /dev/null +++ b/tests/components/remote/test_significant_change.py @@ -0,0 +1,62 @@ +"""Test the Remote significant change platform.""" +from homeassistant.components.remote import ATTR_ACTIVITY_LIST, ATTR_CURRENT_ACTIVITY +from homeassistant.components.remote.significant_change import ( + async_check_significant_change, +) + + +async def test_significant_change() -> None: + """Detect Remote significant changes.""" + # no change at all + attrs = { + ATTR_CURRENT_ACTIVITY: "playing", + ATTR_ACTIVITY_LIST: ["playing", "paused"], + } + assert not async_check_significant_change(None, "on", attrs, "on", attrs) + + # change of state is significant + assert async_check_significant_change(None, "on", attrs, "off", attrs) + + # change of current activity is significant + attrs = { + "old": { + ATTR_CURRENT_ACTIVITY: "playing", + ATTR_ACTIVITY_LIST: ["playing", "paused"], + }, + "new": { + ATTR_CURRENT_ACTIVITY: "paused", + ATTR_ACTIVITY_LIST: ["playing", "paused"], + }, + } + assert async_check_significant_change(None, "on", attrs["old"], "on", attrs["new"]) + + # change of list of possible activities is not significant + attrs = { + "old": { + ATTR_CURRENT_ACTIVITY: "playing", + ATTR_ACTIVITY_LIST: ["playing", "paused"], + }, + "new": { + ATTR_CURRENT_ACTIVITY: "playing", + ATTR_ACTIVITY_LIST: ["playing"], + }, + } + assert not async_check_significant_change( + None, "on", attrs["old"], "on", attrs["new"] + ) + + # change of any not official attribute is not significant + attrs = { + "old": { + ATTR_CURRENT_ACTIVITY: "playing", + ATTR_ACTIVITY_LIST: ["playing", "paused"], + }, + "new": { + ATTR_CURRENT_ACTIVITY: "playing", + ATTR_ACTIVITY_LIST: ["playing", "paused"], + "not_official": "changed", + }, + } + assert not async_check_significant_change( + None, "on", attrs["old"], "on", attrs["new"] + ) diff --git a/tests/components/shelly/conftest.py b/tests/components/shelly/conftest.py index 1662405dc806..6eb74e26dcb9 100644 --- a/tests/components/shelly/conftest.py +++ b/tests/components/shelly/conftest.py @@ -153,7 +153,6 @@ MOCK_CONFIG = { "id": 0, "enable": True, "type": "heating", - "actuator": f"shelly://shellywalldisplay-{MOCK_MAC.lower()}/c/switch:0", }, "sys": { "ui_data": {}, @@ -181,7 +180,7 @@ MOCK_SHELLY_RPC = { "auth_en": False, "auth_domain": None, "profile": "cover", - "relay_operational": False, + "relay_in_thermostat": True, } MOCK_STATUS_COAP = { diff --git a/tests/components/shelly/test_switch.py b/tests/components/shelly/test_switch.py index 69e1423f75a0..e19416706e19 100644 --- a/tests/components/shelly/test_switch.py +++ b/tests/components/shelly/test_switch.py @@ -283,7 +283,8 @@ async def test_block_device_gas_valve( async def test_wall_display_thermostat_mode( - hass: HomeAssistant, mock_rpc_device, monkeypatch + hass: HomeAssistant, + mock_rpc_device, ) -> None: """Test Wall Display in thermostat mode.""" await init_integration(hass, 2, model=MODEL_WALL_DISPLAY) @@ -294,7 +295,10 @@ async def test_wall_display_thermostat_mode( async def test_wall_display_relay_mode( - hass: HomeAssistant, entity_registry, mock_rpc_device, monkeypatch + hass: HomeAssistant, + entity_registry: er.EntityRegistry, + mock_rpc_device, + monkeypatch, ) -> None: """Test Wall Display in thermostat mode.""" entity_id = register_entity( @@ -305,8 +309,7 @@ async def test_wall_display_relay_mode( ) new_shelly = deepcopy(mock_rpc_device.shelly) - new_shelly["relay_operational"] = True - + new_shelly["relay_in_thermostat"] = False monkeypatch.setattr(mock_rpc_device, "shelly", new_shelly) await init_integration(hass, 2, model=MODEL_WALL_DISPLAY) diff --git a/tests/components/trend/test_binary_sensor.py b/tests/components/trend/test_binary_sensor.py index cccf1add61b7..ddd980ae970f 100644 --- a/tests/components/trend/test_binary_sensor.py +++ b/tests/components/trend/test_binary_sensor.py @@ -1,5 +1,6 @@ """The test for the Trend sensor platform.""" from datetime import timedelta +import logging from unittest.mock import patch import pytest @@ -68,6 +69,7 @@ class TestTrendBinarySensor: "sample_duration": 10000, "min_gradient": 1, "max_samples": 25, + "min_samples": 5, } }, } @@ -76,24 +78,35 @@ class TestTrendBinarySensor: self.hass.block_till_done() now = dt_util.utcnow() + + # add not enough states to trigger calculation for val in [10, 0, 20, 30]: with patch("homeassistant.util.dt.utcnow", return_value=now): self.hass.states.set("sensor.test_state", val) self.hass.block_till_done() now += timedelta(seconds=2) - state = self.hass.states.get("binary_sensor.test_trend_sensor") - assert state.state == "on" + assert ( + self.hass.states.get("binary_sensor.test_trend_sensor").state == "unknown" + ) - # have to change state value, otherwise sample will lost + # add one more state to trigger gradient calculation + for val in [100]: + with patch("homeassistant.util.dt.utcnow", return_value=now): + self.hass.states.set("sensor.test_state", val) + self.hass.block_till_done() + now += timedelta(seconds=2) + + assert self.hass.states.get("binary_sensor.test_trend_sensor").state == "on" + + # add more states to trigger a downtrend for val in [0, 30, 1, 0]: with patch("homeassistant.util.dt.utcnow", return_value=now): self.hass.states.set("sensor.test_state", val) self.hass.block_till_done() now += timedelta(seconds=2) - state = self.hass.states.get("binary_sensor.test_trend_sensor") - assert state.state == "off" + assert self.hass.states.get("binary_sensor.test_trend_sensor").state == "off" def test_down_using_trendline(self): """Test down trend using multiple samples and trendline calculation.""" @@ -434,10 +447,72 @@ async def test_restore_state( { "binary_sensor": { "platform": "trend", - "sensors": {"test_trend_sensor": {"entity_id": "sensor.test_state"}}, + "sensors": { + "test_trend_sensor": { + "entity_id": "sensor.test_state", + "sample_duration": 10000, + "min_gradient": 1, + "max_samples": 25, + "min_samples": 5, + } + }, } }, ) await hass.async_block_till_done() + # restored sensor should match saved one assert hass.states.get("binary_sensor.test_trend_sensor").state == restored_state + + now = dt_util.utcnow() + + # add not enough samples to trigger calculation + for val in [10, 20, 30, 40]: + with patch("homeassistant.util.dt.utcnow", return_value=now): + hass.states.async_set("sensor.test_state", val) + await hass.async_block_till_done() + now += timedelta(seconds=2) + + # state should match restored state as no calculation happened + assert hass.states.get("binary_sensor.test_trend_sensor").state == restored_state + + # add more samples to trigger calculation + for val in [50, 60, 70, 80]: + with patch("homeassistant.util.dt.utcnow", return_value=now): + hass.states.async_set("sensor.test_state", val) + await hass.async_block_till_done() + now += timedelta(seconds=2) + + # sensor should detect an upwards trend and turn on + assert hass.states.get("binary_sensor.test_trend_sensor").state == "on" + + +async def test_invalid_min_sample( + hass: HomeAssistant, caplog: pytest.LogCaptureFixture +) -> None: + """Test if error is logged when min_sample is larger than max_samples.""" + with caplog.at_level(logging.ERROR): + assert await setup.async_setup_component( + hass, + "binary_sensor", + { + "binary_sensor": { + "platform": "trend", + "sensors": { + "test_trend_sensor": { + "entity_id": "sensor.test_state", + "max_samples": 25, + "min_samples": 30, + } + }, + } + }, + ) + await hass.async_block_till_done() + + record = caplog.records[0] + assert record.levelname == "ERROR" + assert ( + "Invalid config for 'binary_sensor.trend': min_samples must be smaller than or equal to max_samples" + in record.message + ) diff --git a/tests/conftest.py b/tests/conftest.py index 4050c1cdb6a5..fcd8e8b73a92 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -383,7 +383,7 @@ def reset_hass_threading_local_object() -> Generator[None, None, None]: ha._hass.__dict__.clear() -@pytest.fixture(autouse=True) +@pytest.fixture(scope="session", autouse=True) def bcrypt_cost() -> Generator[None, None, None]: """Run with reduced rounds during tests, to speed up uses.""" import bcrypt @@ -1544,7 +1544,7 @@ async def mock_enable_bluetooth( await hass.async_block_till_done() -@pytest.fixture +@pytest.fixture(scope="session") def mock_bluetooth_adapters() -> Generator[None, None, None]: """Fixture to mock bluetooth adapters.""" with patch( diff --git a/tests/test_bootstrap.py b/tests/test_bootstrap.py index b98d3d0311fc..42d679d7ce63 100644 --- a/tests/test_bootstrap.py +++ b/tests/test_bootstrap.py @@ -40,7 +40,7 @@ async def apply_stop_hass(stop_hass: None) -> None: """Make sure all hass are stopped.""" -@pytest.fixture(autouse=True) +@pytest.fixture(scope="session", autouse=True) def mock_http_start_stop() -> Generator[None, None, None]: """Mock HTTP start and stop.""" with patch( diff --git a/tests/util/test_color.py b/tests/util/test_color.py index 7c5e959aabc6..a7e6ba9ab46e 100644 --- a/tests/util/test_color.py +++ b/tests/util/test_color.py @@ -270,6 +270,15 @@ def test_color_rgbw_to_rgb() -> None: assert color_util.color_rgbw_to_rgb(0, 0, 0, 127) == (127, 127, 127) +def test_color_xy_to_temperature() -> None: + """Test color_xy_to_temperature.""" + assert color_util.color_xy_to_temperature(0.5119, 0.4147) == 2136 + assert color_util.color_xy_to_temperature(0.368, 0.3686) == 4302 + assert color_util.color_xy_to_temperature(0.4448, 0.4066) == 2893 + assert color_util.color_xy_to_temperature(0.1, 0.8) == 8645 + assert color_util.color_xy_to_temperature(0.5, 0.4) == 2140 + + def test_color_rgb_to_hex() -> None: """Test color_rgb_to_hex.""" assert color_util.color_rgb_to_hex(255, 255, 255) == "ffffff" diff --git a/tests/util/test_executor.py b/tests/util/test_executor.py index 076864c65c4e..d7731a44b7de 100644 --- a/tests/util/test_executor.py +++ b/tests/util/test_executor.py @@ -88,6 +88,10 @@ async def test_overall_timeout_reached(caplog: pytest.LogCaptureFixture) -> None iexecutor.shutdown() finish = time.monotonic() - assert finish - start < 1.3 + # Idealy execution time (finish - start) should be < 1.2 sec. + # CI tests might not run in an ideal environment and timing might + # not be accurate, so we let this test pass + # if the duration is below 3 seconds. + assert finish - start < 3.0 iexecutor.shutdown() diff --git a/tests/util/yaml/test_init.py b/tests/util/yaml/test_init.py index 990956ec908a..3a2d9b3734da 100644 --- a/tests/util/yaml/test_init.py +++ b/tests/util/yaml/test_init.py @@ -1,13 +1,15 @@ """Test Home Assistant yaml loader.""" +from collections.abc import Generator import importlib import io import os import pathlib from typing import Any import unittest -from unittest.mock import patch +from unittest.mock import Mock, patch import pytest +import voluptuous as vol import yaml as pyyaml from homeassistant.config import YAML_CONFIG_FILE, load_yaml_config_file @@ -584,6 +586,61 @@ async def test_loading_actual_file_with_syntax_error( await hass.async_add_executor_job(load_yaml_config_file, fixture_path) +@pytest.fixture +def mock_integration_frame() -> Generator[Mock, None, None]: + """Mock as if we're calling code from inside an integration.""" + correct_frame = Mock( + filename="/home/paulus/.homeassistant/custom_components/hue/light.py", + lineno="23", + line="self.light.is_on", + ) + with patch( + "homeassistant.helpers.frame.extract_stack", + return_value=[ + Mock( + filename="/home/paulus/homeassistant/core.py", + lineno="23", + line="do_something()", + ), + correct_frame, + Mock( + filename="/home/paulus/aiohue/lights.py", + lineno="2", + line="something()", + ), + ], + ): + yield correct_frame + + +@pytest.mark.parametrize( + ("loader_class", "new_class"), + [ + (yaml.loader.SafeLoader, "FastSafeLoader"), + ( + yaml.loader.SafeLineLoader, + "PythonSafeLoader", + ), + ], +) +async def test_deprecated_loaders( + hass: HomeAssistant, + mock_integration_frame: Mock, + caplog: pytest.LogCaptureFixture, + loader_class, + new_class: str, +) -> None: + """Test instantiating the deprecated yaml loaders logs a warning.""" + with pytest.raises(TypeError), patch( + "homeassistant.helpers.frame._REPORTED_INTEGRATIONS", set() + ): + loader_class() + assert ( + f"{loader_class.__name__} was called from hue, this is a deprecated class. " + f"Use {new_class} instead" + ) in caplog.text + + def test_string_annotated(try_both_loaders) -> None: """Test strings are annotated with file + line.""" conf = ( @@ -615,3 +672,20 @@ def test_string_annotated(try_both_loaders) -> None: getattr(value, "__config_file__", None) == expected_annotations[key][1][0] ) assert getattr(value, "__line__", None) == expected_annotations[key][1][1] + + +def test_string_used_as_vol_schema(try_both_loaders) -> None: + """Test the subclassed strings can be used in voluptuous schemas.""" + conf = "wanted_data:\n key_1: value_1\n key_2: value_2\n" + with io.StringIO(conf) as file: + doc = yaml_loader.parse_yaml(file) + + # Test using the subclassed strings in a schema + schema = vol.Schema( + {vol.Required(key): value for key, value in doc["wanted_data"].items()}, + ) + # Test using the subclassed strings when validating a schema + schema(doc["wanted_data"]) + schema({"key_1": "value_1", "key_2": "value_2"}) + with pytest.raises(vol.Invalid): + schema({"key_1": "value_2", "key_2": "value_1"})