diff --git a/homeassistant/components/vicare/__init__.py b/homeassistant/components/vicare/__init__.py index eedeffea9378..931040dda190 100644 --- a/homeassistant/components/vicare/__init__.py +++ b/homeassistant/components/vicare/__init__.py @@ -1,5 +1,6 @@ """The ViCare integration.""" +from collections import defaultdict from contextlib import suppress import logging import os @@ -170,11 +171,25 @@ async def async_setup_entry(hass: HomeAssistant, entry: ViCareConfigEntry) -> bo ) as err: raise ConfigEntryAuthFailed("Authentication failed") from err - device_count = len(entry.runtime_data.devices) - coordinators: list[ViCareCoordinator] = [] + # Group devices by gateway: in viaGateway mode one bulk fetch refreshes + # every device behind a gateway, so one coordinator serves the gateway. + devices_by_gateway: dict[str, list[ViCareDevice]] = defaultdict(list) for device in entry.runtime_data.devices: - coordinator = ViCareCoordinator(hass, entry, device.api, device_count) - device.coordinator = coordinator + devices_by_gateway[device.config.getConfig().serial].append(device) + + gateway_count = len(devices_by_gateway) + coordinators: list[ViCareCoordinator] = [] + for gateway_devices in devices_by_gateway.values(): + representative = gateway_devices[0] + coordinator = ViCareCoordinator( + hass, + entry, + representative.api, + representative.config.getConfig(), + gateway_count, + ) + for device in gateway_devices: + device.coordinator = coordinator coordinators.append(coordinator) for device in entry.runtime_data.devices: @@ -233,20 +248,32 @@ def _setup_vicare_api( ) -> ViCareData: """Set up PyVicare API.""" client = PyViCare() + client.loadViaGateway(True) client.setCacheDuration(cache_duration) client.initWithExternalOAuth(auth) device_config_list = get_supported_devices(client.devices) - # increase cache duration to fit rate limit to number of devices - if (number_of_devices := len(device_config_list)) > 1: - cache_duration = DEFAULT_CACHE_DURATION * number_of_devices + # In viaGateway mode each gateway is one bulk fetch per cycle, so the rate + # limit scales with the number of gateways, not devices. Offline gateways + # are never fetched, and are skipped below, so they must not count here + # either; this has to match the grouping in async_setup_entry. + gateway_count = len( + { + config.getConfig().serial + for config in device_config_list + if config.isOnline() + } + ) + if gateway_count > 1: + cache_duration = DEFAULT_CACHE_DURATION * gateway_count _LOGGER.debug( - "Found %s devices, adjusting cache duration to %s", - number_of_devices, + "Found %s gateways, adjusting cache duration to %s", + gateway_count, cache_duration, ) client = PyViCare() + client.loadViaGateway(True) client.setCacheDuration(cache_duration) client.initWithExternalOAuth(auth) device_config_list = get_supported_devices(client.devices) diff --git a/homeassistant/components/vicare/coordinator.py b/homeassistant/components/vicare/coordinator.py index 3472c724afc8..3661bf6e63bf 100644 --- a/homeassistant/components/vicare/coordinator.py +++ b/homeassistant/components/vicare/coordinator.py @@ -5,11 +5,13 @@ import logging from typing import override from PyViCare.PyViCareDevice import Device as PyViCareDevice +from PyViCare.PyViCareService import ViCareDeviceAccessor from PyViCare.PyViCareUtils import ( PyViCareDeviceCommunicationError, PyViCareInternalServerError, PyViCareInvalidCredentialsError, PyViCareInvalidDataError, + PyViCareNotSupportedFeatureError, PyViCareRateLimitError, ) import requests @@ -25,12 +27,12 @@ _LOGGER = logging.getLogger(__name__) class ViCareCoordinator(DataUpdateCoordinator[None]): - """Coordinator for a single ViCare device. + """Coordinator for a single ViCare gateway. - Triggers a fresh fetch of the device's full feature payload into - PyViCare's internal cache so entity ``value_getter`` lambdas read - fresh data on each tick. Carries no payload of its own; freshness - is signalled via ``last_update_success``. + In viaGateway mode all devices behind a gateway share one service, so a + single feature fetch refreshes every device on that gateway. The fetch takes + the accessor of a representative device. Carries no payload of its own; + freshness is signalled via ``last_update_success``. """ config_entry: ViCareConfigEntry @@ -40,28 +42,35 @@ class ViCareCoordinator(DataUpdateCoordinator[None]): hass: HomeAssistant, config_entry: ViCareConfigEntry, device: PyViCareDevice, - device_count: int, + accessor: ViCareDeviceAccessor, + gateway_count: int, ) -> None: - """Initialise the coordinator for one device.""" + """Initialise the coordinator for one gateway.""" super().__init__( hass, _LOGGER, config_entry=config_entry, - name=f"{DOMAIN}_{device.accessor.serial}_{device.accessor.device_id}", - update_interval=timedelta(seconds=DEFAULT_CACHE_DURATION * device_count), + name=f"{DOMAIN}_{accessor.serial}", + update_interval=timedelta(seconds=DEFAULT_CACHE_DURATION * gateway_count), ) self._device = device + self._accessor = accessor @override async def _async_update_data(self) -> None: - """Refresh the device's feature payload.""" + """Refresh the gateway's feature payload.""" await self.hass.async_add_executor_job(self._refresh) def _refresh(self) -> None: """Force a fresh fetch from the Viessmann API.""" try: self._device.service.clear_cache() - self._device.service.fetch_all_features(self._device.accessor) + self._device.service.fetch_all_features(self._accessor) + except PyViCareNotSupportedFeatureError: + # PACKAGE_NOT_PAID_FOR: load with no features instead of retrying setup. + _LOGGER.debug( + "No accessible features for gateway %s", self._accessor.serial + ) except PyViCareInvalidCredentialsError as err: raise ConfigEntryAuthFailed from err except ( diff --git a/homeassistant/components/vicare/diagnostics.py b/homeassistant/components/vicare/diagnostics.py index 008c533b430b..0bc0e4fd5c84 100644 --- a/homeassistant/components/vicare/diagnostics.py +++ b/homeassistant/components/vicare/diagnostics.py @@ -3,6 +3,7 @@ import json from typing import Any +from PyViCare.PyViCareServiceViaGateway import filter_features_for_device from PyViCare.PyViCareUtils import PyViCareDeviceCommunicationError from homeassistant.components.diagnostics import async_redact_data @@ -36,7 +37,13 @@ async def async_get_config_entry_diagnostics( devices: list[dict[str, Any]] = [] for device in entry.runtime_data.client.all_devices: try: - devices.append(json.loads(device.dump_secure())) + dump = json.loads(device.dump_secure()) + # In viaGateway mode dump_secure() returns the whole gateway's + # features, so scope them to the device the entry describes. + dump["data"] = filter_features_for_device( + dump["data"], device.device_id + ) + devices.append(dump) except PyViCareDeviceCommunicationError as err: # One offline gateway must not abort the whole diagnostics dump. devices.append( diff --git a/tests/components/vicare/conftest.py b/tests/components/vicare/conftest.py index 3123693289a0..af731b2ea035 100644 --- a/tests/components/vicare/conftest.py +++ b/tests/components/vicare/conftest.py @@ -2,6 +2,7 @@ from collections.abc import AsyncGenerator, Generator from dataclasses import dataclass +import re import time from unittest.mock import AsyncMock, Mock, patch @@ -32,47 +33,48 @@ class Fixture: data_file: str # Opt-in shared gateway serial; defaults to a per-fixture gateway when unset. gateway_id: str | None = None + online: bool = True class MockPyViCare: """Mocked PyVicare class based on a json dump.""" def __init__(self, fixtures: list[Fixture]) -> None: - """Init a single device from json dump.""" + """Init devices from json dumps, sharing one service per gateway.""" self.devices = [] + self.services: dict[str, MockViCareService] = {} for idx, fixture in enumerate(fixtures): - accessor = ViCareDeviceAccessor( - f"installation{idx}", - fixture.gateway_id or f"gateway{idx}", - f"deviceId{idx}", + gateway_id = fixture.gateway_id or f"gateway{idx}" + device_id = f"deviceId{idx}" + service = self.services.setdefault( + gateway_id, MockViCareService(fixture.roles) ) - service = MockViCareService(fixture) + service.add_device(device_id, fixture) self.devices.append( PyViCareDeviceConfig( - accessor, + ViCareDeviceAccessor(f"installation{idx}", gateway_id, device_id), service, "Vitovalor" if fixture.data_file.endswith("VitoValor.json") else f"model{idx}", - "Online", + "Online" if fixture.online else "Offline", roles=list(fixture.roles), ) ) # Simulate a device with an unsupported deviceType that PyViCare's # `devices` filter would drop but should still appear in `all_devices` # (used by diagnostics). - unsupported_accessor = ViCareDeviceAccessor( - "installation_unsupported", - "gateway_unsupported", - "deviceId_unsupported", - ) - unsupported_service = MockViCareService( - Fixture(set(), "vicare/dummy-device-no-serial.json") - ) + unsupported_fixture = Fixture(set(), "vicare/dummy-device-no-serial.json") + unsupported_service = MockViCareService(set()) + unsupported_service.add_device("deviceId_unsupported", unsupported_fixture) self.all_devices = [ *self.devices, PyViCareDeviceConfig( - unsupported_accessor, + ViCareDeviceAccessor( + "installation_unsupported", + "gateway_unsupported", + "deviceId_unsupported", + ), unsupported_service, "unsupported_model", "Online", @@ -92,25 +94,44 @@ class MockPyViCare: class MockViCareService: - """PyVicareService mock using a json dump.""" + """Mock of the gateway-wide service PyViCare shares in viaGateway mode. - def __init__(self, fixture: Fixture) -> None: - """Initialize the mock from a json dump.""" - self._test_data = load_json_object_fixture(fixture.data_file) - # Mirror the real signature: fetch_all_features() requires an accessor, - # and no real service carries one. - self.fetch_all_features = Mock(side_effect=lambda accessor: self._test_data) + One instance serves every device on the gateway: `fetch_all_features` + returns the bulk payload for all of them, and `getProperty` filters by + `accessor.device_id`, like `ViCareCachedServiceViaGateway` does. + """ + + def __init__(self, roles: set[str]) -> None: + """Initialize an empty gateway service.""" + self._features: dict[str, list] = {} + self.fetch_all_features = Mock(side_effect=self._fetch_all_features) self.setProperty = Mock() self.clear_cache = Mock() - self.roles = fixture.roles + self.roles = roles + + def add_device(self, device_id: str, fixture: Fixture) -> None: + """Add a device's features to the gateway payload.""" + features = load_json_object_fixture(fixture.data_file)["data"] + # In the real bulk payload every feature carries its own device in the + # uri, which is what consumers filter on. The fixtures all say device 0. + for feature in features: + if "uri" in feature: + feature["uri"] = re.sub( + r"/devices/[^/]+/", f"/devices/{device_id}/", feature["uri"] + ) + self._features[device_id] = features + + def _fetch_all_features(self, accessor: ViCareDeviceAccessor): + """Return the features of every device on the gateway.""" + return {"data": [f for features in self._features.values() for f in features]} def hasRoles(self, requested_roles: list[str]) -> bool: """Return true if requested roles are assigned.""" return requested_roles and set(requested_roles).issubset(self.roles) def getProperty(self, accessor: ViCareDeviceAccessor, property_name: str): - """Read a property from json dump.""" - return readFeature(self._test_data["data"], property_name) + """Read a property of one device from the gateway payload.""" + return readFeature(self._features[accessor.device_id], property_name) @pytest.fixture(autouse=True) diff --git a/tests/components/vicare/snapshots/test_diagnostics.ambr b/tests/components/vicare/snapshots/test_diagnostics.ambr index 4f189f5bc56b..7aadd45e009f 100644 --- a/tests/components/vicare/snapshots/test_diagnostics.ambr +++ b/tests/components/vicare/snapshots/test_diagnostics.ambr @@ -20,7 +20,7 @@ }), }), 'timestamp': '2024-07-30T20:03:40.073Z', - 'uri': 'https://api.viessmann.com/iot/v1/features/installations/#######/gateways/################/devices/0/features/device.serial', + 'uri': 'https://api.viessmann.com/iot/v1/features/installations/#######/gateways/################/devices/deviceId0/features/device.serial', }), dict({ 'apiVersion': 1, @@ -36,7 +36,7 @@ 'properties': dict({ }), 'timestamp': '2021-08-25T03:29:47.707Z', - 'uri': 'https://api.viessmann-platform.io/iot/v1/equipment/installations/######/gateways/################/devices/0/features/heating.buffer.charging.level.total', + 'uri': 'https://api.viessmann-platform.io/iot/v1/equipment/installations/######/gateways/################/devices/deviceId0/features/heating.buffer.charging.level.total', }), dict({ 'apiVersion': 1, @@ -56,7 +56,7 @@ 'properties': dict({ }), 'timestamp': '2021-08-25T03:29:46.401Z', - 'uri': 'https://api.viessmann-platform.io/iot/v1/equipment/installations/######/gateways/################/devices/0/features/heating.buffer.charging.level', + 'uri': 'https://api.viessmann-platform.io/iot/v1/equipment/installations/######/gateways/################/devices/deviceId0/features/heating.buffer.charging.level', }), dict({ 'apiVersion': 1, @@ -72,7 +72,7 @@ 'properties': dict({ }), 'timestamp': '2021-08-25T03:29:47.713Z', - 'uri': 'https://api.viessmann-platform.io/iot/v1/equipment/installations/######/gateways/################/devices/0/features/heating.solar.pumps.circuit', + 'uri': 'https://api.viessmann-platform.io/iot/v1/equipment/installations/######/gateways/################/devices/deviceId0/features/heating.solar.pumps.circuit', }), dict({ 'apiVersion': 1, @@ -98,7 +98,7 @@ }), }), 'timestamp': '2021-08-25T14:23:17.238Z', - 'uri': 'https://api.viessmann-platform.io/iot/v1/equipment/installations/######/gateways/################/devices/0/features/heating.burners.0.statistics', + 'uri': 'https://api.viessmann-platform.io/iot/v1/equipment/installations/######/gateways/################/devices/deviceId0/features/heating.burners.0.statistics', }), dict({ 'apiVersion': 1, @@ -114,7 +114,7 @@ 'properties': dict({ }), 'timestamp': '2021-08-25T03:29:46.971Z', - 'uri': 'https://api.viessmann-platform.io/iot/v1/equipment/installations/######/gateways/################/devices/0/features/heating.circuits.0.operating.modes.heating', + 'uri': 'https://api.viessmann-platform.io/iot/v1/equipment/installations/######/gateways/################/devices/deviceId0/features/heating.circuits.0.operating.modes.heating', }), dict({ 'apiVersion': 1, @@ -130,7 +130,7 @@ 'properties': dict({ }), 'timestamp': '2021-08-25T03:29:46.401Z', - 'uri': 'https://api.viessmann-platform.io/iot/v1/equipment/installations/######/gateways/################/devices/0/features/device', + 'uri': 'https://api.viessmann-platform.io/iot/v1/equipment/installations/######/gateways/################/devices/deviceId0/features/device', }), dict({ 'apiVersion': 1, @@ -146,7 +146,7 @@ 'properties': dict({ }), 'timestamp': '2021-08-25T03:29:47.694Z', - 'uri': 'https://api.viessmann-platform.io/iot/v1/equipment/installations/######/gateways/################/devices/0/features/heating.dhw.pumps.circulation.schedule', + 'uri': 'https://api.viessmann-platform.io/iot/v1/equipment/installations/######/gateways/################/devices/deviceId0/features/heating.dhw.pumps.circulation.schedule', }), dict({ 'apiVersion': 1, @@ -166,7 +166,7 @@ }), }), 'timestamp': '2021-08-25T03:29:47.639Z', - 'uri': 'https://api.viessmann-platform.io/iot/v1/equipment/installations/######/gateways/################/devices/0/features/heating.circuits.0.circulation.pump', + 'uri': 'https://api.viessmann-platform.io/iot/v1/equipment/installations/######/gateways/################/devices/deviceId0/features/heating.circuits.0.circulation.pump', }), dict({ 'apiVersion': 1, @@ -183,7 +183,7 @@ 'properties': dict({ }), 'timestamp': '2021-08-25T03:29:46.400Z', - 'uri': 'https://api.viessmann-platform.io/iot/v1/equipment/installations/######/gateways/################/devices/0/features/heating.circuits.2.circulation', + 'uri': 'https://api.viessmann-platform.io/iot/v1/equipment/installations/######/gateways/################/devices/deviceId0/features/heating.circuits.2.circulation', }), dict({ 'apiVersion': 1, @@ -199,7 +199,7 @@ 'properties': dict({ }), 'timestamp': '2021-08-25T03:29:46.922Z', - 'uri': 'https://api.viessmann-platform.io/iot/v1/equipment/installations/######/gateways/################/devices/0/features/heating.circuits.2.heating.schedule', + 'uri': 'https://api.viessmann-platform.io/iot/v1/equipment/installations/######/gateways/################/devices/deviceId0/features/heating.circuits.2.heating.schedule', }), dict({ 'apiVersion': 1, @@ -215,7 +215,7 @@ 'properties': dict({ }), 'timestamp': '2021-08-25T03:29:47.572Z', - 'uri': 'https://api.viessmann-platform.io/iot/v1/equipment/installations/######/gateways/################/devices/0/features/heating.circuits.2.sensors.temperature.supply', + 'uri': 'https://api.viessmann-platform.io/iot/v1/equipment/installations/######/gateways/################/devices/deviceId0/features/heating.circuits.2.sensors.temperature.supply', }), dict({ 'apiVersion': 1, @@ -231,7 +231,7 @@ 'properties': dict({ }), 'timestamp': '2021-08-25T03:29:47.700Z', - 'uri': 'https://api.viessmann-platform.io/iot/v1/equipment/installations/######/gateways/################/devices/0/features/heating.solar.sensors.temperature.collector', + 'uri': 'https://api.viessmann-platform.io/iot/v1/equipment/installations/######/gateways/################/devices/deviceId0/features/heating.solar.sensors.temperature.collector', }), dict({ 'apiVersion': 1, @@ -247,7 +247,7 @@ 'properties': dict({ }), 'timestamp': '2021-08-25T03:29:47.677Z', - 'uri': 'https://api.viessmann-platform.io/iot/v1/equipment/installations/######/gateways/################/devices/0/features/heating.circuits.2.operating.modes.active', + 'uri': 'https://api.viessmann-platform.io/iot/v1/equipment/installations/######/gateways/################/devices/deviceId0/features/heating.circuits.2.operating.modes.active', }), dict({ 'apiVersion': 1, @@ -267,7 +267,7 @@ }), }), 'timestamp': '2021-08-25T14:16:46.543Z', - 'uri': 'https://api.viessmann-platform.io/iot/v1/equipment/installations/######/gateways/################/devices/0/features/heating.burner', + 'uri': 'https://api.viessmann-platform.io/iot/v1/equipment/installations/######/gateways/################/devices/deviceId0/features/heating.burner', }), dict({ 'apiVersion': 1, @@ -283,7 +283,7 @@ 'properties': dict({ }), 'timestamp': '2021-08-25T03:29:47.714Z', - 'uri': 'https://api.viessmann-platform.io/iot/v1/equipment/installations/######/gateways/################/devices/0/features/heating.operating.programs.holiday', + 'uri': 'https://api.viessmann-platform.io/iot/v1/equipment/installations/######/gateways/################/devices/deviceId0/features/heating.operating.programs.holiday', }), dict({ 'apiVersion': 1, @@ -299,7 +299,7 @@ 'properties': dict({ }), 'timestamp': '2021-08-25T03:29:47.711Z', - 'uri': 'https://api.viessmann-platform.io/iot/v1/equipment/installations/######/gateways/################/devices/0/features/heating.buffer.charging.level.bottom', + 'uri': 'https://api.viessmann-platform.io/iot/v1/equipment/installations/######/gateways/################/devices/deviceId0/features/heating.buffer.charging.level.bottom', }), dict({ 'apiVersion': 1, @@ -328,7 +328,7 @@ }), }), 'timestamp': '2021-08-25T15:13:19.679Z', - 'uri': 'https://api.viessmann-platform.io/iot/v1/equipment/installations/######/gateways/################/devices/0/features/heating.circuits.0.sensors.temperature.supply', + 'uri': 'https://api.viessmann-platform.io/iot/v1/equipment/installations/######/gateways/################/devices/deviceId0/features/heating.circuits.0.sensors.temperature.supply', }), dict({ 'apiVersion': 1, @@ -344,7 +344,7 @@ 'properties': dict({ }), 'timestamp': '2021-08-25T03:29:46.955Z', - 'uri': 'https://api.viessmann-platform.io/iot/v1/equipment/installations/######/gateways/################/devices/0/features/heating.circuits.2.operating.modes.dhw', + 'uri': 'https://api.viessmann-platform.io/iot/v1/equipment/installations/######/gateways/################/devices/deviceId0/features/heating.circuits.2.operating.modes.dhw', }), dict({ 'apiVersion': 1, @@ -384,7 +384,7 @@ }), }), 'timestamp': '2021-08-25T03:29:47.654Z', - 'uri': 'https://api.viessmann-platform.io/iot/v1/equipment/installations/######/gateways/################/devices/0/features/heating.circuits.0.operating.modes.active', + 'uri': 'https://api.viessmann-platform.io/iot/v1/equipment/installations/######/gateways/################/devices/deviceId0/features/heating.circuits.0.operating.modes.active', }), dict({ 'apiVersion': 1, @@ -452,7 +452,7 @@ }), }), 'timestamp': '2021-08-25T03:29:46.825Z', - 'uri': 'https://api.viessmann-platform.io/iot/v1/equipment/installations/######/gateways/################/devices/0/features/heating.circuits.0.operating.programs.comfort', + 'uri': 'https://api.viessmann-platform.io/iot/v1/equipment/installations/######/gateways/################/devices/deviceId0/features/heating.circuits.0.operating.programs.comfort', }), dict({ 'apiVersion': 1, @@ -469,7 +469,7 @@ 'properties': dict({ }), 'timestamp': '2021-08-25T03:29:47.717Z', - 'uri': 'https://api.viessmann-platform.io/iot/v1/equipment/installations/######/gateways/################/devices/0/features/ventilation', + 'uri': 'https://api.viessmann-platform.io/iot/v1/equipment/installations/######/gateways/################/devices/deviceId0/features/ventilation', }), dict({ 'apiVersion': 1, @@ -520,7 +520,7 @@ }), }), 'timestamp': '2021-08-25T03:29:46.909Z', - 'uri': 'https://api.viessmann-platform.io/iot/v1/equipment/installations/######/gateways/################/devices/0/features/heating.circuits.1.heating.curve', + 'uri': 'https://api.viessmann-platform.io/iot/v1/equipment/installations/######/gateways/################/devices/deviceId0/features/heating.circuits.1.heating.curve', }), dict({ 'apiVersion': 1, @@ -536,7 +536,7 @@ 'properties': dict({ }), 'timestamp': '2021-08-25T03:29:46.838Z', - 'uri': 'https://api.viessmann-platform.io/iot/v1/equipment/installations/######/gateways/################/devices/0/features/heating.boiler.sensors.temperature.commonSupply', + 'uri': 'https://api.viessmann-platform.io/iot/v1/equipment/installations/######/gateways/################/devices/deviceId0/features/heating.boiler.sensors.temperature.commonSupply', }), dict({ 'apiVersion': 1, @@ -553,7 +553,7 @@ 'properties': dict({ }), 'timestamp': '2021-08-25T03:29:46.400Z', - 'uri': 'https://api.viessmann-platform.io/iot/v1/equipment/installations/######/gateways/################/devices/0/features/heating.circuits.0.circulation', + 'uri': 'https://api.viessmann-platform.io/iot/v1/equipment/installations/######/gateways/################/devices/deviceId0/features/heating.circuits.0.circulation', }), dict({ 'apiVersion': 1, @@ -569,7 +569,7 @@ 'properties': dict({ }), 'timestamp': '2021-08-25T03:29:46.903Z', - 'uri': 'https://api.viessmann-platform.io/iot/v1/equipment/installations/######/gateways/################/devices/0/features/heating.circuits.2.frostprotection', + 'uri': 'https://api.viessmann-platform.io/iot/v1/equipment/installations/######/gateways/################/devices/deviceId0/features/heating.circuits.2.frostprotection', }), dict({ 'apiVersion': 1, @@ -591,7 +591,7 @@ 'properties': dict({ }), 'timestamp': '2021-08-25T03:29:46.863Z', - 'uri': 'https://api.viessmann-platform.io/iot/v1/equipment/installations/######/gateways/################/devices/0/features/heating.circuits.2', + 'uri': 'https://api.viessmann-platform.io/iot/v1/equipment/installations/######/gateways/################/devices/deviceId0/features/heating.circuits.2', }), dict({ 'apiVersion': 1, @@ -609,7 +609,7 @@ 'properties': dict({ }), 'timestamp': '2021-08-25T03:29:47.698Z', - 'uri': 'https://api.viessmann-platform.io/iot/v1/equipment/installations/######/gateways/################/devices/0/features/heating.solar', + 'uri': 'https://api.viessmann-platform.io/iot/v1/equipment/installations/######/gateways/################/devices/deviceId0/features/heating.solar', }), dict({ 'apiVersion': 1, @@ -627,7 +627,7 @@ 'properties': dict({ }), 'timestamp': '2021-08-25T03:29:46.400Z', - 'uri': 'https://api.viessmann-platform.io/iot/v1/equipment/installations/######/gateways/################/devices/0/features/ventilation.operating', + 'uri': 'https://api.viessmann-platform.io/iot/v1/equipment/installations/######/gateways/################/devices/deviceId0/features/ventilation.operating', }), dict({ 'apiVersion': 1, @@ -649,7 +649,7 @@ }), }), 'timestamp': '2021-08-25T14:16:46.550Z', - 'uri': 'https://api.viessmann-platform.io/iot/v1/equipment/installations/######/gateways/################/devices/0/features/heating.burners.0', + 'uri': 'https://api.viessmann-platform.io/iot/v1/equipment/installations/######/gateways/################/devices/deviceId0/features/heating.burners.0', }), dict({ 'apiVersion': 1, @@ -667,7 +667,7 @@ 'properties': dict({ }), 'timestamp': '2021-08-25T03:29:46.400Z', - 'uri': 'https://api.viessmann-platform.io/iot/v1/equipment/installations/######/gateways/################/devices/0/features/heating.circuits.1.operating', + 'uri': 'https://api.viessmann-platform.io/iot/v1/equipment/installations/######/gateways/################/devices/deviceId0/features/heating.circuits.1.operating', }), dict({ 'apiVersion': 1, @@ -683,7 +683,7 @@ 'properties': dict({ }), 'timestamp': '2021-08-25T03:29:47.560Z', - 'uri': 'https://api.viessmann-platform.io/iot/v1/equipment/installations/######/gateways/################/devices/0/features/heating.circuits.2.operating.programs.standby', + 'uri': 'https://api.viessmann-platform.io/iot/v1/equipment/installations/######/gateways/################/devices/deviceId0/features/heating.circuits.2.operating.programs.standby', }), dict({ 'apiVersion': 1, @@ -755,7 +755,7 @@ }), }), 'timestamp': '2021-08-25T03:29:47.541Z', - 'uri': 'https://api.viessmann-platform.io/iot/v1/equipment/installations/######/gateways/################/devices/0/features/heating.circuits.0.operating.programs.holiday', + 'uri': 'https://api.viessmann-platform.io/iot/v1/equipment/installations/######/gateways/################/devices/deviceId0/features/heating.circuits.0.operating.programs.holiday', }), dict({ 'apiVersion': 1, @@ -771,7 +771,7 @@ 'properties': dict({ }), 'timestamp': '2021-08-25T03:29:47.726Z', - 'uri': 'https://api.viessmann-platform.io/iot/v1/equipment/installations/######/gateways/################/devices/0/features/ventilation.operating.modes.standby', + 'uri': 'https://api.viessmann-platform.io/iot/v1/equipment/installations/######/gateways/################/devices/deviceId0/features/ventilation.operating.modes.standby', }), dict({ 'apiVersion': 1, @@ -792,7 +792,7 @@ 'properties': dict({ }), 'timestamp': '2021-08-25T03:29:46.401Z', - 'uri': 'https://api.viessmann-platform.io/iot/v1/equipment/installations/######/gateways/################/devices/0/features/heating.circuits.2.operating.modes', + 'uri': 'https://api.viessmann-platform.io/iot/v1/equipment/installations/######/gateways/################/devices/deviceId0/features/heating.circuits.2.operating.modes', }), dict({ 'apiVersion': 1, @@ -812,7 +812,7 @@ }), }), 'timestamp': '2021-08-25T14:18:44.841Z', - 'uri': 'https://api.viessmann-platform.io/iot/v1/equipment/installations/######/gateways/################/devices/0/features/heating.dhw.pumps.primary', + 'uri': 'https://api.viessmann-platform.io/iot/v1/equipment/installations/######/gateways/################/devices/deviceId0/features/heating.dhw.pumps.primary', }), dict({ 'apiVersion': 1, @@ -828,7 +828,7 @@ 'properties': dict({ }), 'timestamp': '2021-08-25T03:29:47.722Z', - 'uri': 'https://api.viessmann-platform.io/iot/v1/equipment/installations/######/gateways/################/devices/0/features/ventilation.operating.programs.holiday', + 'uri': 'https://api.viessmann-platform.io/iot/v1/equipment/installations/######/gateways/################/devices/deviceId0/features/ventilation.operating.programs.holiday', }), dict({ 'apiVersion': 1, @@ -929,7 +929,7 @@ }), }), 'timestamp': '2021-08-25T03:29:46.920Z', - 'uri': 'https://api.viessmann-platform.io/iot/v1/equipment/installations/######/gateways/################/devices/0/features/heating.circuits.1.heating.schedule', + 'uri': 'https://api.viessmann-platform.io/iot/v1/equipment/installations/######/gateways/################/devices/deviceId0/features/heating.circuits.1.heating.schedule', }), dict({ 'apiVersion': 1, @@ -945,7 +945,7 @@ 'properties': dict({ }), 'timestamp': '2021-08-25T03:29:46.967Z', - 'uri': 'https://api.viessmann-platform.io/iot/v1/equipment/installations/######/gateways/################/devices/0/features/heating.circuits.2.operating.modes.dhwAndHeating', + 'uri': 'https://api.viessmann-platform.io/iot/v1/equipment/installations/######/gateways/################/devices/deviceId0/features/heating.circuits.2.operating.modes.dhwAndHeating', }), dict({ 'apiVersion': 1, @@ -990,7 +990,7 @@ }), }), 'timestamp': '2021-08-25T03:29:47.553Z', - 'uri': 'https://api.viessmann-platform.io/iot/v1/equipment/installations/######/gateways/################/devices/0/features/heating.circuits.0.operating.programs.reduced', + 'uri': 'https://api.viessmann-platform.io/iot/v1/equipment/installations/######/gateways/################/devices/deviceId0/features/heating.circuits.0.operating.programs.reduced', }), dict({ 'apiVersion': 1, @@ -1007,7 +1007,7 @@ 'properties': dict({ }), 'timestamp': '2021-08-25T03:29:46.401Z', - 'uri': 'https://api.viessmann-platform.io/iot/v1/equipment/installations/######/gateways/################/devices/0/features/heating.device.time', + 'uri': 'https://api.viessmann-platform.io/iot/v1/equipment/installations/######/gateways/################/devices/deviceId0/features/heating.device.time', }), dict({ 'apiVersion': 1, @@ -1025,7 +1025,7 @@ 'properties': dict({ }), 'timestamp': '2021-08-25T03:29:46.400Z', - 'uri': 'https://api.viessmann-platform.io/iot/v1/equipment/installations/######/gateways/################/devices/0/features/heating.circuits.0.heating', + 'uri': 'https://api.viessmann-platform.io/iot/v1/equipment/installations/######/gateways/################/devices/deviceId0/features/heating.circuits.0.heating', }), dict({ 'apiVersion': 1, @@ -1097,7 +1097,7 @@ }), }), 'timestamp': '2021-08-25T03:29:47.543Z', - 'uri': 'https://api.viessmann-platform.io/iot/v1/equipment/installations/######/gateways/################/devices/0/features/heating.circuits.1.operating.programs.holiday', + 'uri': 'https://api.viessmann-platform.io/iot/v1/equipment/installations/######/gateways/################/devices/deviceId0/features/heating.circuits.1.operating.programs.holiday', }), dict({ 'apiVersion': 1, @@ -1137,7 +1137,7 @@ }), }), 'timestamp': '2021-08-25T03:29:47.666Z', - 'uri': 'https://api.viessmann-platform.io/iot/v1/equipment/installations/######/gateways/################/devices/0/features/heating.circuits.1.operating.modes.active', + 'uri': 'https://api.viessmann-platform.io/iot/v1/equipment/installations/######/gateways/################/devices/deviceId0/features/heating.circuits.1.operating.modes.active', }), dict({ 'apiVersion': 1, @@ -1238,7 +1238,7 @@ }), }), 'timestamp': '2021-08-25T03:29:46.918Z', - 'uri': 'https://api.viessmann-platform.io/iot/v1/equipment/installations/######/gateways/################/devices/0/features/heating.circuits.0.heating.schedule', + 'uri': 'https://api.viessmann-platform.io/iot/v1/equipment/installations/######/gateways/################/devices/deviceId0/features/heating.circuits.0.heating.schedule', }), dict({ 'apiVersion': 1, @@ -1258,7 +1258,7 @@ }), }), 'timestamp': '2021-08-25T03:29:47.574Z', - 'uri': 'https://api.viessmann-platform.io/iot/v1/equipment/installations/######/gateways/################/devices/0/features/heating.controller.serial', + 'uri': 'https://api.viessmann-platform.io/iot/v1/equipment/installations/######/gateways/################/devices/deviceId0/features/heating.controller.serial', }), dict({ 'apiVersion': 1, @@ -1283,7 +1283,7 @@ }), }), 'timestamp': '2021-08-25T03:29:47.536Z', - 'uri': 'https://api.viessmann-platform.io/iot/v1/equipment/installations/######/gateways/################/devices/0/features/heating.circuits.0.operating.programs.external', + 'uri': 'https://api.viessmann-platform.io/iot/v1/equipment/installations/######/gateways/################/devices/deviceId0/features/heating.circuits.0.operating.programs.external', }), dict({ 'apiVersion': 1, @@ -1332,7 +1332,7 @@ }), }), 'timestamp': '2021-08-25T03:29:46.859Z', - 'uri': 'https://api.viessmann-platform.io/iot/v1/equipment/installations/######/gateways/################/devices/0/features/heating.circuits.0', + 'uri': 'https://api.viessmann-platform.io/iot/v1/equipment/installations/######/gateways/################/devices/deviceId0/features/heating.circuits.0', }), dict({ 'apiVersion': 1, @@ -1352,7 +1352,7 @@ }), }), 'timestamp': '2021-08-25T03:29:46.939Z', - 'uri': 'https://api.viessmann-platform.io/iot/v1/equipment/installations/######/gateways/################/devices/0/features/heating.circuits.1.operating.modes.dhw', + 'uri': 'https://api.viessmann-platform.io/iot/v1/equipment/installations/######/gateways/################/devices/deviceId0/features/heating.circuits.1.operating.modes.dhw', }), dict({ 'apiVersion': 1, @@ -1369,7 +1369,7 @@ 'properties': dict({ }), 'timestamp': '2021-08-25T03:29:46.400Z', - 'uri': 'https://api.viessmann-platform.io/iot/v1/equipment/installations/######/gateways/################/devices/0/features/heating.circuits.0.dhw.pumps.circulation', + 'uri': 'https://api.viessmann-platform.io/iot/v1/equipment/installations/######/gateways/################/devices/deviceId0/features/heating.circuits.0.dhw.pumps.circulation', }), dict({ 'apiVersion': 1, @@ -1393,7 +1393,7 @@ 'properties': dict({ }), 'timestamp': '2021-08-25T03:29:46.400Z', - 'uri': 'https://api.viessmann-platform.io/iot/v1/equipment/installations/######/gateways/################/devices/0/features/heating.circuits.2.operating.programs', + 'uri': 'https://api.viessmann-platform.io/iot/v1/equipment/installations/######/gateways/################/devices/deviceId0/features/heating.circuits.2.operating.programs', }), dict({ 'apiVersion': 1, @@ -1411,7 +1411,7 @@ 'properties': dict({ }), 'timestamp': '2021-08-25T03:29:46.401Z', - 'uri': 'https://api.viessmann-platform.io/iot/v1/equipment/installations/######/gateways/################/devices/0/features/heating.circuits.1.sensors.temperature', + 'uri': 'https://api.viessmann-platform.io/iot/v1/equipment/installations/######/gateways/################/devices/deviceId0/features/heating.circuits.1.sensors.temperature', }), dict({ 'apiVersion': 1, @@ -1431,7 +1431,7 @@ }), }), 'timestamp': '2021-08-25T03:29:46.894Z', - 'uri': 'https://api.viessmann-platform.io/iot/v1/equipment/installations/######/gateways/################/devices/0/features/heating.circuits.0.frostprotection', + 'uri': 'https://api.viessmann-platform.io/iot/v1/equipment/installations/######/gateways/################/devices/deviceId0/features/heating.circuits.0.frostprotection', }), dict({ 'apiVersion': 1, @@ -1451,7 +1451,7 @@ }), }), 'timestamp': '2021-08-25T03:29:46.958Z', - 'uri': 'https://api.viessmann-platform.io/iot/v1/equipment/installations/######/gateways/################/devices/0/features/heating.circuits.0.operating.modes.dhwAndHeating', + 'uri': 'https://api.viessmann-platform.io/iot/v1/equipment/installations/######/gateways/################/devices/deviceId0/features/heating.circuits.0.operating.modes.dhwAndHeating', }), dict({ 'apiVersion': 1, @@ -1468,7 +1468,7 @@ 'properties': dict({ }), 'timestamp': '2021-08-25T03:29:46.400Z', - 'uri': 'https://api.viessmann-platform.io/iot/v1/equipment/installations/######/gateways/################/devices/0/features/heating.operating', + 'uri': 'https://api.viessmann-platform.io/iot/v1/equipment/installations/######/gateways/################/devices/deviceId0/features/heating.operating', }), dict({ 'apiVersion': 1, @@ -1495,7 +1495,7 @@ 'properties': dict({ }), 'timestamp': '2021-08-25T03:29:46.400Z', - 'uri': 'https://api.viessmann-platform.io/iot/v1/equipment/installations/######/gateways/################/devices/0/features/heating', + 'uri': 'https://api.viessmann-platform.io/iot/v1/equipment/installations/######/gateways/################/devices/deviceId0/features/heating', }), dict({ 'apiVersion': 1, @@ -1512,7 +1512,7 @@ 'properties': dict({ }), 'timestamp': '2021-08-25T03:29:46.401Z', - 'uri': 'https://api.viessmann-platform.io/iot/v1/equipment/installations/######/gateways/################/devices/0/features/heating.burners', + 'uri': 'https://api.viessmann-platform.io/iot/v1/equipment/installations/######/gateways/################/devices/deviceId0/features/heating.burners', }), dict({ 'apiVersion': 1, @@ -1529,7 +1529,7 @@ 'properties': dict({ }), 'timestamp': '2021-08-25T03:29:46.400Z', - 'uri': 'https://api.viessmann-platform.io/iot/v1/equipment/installations/######/gateways/################/devices/0/features/heating.circuits.2.dhw.pumps.circulation', + 'uri': 'https://api.viessmann-platform.io/iot/v1/equipment/installations/######/gateways/################/devices/deviceId0/features/heating.circuits.2.dhw.pumps.circulation', }), dict({ 'apiVersion': 1, @@ -1546,7 +1546,7 @@ 'properties': dict({ }), 'timestamp': '2021-08-25T03:29:46.401Z', - 'uri': 'https://api.viessmann-platform.io/iot/v1/equipment/installations/######/gateways/################/devices/0/features/heating.solar.pumps', + 'uri': 'https://api.viessmann-platform.io/iot/v1/equipment/installations/######/gateways/################/devices/deviceId0/features/heating.solar.pumps', }), dict({ 'apiVersion': 1, @@ -1562,7 +1562,7 @@ 'properties': dict({ }), 'timestamp': '2021-08-25T03:29:47.708Z', - 'uri': 'https://api.viessmann-platform.io/iot/v1/equipment/installations/######/gateways/################/devices/0/features/heating.buffer.charging.level.top', + 'uri': 'https://api.viessmann-platform.io/iot/v1/equipment/installations/######/gateways/################/devices/deviceId0/features/heating.buffer.charging.level.top', }), dict({ 'apiVersion': 1, @@ -1579,7 +1579,7 @@ 'properties': dict({ }), 'timestamp': '2021-08-25T03:29:46.401Z', - 'uri': 'https://api.viessmann-platform.io/iot/v1/equipment/installations/######/gateways/################/devices/0/features/heating.solar.sensors', + 'uri': 'https://api.viessmann-platform.io/iot/v1/equipment/installations/######/gateways/################/devices/deviceId0/features/heating.solar.sensors', }), dict({ 'apiVersion': 1, @@ -1598,7 +1598,7 @@ 'properties': dict({ }), 'timestamp': '2021-08-25T03:29:46.401Z', - 'uri': 'https://api.viessmann-platform.io/iot/v1/equipment/installations/######/gateways/################/devices/0/features/heating.boiler', + 'uri': 'https://api.viessmann-platform.io/iot/v1/equipment/installations/######/gateways/################/devices/deviceId0/features/heating.boiler', }), dict({ 'apiVersion': 1, @@ -1614,7 +1614,7 @@ 'properties': dict({ }), 'timestamp': '2021-08-25T03:29:47.545Z', - 'uri': 'https://api.viessmann-platform.io/iot/v1/equipment/installations/######/gateways/################/devices/0/features/heating.circuits.2.operating.programs.holiday', + 'uri': 'https://api.viessmann-platform.io/iot/v1/equipment/installations/######/gateways/################/devices/deviceId0/features/heating.circuits.2.operating.programs.holiday', }), dict({ 'apiVersion': 1, @@ -1643,7 +1643,7 @@ }), }), 'timestamp': '2021-08-25T15:07:33.251Z', - 'uri': 'https://api.viessmann-platform.io/iot/v1/equipment/installations/######/gateways/################/devices/0/features/heating.sensors.temperature.outside', + 'uri': 'https://api.viessmann-platform.io/iot/v1/equipment/installations/######/gateways/################/devices/deviceId0/features/heating.sensors.temperature.outside', }), dict({ 'apiVersion': 1, @@ -1659,7 +1659,7 @@ 'properties': dict({ }), 'timestamp': '2021-08-25T03:29:47.566Z', - 'uri': 'https://api.viessmann-platform.io/iot/v1/equipment/installations/######/gateways/################/devices/0/features/heating.circuits.2.sensors.temperature.room', + 'uri': 'https://api.viessmann-platform.io/iot/v1/equipment/installations/######/gateways/################/devices/deviceId0/features/heating.circuits.2.sensors.temperature.room', }), dict({ 'apiVersion': 1, @@ -1677,7 +1677,7 @@ 'properties': dict({ }), 'timestamp': '2021-08-25T03:29:46.400Z', - 'uri': 'https://api.viessmann-platform.io/iot/v1/equipment/installations/######/gateways/################/devices/0/features/heating.circuits.0.operating', + 'uri': 'https://api.viessmann-platform.io/iot/v1/equipment/installations/######/gateways/################/devices/deviceId0/features/heating.circuits.0.operating', }), dict({ 'apiVersion': 1, @@ -1810,7 +1810,7 @@ }), }), 'timestamp': '2021-08-25T15:13:35.950Z', - 'uri': 'https://api.viessmann-platform.io/iot/v1/equipment/installations/######/gateways/################/devices/0/features/heating.power.consumption.total', + 'uri': 'https://api.viessmann-platform.io/iot/v1/equipment/installations/######/gateways/################/devices/deviceId0/features/heating.power.consumption.total', }), dict({ 'apiVersion': 1, @@ -1828,7 +1828,7 @@ 'properties': dict({ }), 'timestamp': '2021-08-25T03:29:46.400Z', - 'uri': 'https://api.viessmann-platform.io/iot/v1/equipment/installations/######/gateways/################/devices/0/features/heating.circuits.2.dhw', + 'uri': 'https://api.viessmann-platform.io/iot/v1/equipment/installations/######/gateways/################/devices/deviceId0/features/heating.circuits.2.dhw', }), dict({ 'apiVersion': 1, @@ -1844,7 +1844,7 @@ 'properties': dict({ }), 'timestamp': '2021-08-25T03:29:47.724Z', - 'uri': 'https://api.viessmann-platform.io/iot/v1/equipment/installations/######/gateways/################/devices/0/features/ventilation.operating.modes.active', + 'uri': 'https://api.viessmann-platform.io/iot/v1/equipment/installations/######/gateways/################/devices/deviceId0/features/ventilation.operating.modes.active', }), dict({ 'apiVersion': 1, @@ -1893,7 +1893,7 @@ }), }), 'timestamp': '2021-08-25T03:29:46.861Z', - 'uri': 'https://api.viessmann-platform.io/iot/v1/equipment/installations/######/gateways/################/devices/0/features/heating.circuits.1', + 'uri': 'https://api.viessmann-platform.io/iot/v1/equipment/installations/######/gateways/################/devices/deviceId0/features/heating.circuits.1', }), dict({ 'apiVersion': 1, @@ -2026,7 +2026,7 @@ }), }), 'timestamp': '2021-08-25T03:29:47.627Z', - 'uri': 'https://api.viessmann-platform.io/iot/v1/equipment/installations/######/gateways/################/devices/0/features/heating.gas.consumption.heating', + 'uri': 'https://api.viessmann-platform.io/iot/v1/equipment/installations/######/gateways/################/devices/deviceId0/features/heating.gas.consumption.heating', }), dict({ 'apiVersion': 1, @@ -2042,7 +2042,7 @@ 'properties': dict({ }), 'timestamp': '2021-08-25T03:29:47.556Z', - 'uri': 'https://api.viessmann-platform.io/iot/v1/equipment/installations/######/gateways/################/devices/0/features/heating.circuits.2.operating.programs.reduced', + 'uri': 'https://api.viessmann-platform.io/iot/v1/equipment/installations/######/gateways/################/devices/deviceId0/features/heating.circuits.2.operating.programs.reduced', }), dict({ 'apiVersion': 1, @@ -2143,7 +2143,7 @@ }), }), 'timestamp': '2021-08-25T03:29:46.866Z', - 'uri': 'https://api.viessmann-platform.io/iot/v1/equipment/installations/######/gateways/################/devices/0/features/heating.circuits.0.dhw.pumps.circulation.schedule', + 'uri': 'https://api.viessmann-platform.io/iot/v1/equipment/installations/######/gateways/################/devices/deviceId0/features/heating.circuits.0.dhw.pumps.circulation.schedule', }), dict({ 'apiVersion': 1, @@ -2159,7 +2159,7 @@ 'properties': dict({ }), 'timestamp': '2021-08-25T03:29:47.719Z', - 'uri': 'https://api.viessmann-platform.io/iot/v1/equipment/installations/######/gateways/################/devices/0/features/ventilation.operating.programs.standard', + 'uri': 'https://api.viessmann-platform.io/iot/v1/equipment/installations/######/gateways/################/devices/deviceId0/features/ventilation.operating.programs.standard', }), dict({ 'apiVersion': 1, @@ -2176,7 +2176,7 @@ 'properties': dict({ }), 'timestamp': '2021-08-25T03:29:46.400Z', - 'uri': 'https://api.viessmann-platform.io/iot/v1/equipment/installations/######/gateways/################/devices/0/features/heating.circuits.1.dhw.pumps.circulation', + 'uri': 'https://api.viessmann-platform.io/iot/v1/equipment/installations/######/gateways/################/devices/deviceId0/features/heating.circuits.1.dhw.pumps.circulation', }), dict({ 'apiVersion': 1, @@ -2307,7 +2307,7 @@ }), }), 'timestamp': '2021-08-25T03:29:46.883Z', - 'uri': 'https://api.viessmann-platform.io/iot/v1/equipment/installations/######/gateways/################/devices/0/features/heating.circuits.1.dhw.schedule', + 'uri': 'https://api.viessmann-platform.io/iot/v1/equipment/installations/######/gateways/################/devices/deviceId0/features/heating.circuits.1.dhw.schedule', }), dict({ 'apiVersion': 1, @@ -2324,7 +2324,7 @@ 'properties': dict({ }), 'timestamp': '2021-08-25T03:29:46.400Z', - 'uri': 'https://api.viessmann-platform.io/iot/v1/equipment/installations/######/gateways/################/devices/0/features/heating.circuits.2.dhw.pumps', + 'uri': 'https://api.viessmann-platform.io/iot/v1/equipment/installations/######/gateways/################/devices/deviceId0/features/heating.circuits.2.dhw.pumps', }), dict({ 'apiVersion': 1, @@ -2340,7 +2340,7 @@ 'properties': dict({ }), 'timestamp': '2021-08-25T03:29:47.540Z', - 'uri': 'https://api.viessmann-platform.io/iot/v1/equipment/installations/######/gateways/################/devices/0/features/heating.circuits.2.operating.programs.external', + 'uri': 'https://api.viessmann-platform.io/iot/v1/equipment/installations/######/gateways/################/devices/deviceId0/features/heating.circuits.2.operating.programs.external', }), dict({ 'apiVersion': 1, @@ -2357,7 +2357,7 @@ 'properties': dict({ }), 'timestamp': '2021-08-25T03:29:46.401Z', - 'uri': 'https://api.viessmann-platform.io/iot/v1/equipment/installations/######/gateways/################/devices/0/features/heating.configuration', + 'uri': 'https://api.viessmann-platform.io/iot/v1/equipment/installations/######/gateways/################/devices/deviceId0/features/heating.configuration', }), dict({ 'apiVersion': 1, @@ -2375,7 +2375,7 @@ 'properties': dict({ }), 'timestamp': '2021-08-25T03:29:46.400Z', - 'uri': 'https://api.viessmann-platform.io/iot/v1/equipment/installations/######/gateways/################/devices/0/features/heating.circuits.1.dhw', + 'uri': 'https://api.viessmann-platform.io/iot/v1/equipment/installations/######/gateways/################/devices/deviceId0/features/heating.circuits.1.dhw', }), dict({ 'apiVersion': 1, @@ -2391,7 +2391,7 @@ 'properties': dict({ }), 'timestamp': '2021-08-25T03:29:47.720Z', - 'uri': 'https://api.viessmann-platform.io/iot/v1/equipment/installations/######/gateways/################/devices/0/features/ventilation.operating.programs.eco', + 'uri': 'https://api.viessmann-platform.io/iot/v1/equipment/installations/######/gateways/################/devices/deviceId0/features/ventilation.operating.programs.eco', }), dict({ 'apiVersion': 1, @@ -2416,7 +2416,7 @@ }), }), 'timestamp': '2021-08-25T14:16:46.376Z', - 'uri': 'https://api.viessmann-platform.io/iot/v1/equipment/installations/######/gateways/################/devices/0/features/heating.boiler.temperature', + 'uri': 'https://api.viessmann-platform.io/iot/v1/equipment/installations/######/gateways/################/devices/deviceId0/features/heating.boiler.temperature', }), dict({ 'apiVersion': 1, @@ -2436,7 +2436,7 @@ }), }), 'timestamp': '2021-08-25T03:29:46.840Z', - 'uri': 'https://api.viessmann-platform.io/iot/v1/equipment/installations/######/gateways/################/devices/0/features/heating.boiler.serial', + 'uri': 'https://api.viessmann-platform.io/iot/v1/equipment/installations/######/gateways/################/devices/deviceId0/features/heating.boiler.serial', }), dict({ 'apiVersion': 1, @@ -2454,7 +2454,7 @@ 'properties': dict({ }), 'timestamp': '2021-08-25T03:29:46.400Z', - 'uri': 'https://api.viessmann-platform.io/iot/v1/equipment/installations/######/gateways/################/devices/0/features/heating.circuits.1.heating', + 'uri': 'https://api.viessmann-platform.io/iot/v1/equipment/installations/######/gateways/################/devices/deviceId0/features/heating.circuits.1.heating', }), dict({ 'apiVersion': 1, @@ -2475,7 +2475,7 @@ }), }), 'timestamp': '2021-08-25T03:29:47.609Z', - 'uri': 'https://api.viessmann-platform.io/iot/v1/equipment/installations/######/gateways/################/devices/0/features/heating.dhw.pumps.circulation', + 'uri': 'https://api.viessmann-platform.io/iot/v1/equipment/installations/######/gateways/################/devices/deviceId0/features/heating.dhw.pumps.circulation', }), dict({ 'apiVersion': 1, @@ -2495,7 +2495,7 @@ }), }), 'timestamp': '2021-08-25T03:29:47.693Z', - 'uri': 'https://api.viessmann-platform.io/iot/v1/equipment/installations/######/gateways/################/devices/0/features/heating.configuration.multiFamilyHouse', + 'uri': 'https://api.viessmann-platform.io/iot/v1/equipment/installations/######/gateways/################/devices/deviceId0/features/heating.configuration.multiFamilyHouse', }), dict({ 'apiVersion': 1, @@ -2519,7 +2519,7 @@ 'properties': dict({ }), 'timestamp': '2021-08-25T03:29:46.400Z', - 'uri': 'https://api.viessmann-platform.io/iot/v1/equipment/installations/######/gateways/################/devices/0/features/heating.circuits.0.operating.programs', + 'uri': 'https://api.viessmann-platform.io/iot/v1/equipment/installations/######/gateways/################/devices/deviceId0/features/heating.circuits.0.operating.programs', }), dict({ 'apiVersion': 1, @@ -2537,7 +2537,7 @@ 'properties': dict({ }), 'timestamp': '2021-08-25T03:29:46.400Z', - 'uri': 'https://api.viessmann-platform.io/iot/v1/equipment/installations/######/gateways/################/devices/0/features/heating.circuits.2.operating', + 'uri': 'https://api.viessmann-platform.io/iot/v1/equipment/installations/######/gateways/################/devices/deviceId0/features/heating.circuits.2.operating', }), dict({ 'apiVersion': 1, @@ -2553,7 +2553,7 @@ 'properties': dict({ }), 'timestamp': '2021-08-25T03:29:47.533Z', - 'uri': 'https://api.viessmann-platform.io/iot/v1/equipment/installations/######/gateways/################/devices/0/features/heating.circuits.2.operating.modes.standby', + 'uri': 'https://api.viessmann-platform.io/iot/v1/equipment/installations/######/gateways/################/devices/deviceId0/features/heating.circuits.2.operating.modes.standby', }), dict({ 'apiVersion': 1, @@ -2573,7 +2573,7 @@ }), }), 'timestamp': '2021-08-25T03:29:47.558Z', - 'uri': 'https://api.viessmann-platform.io/iot/v1/equipment/installations/######/gateways/################/devices/0/features/heating.circuits.0.operating.programs.standby', + 'uri': 'https://api.viessmann-platform.io/iot/v1/equipment/installations/######/gateways/################/devices/deviceId0/features/heating.circuits.0.operating.programs.standby', }), dict({ 'apiVersion': 1, @@ -2589,7 +2589,7 @@ 'properties': dict({ }), 'timestamp': '2021-08-25T03:29:47.729Z', - 'uri': 'https://api.viessmann-platform.io/iot/v1/equipment/installations/######/gateways/################/devices/0/features/ventilation.operating.modes.ventilation', + 'uri': 'https://api.viessmann-platform.io/iot/v1/equipment/installations/######/gateways/################/devices/deviceId0/features/ventilation.operating.modes.ventilation', }), dict({ 'apiVersion': 1, @@ -2607,7 +2607,7 @@ 'properties': dict({ }), 'timestamp': '2021-08-25T03:29:46.400Z', - 'uri': 'https://api.viessmann-platform.io/iot/v1/equipment/installations/######/gateways/################/devices/0/features/heating.circuits.2.heating', + 'uri': 'https://api.viessmann-platform.io/iot/v1/equipment/installations/######/gateways/################/devices/deviceId0/features/heating.circuits.2.heating', }), dict({ 'apiVersion': 1, @@ -2623,7 +2623,7 @@ 'properties': dict({ }), 'timestamp': '2021-08-25T03:29:46.876Z', - 'uri': 'https://api.viessmann-platform.io/iot/v1/equipment/installations/######/gateways/################/devices/0/features/heating.circuits.2.dhw.pumps.circulation.schedule', + 'uri': 'https://api.viessmann-platform.io/iot/v1/equipment/installations/######/gateways/################/devices/deviceId0/features/heating.circuits.2.dhw.pumps.circulation.schedule', }), dict({ 'apiVersion': 1, @@ -2668,7 +2668,7 @@ }), }), 'timestamp': '2021-08-25T03:29:47.548Z', - 'uri': 'https://api.viessmann-platform.io/iot/v1/equipment/installations/######/gateways/################/devices/0/features/heating.circuits.1.operating.programs.normal', + 'uri': 'https://api.viessmann-platform.io/iot/v1/equipment/installations/######/gateways/################/devices/deviceId0/features/heating.circuits.1.operating.programs.normal', }), dict({ 'apiVersion': 1, @@ -2713,7 +2713,7 @@ }), }), 'timestamp': '2021-08-25T03:29:47.546Z', - 'uri': 'https://api.viessmann-platform.io/iot/v1/equipment/installations/######/gateways/################/devices/0/features/heating.circuits.0.operating.programs.normal', + 'uri': 'https://api.viessmann-platform.io/iot/v1/equipment/installations/######/gateways/################/devices/deviceId0/features/heating.circuits.0.operating.programs.normal', }), dict({ 'apiVersion': 1, @@ -2733,7 +2733,7 @@ }), }), 'timestamp': '2021-08-25T03:29:46.963Z', - 'uri': 'https://api.viessmann-platform.io/iot/v1/equipment/installations/######/gateways/################/devices/0/features/heating.circuits.1.operating.modes.dhwAndHeating', + 'uri': 'https://api.viessmann-platform.io/iot/v1/equipment/installations/######/gateways/################/devices/deviceId0/features/heating.circuits.1.operating.modes.dhwAndHeating', }), dict({ 'apiVersion': 1, @@ -2749,7 +2749,7 @@ 'properties': dict({ }), 'timestamp': '2021-08-25T03:29:47.649Z', - 'uri': 'https://api.viessmann-platform.io/iot/v1/equipment/installations/######/gateways/################/devices/0/features/heating.circuits.2.operating.programs.active', + 'uri': 'https://api.viessmann-platform.io/iot/v1/equipment/installations/######/gateways/################/devices/deviceId0/features/heating.circuits.2.operating.programs.active', }), dict({ 'apiVersion': 1, @@ -2769,7 +2769,7 @@ }), }), 'timestamp': '2021-08-25T03:29:46.933Z', - 'uri': 'https://api.viessmann-platform.io/iot/v1/equipment/installations/######/gateways/################/devices/0/features/heating.circuits.0.operating.modes.dhw', + 'uri': 'https://api.viessmann-platform.io/iot/v1/equipment/installations/######/gateways/################/devices/deviceId0/features/heating.circuits.0.operating.modes.dhw', }), dict({ 'apiVersion': 1, @@ -2785,7 +2785,7 @@ 'properties': dict({ }), 'timestamp': '2021-08-25T03:29:46.890Z', - 'uri': 'https://api.viessmann-platform.io/iot/v1/equipment/installations/######/gateways/################/devices/0/features/heating.circuits.2.dhw.schedule', + 'uri': 'https://api.viessmann-platform.io/iot/v1/equipment/installations/######/gateways/################/devices/deviceId0/features/heating.circuits.2.dhw.schedule', }), dict({ 'apiVersion': 1, @@ -2853,7 +2853,7 @@ }), }), 'timestamp': '2021-08-25T03:29:46.827Z', - 'uri': 'https://api.viessmann-platform.io/iot/v1/equipment/installations/######/gateways/################/devices/0/features/heating.circuits.1.operating.programs.comfort', + 'uri': 'https://api.viessmann-platform.io/iot/v1/equipment/installations/######/gateways/################/devices/deviceId0/features/heating.circuits.1.operating.programs.comfort', }), dict({ 'apiVersion': 1, @@ -2873,7 +2873,7 @@ }), }), 'timestamp': '2021-08-25T03:29:47.559Z', - 'uri': 'https://api.viessmann-platform.io/iot/v1/equipment/installations/######/gateways/################/devices/0/features/heating.circuits.1.operating.programs.standby', + 'uri': 'https://api.viessmann-platform.io/iot/v1/equipment/installations/######/gateways/################/devices/deviceId0/features/heating.circuits.1.operating.programs.standby', }), dict({ 'apiVersion': 1, @@ -2924,7 +2924,7 @@ }), }), 'timestamp': '2021-08-25T03:29:46.906Z', - 'uri': 'https://api.viessmann-platform.io/iot/v1/equipment/installations/######/gateways/################/devices/0/features/heating.circuits.0.heating.curve', + 'uri': 'https://api.viessmann-platform.io/iot/v1/equipment/installations/######/gateways/################/devices/deviceId0/features/heating.circuits.0.heating.curve', }), dict({ 'apiVersion': 1, @@ -2940,7 +2940,7 @@ 'properties': dict({ }), 'timestamp': '2021-08-25T03:29:47.552Z', - 'uri': 'https://api.viessmann-platform.io/iot/v1/equipment/installations/######/gateways/################/devices/0/features/heating.circuits.2.operating.programs.eco', + 'uri': 'https://api.viessmann-platform.io/iot/v1/equipment/installations/######/gateways/################/devices/deviceId0/features/heating.circuits.2.operating.programs.eco', }), dict({ 'apiVersion': 1, @@ -3073,7 +3073,7 @@ }), }), 'timestamp': '2021-08-25T14:16:41.758Z', - 'uri': 'https://api.viessmann-platform.io/iot/v1/equipment/installations/######/gateways/################/devices/0/features/heating.gas.consumption.dhw', + 'uri': 'https://api.viessmann-platform.io/iot/v1/equipment/installations/######/gateways/################/devices/deviceId0/features/heating.gas.consumption.dhw', }), dict({ 'apiVersion': 1, @@ -3090,7 +3090,7 @@ 'properties': dict({ }), 'timestamp': '2021-08-25T03:29:46.401Z', - 'uri': 'https://api.viessmann-platform.io/iot/v1/equipment/installations/######/gateways/################/devices/0/features/heating.circuits.1.sensors', + 'uri': 'https://api.viessmann-platform.io/iot/v1/equipment/installations/######/gateways/################/devices/deviceId0/features/heating.circuits.1.sensors', }), dict({ 'apiVersion': 1, @@ -3116,7 +3116,7 @@ }), }), 'timestamp': '2021-08-25T03:29:46.864Z', - 'uri': 'https://api.viessmann-platform.io/iot/v1/equipment/installations/######/gateways/################/devices/0/features/heating.circuits', + 'uri': 'https://api.viessmann-platform.io/iot/v1/equipment/installations/######/gateways/################/devices/deviceId0/features/heating.circuits', }), dict({ 'apiVersion': 1, @@ -3136,7 +3136,7 @@ }), }), 'timestamp': '2021-08-25T03:29:47.643Z', - 'uri': 'https://api.viessmann-platform.io/iot/v1/equipment/installations/######/gateways/################/devices/0/features/heating.circuits.0.operating.programs.active', + 'uri': 'https://api.viessmann-platform.io/iot/v1/equipment/installations/######/gateways/################/devices/deviceId0/features/heating.circuits.0.operating.programs.active', }), dict({ 'apiVersion': 1, @@ -3152,7 +3152,7 @@ 'properties': dict({ }), 'timestamp': '2021-08-25T03:29:47.634Z', - 'uri': 'https://api.viessmann-platform.io/iot/v1/equipment/installations/######/gateways/################/devices/0/features/heating.solar.power.production', + 'uri': 'https://api.viessmann-platform.io/iot/v1/equipment/installations/######/gateways/################/devices/deviceId0/features/heating.solar.power.production', }), dict({ 'apiVersion': 1, @@ -3169,7 +3169,7 @@ 'properties': dict({ }), 'timestamp': '2021-08-25T03:29:46.401Z', - 'uri': 'https://api.viessmann-platform.io/iot/v1/equipment/installations/######/gateways/################/devices/0/features/heating.circuits.2.sensors', + 'uri': 'https://api.viessmann-platform.io/iot/v1/equipment/installations/######/gateways/################/devices/deviceId0/features/heating.circuits.2.sensors', }), dict({ 'apiVersion': 1, @@ -3208,7 +3208,7 @@ }), }), 'timestamp': '2021-08-25T03:29:47.547Z', - 'uri': 'https://api.viessmann-platform.io/iot/v1/equipment/installations/######/gateways/################/devices/0/features/heating.circuits.0.operating.programs.eco', + 'uri': 'https://api.viessmann-platform.io/iot/v1/equipment/installations/######/gateways/################/devices/deviceId0/features/heating.circuits.0.operating.programs.eco', }), dict({ 'apiVersion': 1, @@ -3224,7 +3224,7 @@ 'properties': dict({ }), 'timestamp': '2021-08-25T03:29:47.551Z', - 'uri': 'https://api.viessmann-platform.io/iot/v1/equipment/installations/######/gateways/################/devices/0/features/heating.circuits.2.operating.programs.normal', + 'uri': 'https://api.viessmann-platform.io/iot/v1/equipment/installations/######/gateways/################/devices/deviceId0/features/heating.circuits.2.operating.programs.normal', }), dict({ 'apiVersion': 1, @@ -3253,7 +3253,7 @@ }), }), 'timestamp': '2021-08-25T03:29:47.650Z', - 'uri': 'https://api.viessmann-platform.io/iot/v1/equipment/installations/######/gateways/################/devices/0/features/heating.dhw', + 'uri': 'https://api.viessmann-platform.io/iot/v1/equipment/installations/######/gateways/################/devices/deviceId0/features/heating.dhw', }), dict({ 'apiVersion': 1, @@ -3269,7 +3269,7 @@ 'properties': dict({ }), 'timestamp': '2021-08-25T03:29:47.642Z', - 'uri': 'https://api.viessmann-platform.io/iot/v1/equipment/installations/######/gateways/################/devices/0/features/heating.circuits.2.circulation.pump', + 'uri': 'https://api.viessmann-platform.io/iot/v1/equipment/installations/######/gateways/################/devices/deviceId0/features/heating.circuits.2.circulation.pump', }), dict({ 'apiVersion': 1, @@ -3298,7 +3298,7 @@ }), }), 'timestamp': '2021-08-25T15:13:19.598Z', - 'uri': 'https://api.viessmann-platform.io/iot/v1/equipment/installations/######/gateways/################/devices/0/features/heating.boiler.sensors.temperature.main', + 'uri': 'https://api.viessmann-platform.io/iot/v1/equipment/installations/######/gateways/################/devices/deviceId0/features/heating.boiler.sensors.temperature.main', }), dict({ 'apiVersion': 1, @@ -3318,7 +3318,7 @@ }), }), 'timestamp': '2021-08-25T03:29:47.641Z', - 'uri': 'https://api.viessmann-platform.io/iot/v1/equipment/installations/######/gateways/################/devices/0/features/heating.circuits.1.circulation.pump', + 'uri': 'https://api.viessmann-platform.io/iot/v1/equipment/installations/######/gateways/################/devices/deviceId0/features/heating.circuits.1.circulation.pump', }), dict({ 'apiVersion': 1, @@ -3357,7 +3357,7 @@ }), }), 'timestamp': '2021-08-25T03:29:47.549Z', - 'uri': 'https://api.viessmann-platform.io/iot/v1/equipment/installations/######/gateways/################/devices/0/features/heating.circuits.1.operating.programs.eco', + 'uri': 'https://api.viessmann-platform.io/iot/v1/equipment/installations/######/gateways/################/devices/deviceId0/features/heating.circuits.1.operating.programs.eco', }), dict({ 'apiVersion': 1, @@ -3393,7 +3393,7 @@ }), }), 'timestamp': '2021-08-25T03:29:47.603Z', - 'uri': 'https://api.viessmann-platform.io/iot/v1/equipment/installations/######/gateways/################/devices/0/features/heating.dhw.charging.level', + 'uri': 'https://api.viessmann-platform.io/iot/v1/equipment/installations/######/gateways/################/devices/deviceId0/features/heating.dhw.charging.level', }), dict({ 'apiVersion': 1, @@ -3410,7 +3410,7 @@ 'properties': dict({ }), 'timestamp': '2021-08-25T03:29:46.400Z', - 'uri': 'https://api.viessmann-platform.io/iot/v1/equipment/installations/######/gateways/################/devices/0/features/heating.circuits.1.circulation', + 'uri': 'https://api.viessmann-platform.io/iot/v1/equipment/installations/######/gateways/################/devices/deviceId0/features/heating.circuits.1.circulation', }), dict({ 'apiVersion': 1, @@ -3426,7 +3426,7 @@ 'properties': dict({ }), 'timestamp': '2021-08-25T03:29:47.728Z', - 'uri': 'https://api.viessmann-platform.io/iot/v1/equipment/installations/######/gateways/################/devices/0/features/ventilation.operating.modes.standard', + 'uri': 'https://api.viessmann-platform.io/iot/v1/equipment/installations/######/gateways/################/devices/deviceId0/features/ventilation.operating.modes.standard', }), dict({ 'apiVersion': 1, @@ -3443,7 +3443,7 @@ 'properties': dict({ }), 'timestamp': '2021-08-25T03:29:46.400Z', - 'uri': 'https://api.viessmann-platform.io/iot/v1/equipment/installations/######/gateways/################/devices/0/features/heating.operating.programs', + 'uri': 'https://api.viessmann-platform.io/iot/v1/equipment/installations/######/gateways/################/devices/deviceId0/features/heating.operating.programs', }), dict({ 'apiVersion': 1, @@ -3544,7 +3544,7 @@ }), }), 'timestamp': '2021-08-25T03:29:46.880Z', - 'uri': 'https://api.viessmann-platform.io/iot/v1/equipment/installations/######/gateways/################/devices/0/features/heating.circuits.0.dhw.schedule', + 'uri': 'https://api.viessmann-platform.io/iot/v1/equipment/installations/######/gateways/################/devices/deviceId0/features/heating.circuits.0.dhw.schedule', }), dict({ 'apiVersion': 1, @@ -3563,7 +3563,7 @@ 'properties': dict({ }), 'timestamp': '2021-08-25T03:29:46.400Z', - 'uri': 'https://api.viessmann-platform.io/iot/v1/equipment/installations/######/gateways/################/devices/0/features/ventilation.operating.programs', + 'uri': 'https://api.viessmann-platform.io/iot/v1/equipment/installations/######/gateways/################/devices/deviceId0/features/ventilation.operating.programs', }), dict({ 'apiVersion': 1, @@ -3664,7 +3664,7 @@ }), }), 'timestamp': '2021-08-25T03:29:46.871Z', - 'uri': 'https://api.viessmann-platform.io/iot/v1/equipment/installations/######/gateways/################/devices/0/features/heating.circuits.1.dhw.pumps.circulation.schedule', + 'uri': 'https://api.viessmann-platform.io/iot/v1/equipment/installations/######/gateways/################/devices/deviceId0/features/heating.circuits.1.dhw.pumps.circulation.schedule', }), dict({ 'apiVersion': 1, @@ -3682,7 +3682,7 @@ 'properties': dict({ }), 'timestamp': '2021-08-25T03:29:46.401Z', - 'uri': 'https://api.viessmann-platform.io/iot/v1/equipment/installations/######/gateways/################/devices/0/features/heating.circuits.0.sensors.temperature', + 'uri': 'https://api.viessmann-platform.io/iot/v1/equipment/installations/######/gateways/################/devices/deviceId0/features/heating.circuits.0.sensors.temperature', }), dict({ 'apiVersion': 1, @@ -3698,7 +3698,7 @@ 'properties': dict({ }), 'timestamp': '2021-08-25T03:29:47.710Z', - 'uri': 'https://api.viessmann-platform.io/iot/v1/equipment/installations/######/gateways/################/devices/0/features/heating.buffer.charging.level.middle', + 'uri': 'https://api.viessmann-platform.io/iot/v1/equipment/installations/######/gateways/################/devices/deviceId0/features/heating.buffer.charging.level.middle', }), dict({ 'apiVersion': 1, @@ -3718,7 +3718,7 @@ }), }), 'timestamp': '2021-08-25T03:29:47.508Z', - 'uri': 'https://api.viessmann-platform.io/iot/v1/equipment/installations/######/gateways/################/devices/0/features/heating.circuits.0.operating.modes.standby', + 'uri': 'https://api.viessmann-platform.io/iot/v1/equipment/installations/######/gateways/################/devices/deviceId0/features/heating.circuits.0.operating.modes.standby', }), dict({ 'apiVersion': 1, @@ -3757,7 +3757,7 @@ }), }), 'timestamp': '2021-08-25T03:29:46.819Z', - 'uri': 'https://api.viessmann-platform.io/iot/v1/equipment/installations/######/gateways/################/devices/0/features/heating.dhw.temperature.main', + 'uri': 'https://api.viessmann-platform.io/iot/v1/equipment/installations/######/gateways/################/devices/deviceId0/features/heating.dhw.temperature.main', }), dict({ 'apiVersion': 1, @@ -3791,7 +3791,7 @@ }), }), 'timestamp': '2021-08-25T03:29:47.607Z', - 'uri': 'https://api.viessmann-platform.io/iot/v1/equipment/installations/######/gateways/################/devices/0/features/heating.dhw.oneTimeCharge', + 'uri': 'https://api.viessmann-platform.io/iot/v1/equipment/installations/######/gateways/################/devices/deviceId0/features/heating.dhw.oneTimeCharge', }), dict({ 'apiVersion': 1, @@ -3924,7 +3924,7 @@ }), }), 'timestamp': '2021-08-25T14:16:41.785Z', - 'uri': 'https://api.viessmann-platform.io/iot/v1/equipment/installations/######/gateways/################/devices/0/features/heating.gas.consumption.total', + 'uri': 'https://api.viessmann-platform.io/iot/v1/equipment/installations/######/gateways/################/devices/deviceId0/features/heating.gas.consumption.total', }), dict({ 'apiVersion': 1, @@ -3941,7 +3941,7 @@ 'properties': dict({ }), 'timestamp': '2021-08-25T03:29:46.401Z', - 'uri': 'https://api.viessmann-platform.io/iot/v1/equipment/installations/######/gateways/################/devices/0/features/heating.circuits.0.sensors', + 'uri': 'https://api.viessmann-platform.io/iot/v1/equipment/installations/######/gateways/################/devices/deviceId0/features/heating.circuits.0.sensors', }), dict({ 'apiVersion': 1, @@ -3966,7 +3966,7 @@ }), }), 'timestamp': '2021-08-25T14:16:46.499Z', - 'uri': 'https://api.viessmann-platform.io/iot/v1/equipment/installations/######/gateways/################/devices/0/features/heating.burners.0.modulation', + 'uri': 'https://api.viessmann-platform.io/iot/v1/equipment/installations/######/gateways/################/devices/deviceId0/features/heating.burners.0.modulation', }), dict({ 'apiVersion': 1, @@ -3983,7 +3983,7 @@ 'properties': dict({ }), 'timestamp': '2021-08-25T03:29:46.401Z', - 'uri': 'https://api.viessmann-platform.io/iot/v1/equipment/installations/######/gateways/################/devices/0/features/heating.power.consumption', + 'uri': 'https://api.viessmann-platform.io/iot/v1/equipment/installations/######/gateways/################/devices/deviceId0/features/heating.power.consumption', }), dict({ 'apiVersion': 1, @@ -4028,7 +4028,7 @@ }), }), 'timestamp': '2021-08-25T03:29:47.555Z', - 'uri': 'https://api.viessmann-platform.io/iot/v1/equipment/installations/######/gateways/################/devices/0/features/heating.circuits.1.operating.programs.reduced', + 'uri': 'https://api.viessmann-platform.io/iot/v1/equipment/installations/######/gateways/################/devices/deviceId0/features/heating.circuits.1.operating.programs.reduced', }), dict({ 'apiVersion': 1, @@ -4045,7 +4045,7 @@ 'properties': dict({ }), 'timestamp': '2021-08-25T03:29:46.401Z', - 'uri': 'https://api.viessmann-platform.io/iot/v1/equipment/installations/######/gateways/################/devices/0/features/heating.sensors.temperature', + 'uri': 'https://api.viessmann-platform.io/iot/v1/equipment/installations/######/gateways/################/devices/deviceId0/features/heating.sensors.temperature', }), dict({ 'apiVersion': 1, @@ -4061,7 +4061,7 @@ 'properties': dict({ }), 'timestamp': '2021-08-25T03:29:47.564Z', - 'uri': 'https://api.viessmann-platform.io/iot/v1/equipment/installations/######/gateways/################/devices/0/features/heating.circuits.1.sensors.temperature.room', + 'uri': 'https://api.viessmann-platform.io/iot/v1/equipment/installations/######/gateways/################/devices/deviceId0/features/heating.circuits.1.sensors.temperature.room', }), dict({ 'apiVersion': 1, @@ -4077,7 +4077,7 @@ 'properties': dict({ }), 'timestamp': '2021-08-25T03:29:46.401Z', - 'uri': 'https://api.viessmann-platform.io/iot/v1/equipment/installations/######/gateways/################/devices/0/features/heating.boiler.sensors', + 'uri': 'https://api.viessmann-platform.io/iot/v1/equipment/installations/######/gateways/################/devices/deviceId0/features/heating.boiler.sensors', }), dict({ 'apiVersion': 1, @@ -4095,7 +4095,7 @@ 'properties': dict({ }), 'timestamp': '2021-08-25T03:29:46.401Z', - 'uri': 'https://api.viessmann-platform.io/iot/v1/equipment/installations/######/gateways/################/devices/0/features/heating.solar.sensors.temperature', + 'uri': 'https://api.viessmann-platform.io/iot/v1/equipment/installations/######/gateways/################/devices/deviceId0/features/heating.solar.sensors.temperature', }), dict({ 'apiVersion': 1, @@ -4116,7 +4116,7 @@ }), }), 'timestamp': '2021-08-25T14:16:41.453Z', - 'uri': 'https://api.viessmann-platform.io/iot/v1/equipment/installations/######/gateways/################/devices/0/features/heating.dhw.charging', + 'uri': 'https://api.viessmann-platform.io/iot/v1/equipment/installations/######/gateways/################/devices/deviceId0/features/heating.dhw.charging', }), dict({ 'apiVersion': 1, @@ -4136,7 +4136,7 @@ }), }), 'timestamp': '2021-08-25T03:29:47.524Z', - 'uri': 'https://api.viessmann-platform.io/iot/v1/equipment/installations/######/gateways/################/devices/0/features/heating.circuits.1.operating.modes.standby', + 'uri': 'https://api.viessmann-platform.io/iot/v1/equipment/installations/######/gateways/################/devices/deviceId0/features/heating.circuits.1.operating.modes.standby', }), dict({ 'apiVersion': 1, @@ -4153,7 +4153,7 @@ 'properties': dict({ }), 'timestamp': '2021-08-25T03:29:46.401Z', - 'uri': 'https://api.viessmann-platform.io/iot/v1/equipment/installations/######/gateways/################/devices/0/features/heating.buffer', + 'uri': 'https://api.viessmann-platform.io/iot/v1/equipment/installations/######/gateways/################/devices/deviceId0/features/heating.buffer', }), dict({ 'apiVersion': 1, @@ -4170,7 +4170,7 @@ 'properties': dict({ }), 'timestamp': '2021-08-25T03:29:46.400Z', - 'uri': 'https://api.viessmann-platform.io/iot/v1/equipment/installations/######/gateways/################/devices/0/features/heating.dhw.temperature', + 'uri': 'https://api.viessmann-platform.io/iot/v1/equipment/installations/######/gateways/################/devices/deviceId0/features/heating.dhw.temperature', }), dict({ 'apiVersion': 1, @@ -4190,7 +4190,7 @@ }), }), 'timestamp': '2021-08-25T03:29:47.645Z', - 'uri': 'https://api.viessmann-platform.io/iot/v1/equipment/installations/######/gateways/################/devices/0/features/heating.circuits.1.operating.programs.active', + 'uri': 'https://api.viessmann-platform.io/iot/v1/equipment/installations/######/gateways/################/devices/deviceId0/features/heating.circuits.1.operating.programs.active', }), dict({ 'apiVersion': 1, @@ -4206,7 +4206,7 @@ 'properties': dict({ }), 'timestamp': '2021-08-25T03:29:47.695Z', - 'uri': 'https://api.viessmann-platform.io/iot/v1/equipment/installations/######/gateways/################/devices/0/features/heating.dhw.schedule', + 'uri': 'https://api.viessmann-platform.io/iot/v1/equipment/installations/######/gateways/################/devices/deviceId0/features/heating.dhw.schedule', }), dict({ 'apiVersion': 1, @@ -4223,7 +4223,7 @@ 'properties': dict({ }), 'timestamp': '2021-08-25T03:29:46.401Z', - 'uri': 'https://api.viessmann-platform.io/iot/v1/equipment/installations/######/gateways/################/devices/0/features/heating.buffer.charging', + 'uri': 'https://api.viessmann-platform.io/iot/v1/equipment/installations/######/gateways/################/devices/deviceId0/features/heating.buffer.charging', }), dict({ 'apiVersion': 1, @@ -4239,7 +4239,7 @@ 'properties': dict({ }), 'timestamp': '2021-08-25T03:29:46.830Z', - 'uri': 'https://api.viessmann-platform.io/iot/v1/equipment/installations/######/gateways/################/devices/0/features/heating.circuits.2.operating.programs.comfort', + 'uri': 'https://api.viessmann-platform.io/iot/v1/equipment/installations/######/gateways/################/devices/deviceId0/features/heating.circuits.2.operating.programs.comfort', }), dict({ 'apiVersion': 1, @@ -4260,7 +4260,7 @@ 'properties': dict({ }), 'timestamp': '2021-08-25T03:29:46.401Z', - 'uri': 'https://api.viessmann-platform.io/iot/v1/equipment/installations/######/gateways/################/devices/0/features/heating.circuits.0.operating.modes', + 'uri': 'https://api.viessmann-platform.io/iot/v1/equipment/installations/######/gateways/################/devices/deviceId0/features/heating.circuits.0.operating.modes', }), dict({ 'apiVersion': 1, @@ -4280,7 +4280,7 @@ 'properties': dict({ }), 'timestamp': '2021-08-25T03:29:46.400Z', - 'uri': 'https://api.viessmann-platform.io/iot/v1/equipment/installations/######/gateways/################/devices/0/features/ventilation.operating.modes', + 'uri': 'https://api.viessmann-platform.io/iot/v1/equipment/installations/######/gateways/################/devices/deviceId0/features/ventilation.operating.modes', }), dict({ 'apiVersion': 1, @@ -4297,7 +4297,7 @@ 'properties': dict({ }), 'timestamp': '2021-08-25T03:29:46.400Z', - 'uri': 'https://api.viessmann-platform.io/iot/v1/equipment/installations/######/gateways/################/devices/0/features/heating.circuits.1.dhw.pumps', + 'uri': 'https://api.viessmann-platform.io/iot/v1/equipment/installations/######/gateways/################/devices/deviceId0/features/heating.circuits.1.dhw.pumps', }), dict({ 'apiVersion': 1, @@ -4321,7 +4321,7 @@ 'properties': dict({ }), 'timestamp': '2021-08-25T03:29:46.400Z', - 'uri': 'https://api.viessmann-platform.io/iot/v1/equipment/installations/######/gateways/################/devices/0/features/heating.circuits.1.operating.programs', + 'uri': 'https://api.viessmann-platform.io/iot/v1/equipment/installations/######/gateways/################/devices/deviceId0/features/heating.circuits.1.operating.programs', }), dict({ 'apiVersion': 1, @@ -4337,7 +4337,7 @@ 'properties': dict({ }), 'timestamp': '2021-08-25T03:29:46.978Z', - 'uri': 'https://api.viessmann-platform.io/iot/v1/equipment/installations/######/gateways/################/devices/0/features/heating.circuits.2.operating.modes.heating', + 'uri': 'https://api.viessmann-platform.io/iot/v1/equipment/installations/######/gateways/################/devices/deviceId0/features/heating.circuits.2.operating.modes.heating', }), dict({ 'apiVersion': 1, @@ -4355,7 +4355,7 @@ 'properties': dict({ }), 'timestamp': '2021-08-25T03:29:46.401Z', - 'uri': 'https://api.viessmann-platform.io/iot/v1/equipment/installations/######/gateways/################/devices/0/features/heating.circuits.2.sensors.temperature', + 'uri': 'https://api.viessmann-platform.io/iot/v1/equipment/installations/######/gateways/################/devices/deviceId0/features/heating.circuits.2.sensors.temperature', }), dict({ 'apiVersion': 1, @@ -4371,7 +4371,7 @@ 'properties': dict({ }), 'timestamp': '2021-08-25T03:29:46.401Z', - 'uri': 'https://api.viessmann-platform.io/iot/v1/equipment/installations/######/gateways/################/devices/0/features/heating.dhw.sensors', + 'uri': 'https://api.viessmann-platform.io/iot/v1/equipment/installations/######/gateways/################/devices/deviceId0/features/heating.dhw.sensors', }), dict({ 'apiVersion': 1, @@ -4395,7 +4395,7 @@ }), }), 'timestamp': '2021-08-25T03:29:47.637Z', - 'uri': 'https://api.viessmann-platform.io/iot/v1/equipment/installations/######/gateways/################/devices/0/features/heating.dhw.sensors.temperature.outlet', + 'uri': 'https://api.viessmann-platform.io/iot/v1/equipment/installations/######/gateways/################/devices/deviceId0/features/heating.dhw.sensors.temperature.outlet', }), dict({ 'apiVersion': 1, @@ -4412,7 +4412,7 @@ 'properties': dict({ }), 'timestamp': '2021-08-25T03:29:46.401Z', - 'uri': 'https://api.viessmann-platform.io/iot/v1/equipment/installations/######/gateways/################/devices/0/features/heating.device', + 'uri': 'https://api.viessmann-platform.io/iot/v1/equipment/installations/######/gateways/################/devices/deviceId0/features/heating.device', }), dict({ 'apiVersion': 1, @@ -4429,7 +4429,7 @@ 'properties': dict({ }), 'timestamp': '2021-08-25T03:29:46.401Z', - 'uri': 'https://api.viessmann-platform.io/iot/v1/equipment/installations/######/gateways/################/devices/0/features/heating.sensors', + 'uri': 'https://api.viessmann-platform.io/iot/v1/equipment/installations/######/gateways/################/devices/deviceId0/features/heating.sensors', }), dict({ 'apiVersion': 1, @@ -4450,7 +4450,7 @@ }), }), 'timestamp': '2021-08-25T03:29:47.575Z', - 'uri': 'https://api.viessmann-platform.io/iot/v1/equipment/installations/######/gateways/################/devices/0/features/heating.device.time.offset', + 'uri': 'https://api.viessmann-platform.io/iot/v1/equipment/installations/######/gateways/################/devices/deviceId0/features/heating.device.time.offset', }), dict({ 'apiVersion': 1, @@ -4466,7 +4466,7 @@ 'properties': dict({ }), 'timestamp': '2021-08-25T03:29:47.562Z', - 'uri': 'https://api.viessmann-platform.io/iot/v1/equipment/installations/######/gateways/################/devices/0/features/heating.circuits.0.sensors.temperature.room', + 'uri': 'https://api.viessmann-platform.io/iot/v1/equipment/installations/######/gateways/################/devices/deviceId0/features/heating.circuits.0.sensors.temperature.room', }), dict({ 'apiVersion': 1, @@ -4483,7 +4483,7 @@ 'properties': dict({ }), 'timestamp': '2021-08-25T03:29:46.400Z', - 'uri': 'https://api.viessmann-platform.io/iot/v1/equipment/installations/######/gateways/################/devices/0/features/heating.circuits.0.dhw.pumps', + 'uri': 'https://api.viessmann-platform.io/iot/v1/equipment/installations/######/gateways/################/devices/deviceId0/features/heating.circuits.0.dhw.pumps', }), dict({ 'apiVersion': 1, @@ -4503,7 +4503,7 @@ }), }), 'timestamp': '2021-08-25T03:29:46.900Z', - 'uri': 'https://api.viessmann-platform.io/iot/v1/equipment/installations/######/gateways/################/devices/0/features/heating.circuits.1.frostprotection', + 'uri': 'https://api.viessmann-platform.io/iot/v1/equipment/installations/######/gateways/################/devices/deviceId0/features/heating.circuits.1.frostprotection', }), dict({ 'apiVersion': 1, @@ -4519,7 +4519,7 @@ 'properties': dict({ }), 'timestamp': '2021-08-25T03:29:47.633Z', - 'uri': 'https://api.viessmann-platform.io/iot/v1/equipment/installations/######/gateways/################/devices/0/features/heating.solar.sensors.temperature.dhw', + 'uri': 'https://api.viessmann-platform.io/iot/v1/equipment/installations/######/gateways/################/devices/deviceId0/features/heating.solar.sensors.temperature.dhw', }), dict({ 'apiVersion': 1, @@ -4537,7 +4537,7 @@ 'properties': dict({ }), 'timestamp': '2021-08-25T03:29:46.400Z', - 'uri': 'https://api.viessmann-platform.io/iot/v1/equipment/installations/######/gateways/################/devices/0/features/heating.circuits.0.dhw', + 'uri': 'https://api.viessmann-platform.io/iot/v1/equipment/installations/######/gateways/################/devices/deviceId0/features/heating.circuits.0.dhw', }), dict({ 'apiVersion': 1, @@ -4588,7 +4588,7 @@ }), }), 'timestamp': '2021-08-25T03:29:46.910Z', - 'uri': 'https://api.viessmann-platform.io/iot/v1/equipment/installations/######/gateways/################/devices/0/features/heating.circuits.2.heating.curve', + 'uri': 'https://api.viessmann-platform.io/iot/v1/equipment/installations/######/gateways/################/devices/deviceId0/features/heating.circuits.2.heating.curve', }), dict({ 'apiVersion': 1, @@ -4604,7 +4604,7 @@ 'properties': dict({ }), 'timestamp': '2021-08-25T03:29:46.975Z', - 'uri': 'https://api.viessmann-platform.io/iot/v1/equipment/installations/######/gateways/################/devices/0/features/heating.circuits.1.operating.modes.heating', + 'uri': 'https://api.viessmann-platform.io/iot/v1/equipment/installations/######/gateways/################/devices/deviceId0/features/heating.circuits.1.operating.modes.heating', }), dict({ 'apiVersion': 1, @@ -4629,7 +4629,7 @@ }), }), 'timestamp': '2021-08-25T03:29:47.538Z', - 'uri': 'https://api.viessmann-platform.io/iot/v1/equipment/installations/######/gateways/################/devices/0/features/heating.circuits.1.operating.programs.external', + 'uri': 'https://api.viessmann-platform.io/iot/v1/equipment/installations/######/gateways/################/devices/deviceId0/features/heating.circuits.1.operating.programs.external', }), dict({ 'apiVersion': 1, @@ -4658,7 +4658,7 @@ }), }), 'timestamp': '2021-08-25T15:02:49.557Z', - 'uri': 'https://api.viessmann-platform.io/iot/v1/equipment/installations/######/gateways/################/devices/0/features/heating.dhw.sensors.temperature.hotWaterStorage', + 'uri': 'https://api.viessmann-platform.io/iot/v1/equipment/installations/######/gateways/################/devices/deviceId0/features/heating.dhw.sensors.temperature.hotWaterStorage', }), dict({ 'apiVersion': 1, @@ -4687,7 +4687,7 @@ }), }), 'timestamp': '2021-08-25T11:03:00.515Z', - 'uri': 'https://api.viessmann-platform.io/iot/v1/equipment/installations/######/gateways/################/devices/0/features/heating.circuits.1.sensors.temperature.supply', + 'uri': 'https://api.viessmann-platform.io/iot/v1/equipment/installations/######/gateways/################/devices/deviceId0/features/heating.circuits.1.sensors.temperature.supply', }), dict({ 'apiVersion': 1, @@ -4708,7 +4708,7 @@ 'properties': dict({ }), 'timestamp': '2021-08-25T03:29:46.401Z', - 'uri': 'https://api.viessmann-platform.io/iot/v1/equipment/installations/######/gateways/################/devices/0/features/heating.circuits.1.operating.modes', + 'uri': 'https://api.viessmann-platform.io/iot/v1/equipment/installations/######/gateways/################/devices/deviceId0/features/heating.circuits.1.operating.modes', }), ]), 'device': dict({ diff --git a/tests/components/vicare/test_diagnostics.py b/tests/components/vicare/test_diagnostics.py index 7c25fa8a8bda..3dc22ac2ad9c 100644 --- a/tests/components/vicare/test_diagnostics.py +++ b/tests/components/vicare/test_diagnostics.py @@ -1,6 +1,6 @@ """Test ViCare diagnostics.""" -from unittest.mock import MagicMock +from unittest.mock import MagicMock, patch from PyViCare.PyViCareUtils import PyViCareDeviceCommunicationError from syrupy.assertion import SnapshotAssertion @@ -8,6 +8,10 @@ from syrupy.filters import props from homeassistant.core import HomeAssistant +from . import MODULE, setup_integration +from .conftest import Fixture, MockPyViCare + +from tests.common import MockConfigEntry from tests.components.diagnostics import get_diagnostics_for_config_entry from tests.typing import ClientSessionGenerator @@ -51,3 +55,36 @@ async def test_diagnostics_with_offline_device( assert "error" in error_entry assert "GATEWAY_OFFLINE" in error_entry["error"] assert error_entry["device"]["id"] == devices[0].device_id + + +async def test_diagnostics_scopes_features_to_their_device( + hass: HomeAssistant, + hass_client: ClientSessionGenerator, + mock_config_entry: MockConfigEntry, +) -> None: + """Devices sharing a gateway must not each dump the whole gateway payload.""" + fixtures: list[Fixture] = [ + Fixture({"type:boiler"}, "vicare/Vitodens300W.json", gateway_id="gateway0"), + Fixture({"type:heatpump"}, "vicare/Vitocal250A.json", gateway_id="gateway0"), + ] + with ( + patch( + "homeassistant.helpers.config_entry_oauth2_flow.OAuth2Session.async_ensure_token_valid", + ), + patch( + f"{MODULE}._setup_vicare_api", + return_value=MockPyViCare(fixtures).as_vicare_data(), + ), + ): + await setup_integration(hass, mock_config_entry) + + diag = await get_diagnostics_for_config_entry(hass, hass_client, mock_config_entry) + + dumps = {entry["device"]["id"]: entry["data"] for entry in diag["data"]} + # 167 and 325 features; without scoping both would dump all 492. + assert [len(dumps["deviceId0"]), len(dumps["deviceId1"])] == [167, 325] + for device_id in ("deviceId0", "deviceId1"): + assert { + feature["uri"].split("/devices/")[1].split("/")[0] + for feature in dumps[device_id] + } == {device_id} diff --git a/tests/components/vicare/test_init.py b/tests/components/vicare/test_init.py index 10f370691e50..7a1f4a7d9d15 100644 --- a/tests/components/vicare/test_init.py +++ b/tests/components/vicare/test_init.py @@ -1,7 +1,7 @@ """Test ViCare initialization and migration.""" from datetime import timedelta -from unittest.mock import Mock, patch +from unittest.mock import Mock, call, patch from aiohttp import ClientError from freezegun.api import FrozenDateTimeFactory @@ -11,9 +11,10 @@ from PyViCare.PyViCareUtils import ( PyViCareInvalidConfigurationError, PyViCareInvalidCredentialsError, PyViCareInvalidDataError, + PyViCareNotSupportedFeatureError, ) -from homeassistant.components.vicare.const import DOMAIN +from homeassistant.components.vicare.const import DEFAULT_CACHE_DURATION, DOMAIN from homeassistant.config_entries import SOURCE_REAUTH, ConfigEntryState from homeassistant.const import ( CONF_CLIENT_ID, @@ -542,12 +543,12 @@ async def test_coordinator_handles_invalid_data( assert "Unexpected error fetching" not in caplog.text -async def test_per_device_failure_isolation( +async def test_per_gateway_failure_isolation( hass: HomeAssistant, freezer: FrozenDateTimeFactory, mock_config_entry: MockConfigEntry, ) -> None: - """A transient failure on one device must not affect the other device's sensors.""" + """A transient failure on one gateway must not affect another gateway's sensors.""" fixtures: list[Fixture] = [ Fixture({"type:climateSensor"}, "vicare/RoomSensor1.json"), Fixture({"type:climateSensor"}, "vicare/RoomSensor2.json"), @@ -586,7 +587,7 @@ async def test_per_device_failure_isolation( } ) - # Coordinator interval scales by device count (60 * 2 = 120s); tick past it. + # Coordinator interval scales by gateway count (60 * 2 = 120s); tick past it. freezer.tick(timedelta(seconds=300)) async_fire_time_changed(hass, fire_all=True) await hass.async_block_till_done(wait_background_tasks=True) @@ -595,6 +596,56 @@ async def test_per_device_failure_isolation( assert hass.states.get(sensor_device1).state != STATE_UNAVAILABLE +async def test_devices_on_same_gateway_share_coordinator( + hass: HomeAssistant, + freezer: FrozenDateTimeFactory, + mock_config_entry: MockConfigEntry, +) -> None: + """Two devices behind one gateway share one coordinator and one fetch.""" + fixtures: list[Fixture] = [ + Fixture({"type:climateSensor"}, "vicare/RoomSensor1.json", gateway_id="gwA"), + Fixture({"type:climateSensor"}, "vicare/RoomSensor2.json", gateway_id="gwA"), + ] + mock_vicare = MockPyViCare(fixtures) + service0 = mock_vicare.devices[0].service + + with ( + patch( + "homeassistant.helpers.config_entry_oauth2_flow.OAuth2Session.async_ensure_token_valid", + ), + patch( + f"{MODULE}._setup_vicare_api", + return_value=mock_vicare.as_vicare_data(), + ), + patch(f"{MODULE}.PLATFORMS", [Platform.SENSOR]), + ): + mock_config_entry.add_to_hass(hass) + await hass.config_entries.async_setup(mock_config_entry.entry_id) + await hass.async_block_till_done() + + sensor_device0 = "sensor.model0_temperature" + sensor_device1 = "sensor.model1_temperature" + assert hass.states.get(sensor_device0).state != STATE_UNAVAILABLE + assert hass.states.get(sensor_device1).state != STATE_UNAVAILABLE + + # The gateway's single fetch failing takes every device on it offline. + service0.fetch_all_features.side_effect = PyViCareInternalServerError( + { + "statusCode": 500, + "errorType": "INTERNAL_SERVER_ERROR", + "message": "Internal Server Error", + "viErrorId": "0", + } + ) + # One gateway -> interval 60s; tick past it. + freezer.tick(timedelta(seconds=120)) + async_fire_time_changed(hass, fire_all=True) + await hass.async_block_till_done(wait_background_tasks=True) + + assert hass.states.get(sensor_device0).state == STATE_UNAVAILABLE + assert hass.states.get(sensor_device1).state == STATE_UNAVAILABLE + + async def test_coordinator_auth_failure_triggers_reauth( hass: HomeAssistant, freezer: FrozenDateTimeFactory, @@ -698,3 +749,127 @@ async def test_device_via_device_missing_gateway( ) assert channel_device is not None assert channel_device.via_device_id is None + + +async def test_setup_runs_pyvicare_init_and_fetches_once_per_gateway( + hass: HomeAssistant, + mock_config_entry: MockConfigEntry, +) -> None: + """Set up through _setup_vicare_api instead of a prebuilt ViCareData. + + Covers the via-gateway init, the gateway-based cache duration, and one + fetch per gateway. + """ + # Two devices behind gwA, one behind gwB: two gateways, three devices. + fixtures: list[Fixture] = [ + Fixture({"type:climateSensor"}, "vicare/RoomSensor1.json", gateway_id="gwA"), + Fixture({"type:climateSensor"}, "vicare/RoomSensor2.json", gateway_id="gwA"), + Fixture({"type:climateSensor"}, "vicare/RoomSensor1.json", gateway_id="gwB"), + ] + client = MockPyViCare(fixtures) + # viaGateway has to be set before init, the services are wired during init. + setup_calls: list[str] = [] + client.loadViaGateway = Mock(side_effect=lambda _: setup_calls.append("gateway")) + client.setCacheDuration = Mock() + client.initWithExternalOAuth = Mock( + side_effect=lambda _: setup_calls.append("init") + ) + + with ( + patch( + "homeassistant.helpers.config_entry_oauth2_flow.OAuth2Session.async_ensure_token_valid", + ), + patch(f"{MODULE}.PyViCare", return_value=client), + patch(f"{MODULE}.PLATFORMS", [Platform.SENSOR]), + ): + mock_config_entry.add_to_hass(hass) + await hass.config_entries.async_setup(mock_config_entry.entry_id) + await hass.async_block_till_done() + + assert mock_config_entry.state is ConfigEntryState.LOADED + + # viaGateway mode enabled, cache duration scaled to the gateway count. + client.loadViaGateway.assert_called_with(True) + # Setup re-inits once to apply the gateway-based cache duration. + assert setup_calls == ["gateway", "init", "gateway", "init"] + assert call(DEFAULT_CACHE_DURATION * 2) in client.setCacheDuration.call_args_list + + # One refresh per gateway, and the two devices behind gwA share that one + # service, so the second device is served without a fetch of its own. + assert client.services["gwA"].fetch_all_features.call_count == 1 + assert client.services["gwB"].fetch_all_features.call_count == 1 + assert hass.states.get("sensor.model0_temperature").state == "17.5" + assert hass.states.get("sensor.model1_temperature").state == "16.9" + + +async def test_offline_gateway_does_not_stretch_the_cache( + hass: HomeAssistant, + mock_config_entry: MockConfigEntry, +) -> None: + """An offline gateway is never fetched, so it must not size the cache.""" + fixtures: list[Fixture] = [ + Fixture({"type:climateSensor"}, "vicare/RoomSensor1.json", gateway_id="gwA"), + Fixture({"type:climateSensor"}, "vicare/RoomSensor2.json", gateway_id="gwB"), + Fixture( + {"type:climateSensor"}, + "vicare/RoomSensor1.json", + gateway_id="gwC", + online=False, + ), + ] + client = MockPyViCare(fixtures) + client.loadViaGateway = Mock() + client.setCacheDuration = Mock() + client.initWithExternalOAuth = Mock() + + with ( + patch( + "homeassistant.helpers.config_entry_oauth2_flow.OAuth2Session.async_ensure_token_valid", + ), + patch(f"{MODULE}.PyViCare", return_value=client), + patch(f"{MODULE}.PLATFORMS", [Platform.SENSOR]), + ): + mock_config_entry.add_to_hass(hass) + await hass.config_entries.async_setup(mock_config_entry.entry_id) + await hass.async_block_till_done() + + assert mock_config_entry.state is ConfigEntryState.LOADED + # Two online gateways out of three, so the cache matches the coordinator + # interval instead of being stretched to 3 x 60s. + assert call(DEFAULT_CACHE_DURATION * 2) in client.setCacheDuration.call_args_list + assert ( + call(DEFAULT_CACHE_DURATION * 3) not in client.setCacheDuration.call_args_list + ) + assert client.services["gwC"].fetch_all_features.call_count == 0 + + +async def test_setup_loads_with_unpaid_package_gateway( + hass: HomeAssistant, + mock_config_entry: MockConfigEntry, +) -> None: + """A gateway whose bulk fetch raises PACKAGE_NOT_PAID_FOR still loads.""" + fixtures: list[Fixture] = [ + Fixture({"type:climateSensor"}, "vicare/RoomSensor1.json") + ] + mock_vicare = MockPyViCare(fixtures) + mock_vicare.devices[ + 0 + ].service.fetch_all_features.side_effect = PyViCareNotSupportedFeatureError( + "PACKAGE_NOT_PAID_FOR" + ) + + with ( + patch( + "homeassistant.helpers.config_entry_oauth2_flow.OAuth2Session.async_ensure_token_valid", + ), + patch( + f"{MODULE}._setup_vicare_api", + return_value=mock_vicare.as_vicare_data(), + ), + patch(f"{MODULE}.PLATFORMS", [Platform.SENSOR]), + ): + mock_config_entry.add_to_hass(hass) + await hass.config_entries.async_setup(mock_config_entry.entry_id) + await hass.async_block_till_done() + + assert mock_config_entry.state is ConfigEntryState.LOADED diff --git a/tests/components/vicare/test_switch.py b/tests/components/vicare/test_switch.py index efdda6901e05..24e3b6c80478 100644 --- a/tests/components/vicare/test_switch.py +++ b/tests/components/vicare/test_switch.py @@ -201,7 +201,8 @@ async def test_turn_on_refused_while_another_quickmode_runs( def activate_quickmode(mock_vicare: MockPyViCare, device: int, quickmode: str) -> None: """Mark a quickmode as active in the fixture data of a mocked device.""" - for feature in mock_vicare.devices[device].service._test_data["data"]: + config = mock_vicare.devices[device] + for feature in config.service._features[config.device_id]: if feature["feature"] == f"ventilation.quickmodes.{quickmode}": feature["properties"]["active"]["value"] = True return