From 9c689d757c5a9ea2d45cb0ed54e2ff8db93ea19c Mon Sep 17 00:00:00 2001 From: Robert Svensson Date: Thu, 5 Jan 2023 21:38:24 +0100 Subject: [PATCH 01/16] Limit calls in UniFi to write state (#85248) Limit calls to write state to when relevant --- homeassistant/components/unifi/entity.py | 10 ++++++++-- homeassistant/components/unifi/sensor.py | 12 ++++++++++-- homeassistant/components/unifi/switch.py | 15 ++++++++++++--- homeassistant/components/unifi/update.py | 8 +++++++- 4 files changed, 37 insertions(+), 8 deletions(-) diff --git a/homeassistant/components/unifi/entity.py b/homeassistant/components/unifi/entity.py index 79a80fad73c..0809e81fe54 100644 --- a/homeassistant/components/unifi/entity.py +++ b/homeassistant/components/unifi/entity.py @@ -65,6 +65,7 @@ class UnifiEntity(Entity, Generic[HandlerT, DataT]): self.entity_description = description self._removed = False + self._write_state = False self._attr_available = description.available_fn(controller, obj_id) self._attr_device_info = description.device_info_fn(controller.api, obj_id) @@ -117,9 +118,14 @@ class UnifiEntity(Entity, Generic[HandlerT, DataT]): self.hass.async_create_task(self.remove_item({self._obj_id})) return - self._attr_available = description.available_fn(self.controller, self._obj_id) + if ( + available := description.available_fn(self.controller, self._obj_id) + ) != self.available: + self._attr_available = available + self._write_state = True self.async_update_state(event, obj_id) - self.async_write_ha_state() + if self._write_state: + self.async_write_ha_state() @callback def async_signal_reachable_callback(self) -> None: diff --git a/homeassistant/components/unifi/sensor.py b/homeassistant/components/unifi/sensor.py index e320d1a0d4e..585221d05bb 100644 --- a/homeassistant/components/unifi/sensor.py +++ b/homeassistant/components/unifi/sensor.py @@ -253,11 +253,19 @@ class UnifiSensorEntity(SensorEntity, Generic[_HandlerT, _DataT]): self.hass.async_create_task(self.remove_item({self._obj_id})) return + update_state = False + obj = description.object_fn(self.controller.api, self._obj_id) if (value := description.value_fn(self.controller, obj)) != self.native_value: self._attr_native_value = value - self._attr_available = description.available_fn(self.controller, self._obj_id) - self.async_write_ha_state() + update_state = True + if ( + available := description.available_fn(self.controller, self._obj_id) + ) != self.available: + self._attr_available = available + update_state = True + if update_state: + self.async_write_ha_state() @callback def async_signal_reachable_callback(self) -> None: diff --git a/homeassistant/components/unifi/switch.py b/homeassistant/components/unifi/switch.py index 44007e4c1a8..1d83effba36 100644 --- a/homeassistant/components/unifi/switch.py +++ b/homeassistant/components/unifi/switch.py @@ -410,11 +410,20 @@ class UnifiSwitchEntity(SwitchEntity, Generic[_HandlerT, _DataT]): self.hass.async_create_task(self.remove_item({self._obj_id})) return + update_state = False + if not description.only_event_for_state_change: obj = description.object_fn(self.controller.api, self._obj_id) - self._attr_is_on = description.is_on_fn(self.controller.api, obj) - self._attr_available = description.available_fn(self.controller, self._obj_id) - self.async_write_ha_state() + if (is_on := description.is_on_fn(self.controller.api, obj)) != self.is_on: + self._attr_is_on = is_on + update_state = True + if ( + available := description.available_fn(self.controller, self._obj_id) + ) != self.available: + self._attr_available = available + update_state = True + if update_state: + self.async_write_ha_state() @callback def async_signal_reachable_callback(self) -> None: diff --git a/homeassistant/components/unifi/update.py b/homeassistant/components/unifi/update.py index 6cff6b7932d..0810cbb780c 100644 --- a/homeassistant/components/unifi/update.py +++ b/homeassistant/components/unifi/update.py @@ -163,6 +163,12 @@ class UnifiDeviceUpdateEntity(UnifiEntity[HandlerT, DataT], UpdateEntity): description = self.entity_description obj = description.object_fn(self.controller.api, self._obj_id) - self._attr_in_progress = description.state_fn(self.controller.api, obj) + if ( + in_progress := description.state_fn(self.controller.api, obj) + ) != self.in_progress: + self._attr_in_progress = in_progress + self._write_state = True self._attr_installed_version = obj.version self._attr_latest_version = obj.upgrade_to_firmware or obj.version + if self.installed_version != self.latest_version: + self._write_state = True From 8bf2299407a948c7e3becf4650a0ce28920ec221 Mon Sep 17 00:00:00 2001 From: Robert Svensson Date: Fri, 6 Jan 2023 03:49:22 +0100 Subject: [PATCH 02/16] Only subscribe to relevant IDs for state updates (#85252) Make sure to only subscribe to the relevant ID --- homeassistant/components/unifi/sensor.py | 1 + homeassistant/components/unifi/switch.py | 1 + 2 files changed, 2 insertions(+) diff --git a/homeassistant/components/unifi/sensor.py b/homeassistant/components/unifi/sensor.py index 585221d05bb..e017ff4ed0e 100644 --- a/homeassistant/components/unifi/sensor.py +++ b/homeassistant/components/unifi/sensor.py @@ -217,6 +217,7 @@ class UnifiSensorEntity(SensorEntity, Generic[_HandlerT, _DataT]): self.async_on_remove( handler.subscribe( self.async_signalling_callback, + id_filter=self._obj_id, ) ) self.async_on_remove( diff --git a/homeassistant/components/unifi/switch.py b/homeassistant/components/unifi/switch.py index 1d83effba36..b0f042c78cd 100644 --- a/homeassistant/components/unifi/switch.py +++ b/homeassistant/components/unifi/switch.py @@ -361,6 +361,7 @@ class UnifiSwitchEntity(SwitchEntity, Generic[_HandlerT, _DataT]): self.async_on_remove( handler.subscribe( self.async_signalling_callback, + id_filter=self._obj_id, ) ) self.async_on_remove( From dcd07d313598b1c781da381372e6f1551a87c22b Mon Sep 17 00:00:00 2001 From: William Scanlon <6432770+w1ll1am23@users.noreply.github.com> Date: Wed, 4 Jan 2023 21:09:54 -0500 Subject: [PATCH 03/16] Bump pyeconet to 0.1.18 to fix energy usage (#85094) --- homeassistant/components/econet/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/econet/manifest.json b/homeassistant/components/econet/manifest.json index 19455d8dffb..8aed197cf4c 100644 --- a/homeassistant/components/econet/manifest.json +++ b/homeassistant/components/econet/manifest.json @@ -3,7 +3,7 @@ "name": "Rheem EcoNet Products", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/econet", - "requirements": ["pyeconet==0.1.17"], + "requirements": ["pyeconet==0.1.18"], "codeowners": ["@vangorra", "@w1ll1am23"], "iot_class": "cloud_push", "loggers": ["paho_mqtt", "pyeconet"] diff --git a/requirements_all.txt b/requirements_all.txt index 2777f4c5226..cf138171729 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1560,7 +1560,7 @@ pydroid-ipcam==2.0.0 pyebox==1.1.4 # homeassistant.components.econet -pyeconet==0.1.17 +pyeconet==0.1.18 # homeassistant.components.edimax pyedimax==0.2.1 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 75dd5bbf45d..941538838b0 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1106,7 +1106,7 @@ pydexcom==0.2.3 pydroid-ipcam==2.0.0 # homeassistant.components.econet -pyeconet==0.1.17 +pyeconet==0.1.18 # homeassistant.components.efergy pyefergy==22.1.1 From fe89b663e77f7a46fd2921f3e9c3d41ddda80487 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michal=20=C4=8Ciha=C5=99?= Date: Thu, 5 Jan 2023 23:45:29 +0100 Subject: [PATCH 04/16] Fix lacrosse_view fetching of latest data (#85117) lacrosse_view: fixed fetching of latest data When using datetime.utcnow(), it only replaces timezone information with UTC making the actual time offset by the timezone. When you are in UTC- timezones, it makes no issue as the offset is in the future, but when in UTC+, the last hour(s) of data are missing. This commits swtiches to time.time() as UTC timestamp is actually what the API expects. It also reduces the window to one hour what noticeably improves the API performance. --- .../components/lacrosse_view/coordinator.py | 19 ++++++++----------- 1 file changed, 8 insertions(+), 11 deletions(-) diff --git a/homeassistant/components/lacrosse_view/coordinator.py b/homeassistant/components/lacrosse_view/coordinator.py index 5361f94d04f..8dcbd8a2e5e 100644 --- a/homeassistant/components/lacrosse_view/coordinator.py +++ b/homeassistant/components/lacrosse_view/coordinator.py @@ -1,7 +1,8 @@ """DataUpdateCoordinator for LaCrosse View.""" from __future__ import annotations -from datetime import datetime, timedelta +from datetime import timedelta +from time import time from lacrosse_view import HTTPError, LaCrosse, Location, LoginError, Sensor @@ -30,7 +31,7 @@ class LaCrosseUpdateCoordinator(DataUpdateCoordinator[list[Sensor]]): ) -> None: """Initialize DataUpdateCoordinator for LaCrosse View.""" self.api = api - self.last_update = datetime.utcnow() + self.last_update = time() self.username = entry.data["username"] self.password = entry.data["password"] self.hass = hass @@ -45,26 +46,22 @@ class LaCrosseUpdateCoordinator(DataUpdateCoordinator[list[Sensor]]): async def _async_update_data(self) -> list[Sensor]: """Get the data for LaCrosse View.""" - now = datetime.utcnow() + now = int(time()) - if self.last_update < now - timedelta(minutes=59): # Get new token + if self.last_update < now - 59 * 60: # Get new token once in a hour self.last_update = now try: await self.api.login(self.username, self.password) except LoginError as error: raise ConfigEntryAuthFailed from error - # Get the timestamp for yesterday at 6 PM (this is what is used in the app, i noticed it when proxying the request) - yesterday = now - timedelta(days=1) - yesterday = yesterday.replace(hour=18, minute=0, second=0, microsecond=0) - yesterday_timestamp = datetime.timestamp(yesterday) - try: + # Fetch last hour of data sensors = await self.api.get_sensors( location=Location(id=self.id, name=self.name), tz=self.hass.config.time_zone, - start=str(int(yesterday_timestamp)), - end=str(int(datetime.timestamp(now))), + start=str(now - 3600), + end=str(now), ) except HTTPError as error: raise ConfigEntryNotReady from error From 563ad02c65264edeefddf941d4934bb28e828cac Mon Sep 17 00:00:00 2001 From: Ernst Klamer Date: Thu, 5 Jan 2023 03:05:46 +0100 Subject: [PATCH 05/16] Bump bthome-ble to 2.4.1 (#85153) fix https://github.com/home-assistant/core/issues/85142 fixes undefined --- homeassistant/components/bthome/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/bthome/manifest.json b/homeassistant/components/bthome/manifest.json index 7a879608fc4..1be63f5f486 100644 --- a/homeassistant/components/bthome/manifest.json +++ b/homeassistant/components/bthome/manifest.json @@ -17,7 +17,7 @@ "service_data_uuid": "0000fcd2-0000-1000-8000-00805f9b34fb" } ], - "requirements": ["bthome-ble==2.4.0"], + "requirements": ["bthome-ble==2.4.1"], "dependencies": ["bluetooth"], "codeowners": ["@Ernst79"], "iot_class": "local_push" diff --git a/requirements_all.txt b/requirements_all.txt index cf138171729..7792d48739d 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -488,7 +488,7 @@ brunt==1.2.0 bt_proximity==0.2.1 # homeassistant.components.bthome -bthome-ble==2.4.0 +bthome-ble==2.4.1 # homeassistant.components.bt_home_hub_5 bthomehub5-devicelist==0.1.1 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 941538838b0..8b9827a2cd3 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -392,7 +392,7 @@ brother==2.1.1 brunt==1.2.0 # homeassistant.components.bthome -bthome-ble==2.4.0 +bthome-ble==2.4.1 # homeassistant.components.buienradar buienradar==1.0.5 From 3c2b7c0d6951908f35fa7f33ff76a2f38a39b12e Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Thu, 5 Jan 2023 11:24:38 +0100 Subject: [PATCH 06/16] Bump hatasmota to 0.6.2 (#85182) --- homeassistant/components/tasmota/manifest.json | 2 +- homeassistant/components/tasmota/sensor.py | 3 +++ requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 4 files changed, 6 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/tasmota/manifest.json b/homeassistant/components/tasmota/manifest.json index 6e3e69f59fe..df01f719cec 100644 --- a/homeassistant/components/tasmota/manifest.json +++ b/homeassistant/components/tasmota/manifest.json @@ -3,7 +3,7 @@ "name": "Tasmota", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/tasmota", - "requirements": ["hatasmota==0.6.1"], + "requirements": ["hatasmota==0.6.2"], "dependencies": ["mqtt"], "mqtt": ["tasmota/discovery/#"], "codeowners": ["@emontnemery"], diff --git a/homeassistant/components/tasmota/sensor.py b/homeassistant/components/tasmota/sensor.py index 4b84bb8f86a..74402a51586 100644 --- a/homeassistant/components/tasmota/sensor.py +++ b/homeassistant/components/tasmota/sensor.py @@ -21,6 +21,7 @@ from homeassistant.const import ( CONCENTRATION_PARTS_PER_MILLION, LIGHT_LUX, PERCENTAGE, + POWER_VOLT_AMPERE_REACTIVE, SIGNAL_STRENGTH_DECIBELS, SIGNAL_STRENGTH_DECIBELS_MILLIWATT, UnitOfApparentPower, @@ -217,8 +218,10 @@ SENSOR_UNIT_MAP = { hc.LIGHT_LUX: LIGHT_LUX, hc.MASS_KILOGRAMS: UnitOfMass.KILOGRAMS, hc.PERCENTAGE: PERCENTAGE, + hc.POWER_FACTOR: None, hc.POWER_WATT: UnitOfPower.WATT, hc.PRESSURE_HPA: UnitOfPressure.HPA, + hc.REACTIVE_POWER: POWER_VOLT_AMPERE_REACTIVE, hc.SIGNAL_STRENGTH_DECIBELS: SIGNAL_STRENGTH_DECIBELS, hc.SIGNAL_STRENGTH_DECIBELS_MILLIWATT: SIGNAL_STRENGTH_DECIBELS_MILLIWATT, hc.SPEED_KILOMETERS_PER_HOUR: UnitOfSpeed.KILOMETERS_PER_HOUR, diff --git a/requirements_all.txt b/requirements_all.txt index 7792d48739d..682b8781edf 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -858,7 +858,7 @@ hass-nabucasa==0.61.0 hass_splunk==0.1.1 # homeassistant.components.tasmota -hatasmota==0.6.1 +hatasmota==0.6.2 # homeassistant.components.jewish_calendar hdate==0.10.4 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 8b9827a2cd3..c5be8d9c6d9 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -647,7 +647,7 @@ habitipy==0.2.0 hass-nabucasa==0.61.0 # homeassistant.components.tasmota -hatasmota==0.6.1 +hatasmota==0.6.2 # homeassistant.components.jewish_calendar hdate==0.10.4 From 8034faadcad20b570f7b167696368e5fd29941ba Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Thu, 5 Jan 2023 17:02:17 +0100 Subject: [PATCH 07/16] Remove invalid AQI unit from Environment Canada (#85183) --- homeassistant/components/environment_canada/sensor.py | 1 - 1 file changed, 1 deletion(-) diff --git a/homeassistant/components/environment_canada/sensor.py b/homeassistant/components/environment_canada/sensor.py index d3848086bf5..e7eceb8dadc 100644 --- a/homeassistant/components/environment_canada/sensor.py +++ b/homeassistant/components/environment_canada/sensor.py @@ -228,7 +228,6 @@ AQHI_SENSOR = ECSensorEntityDescription( key="aqhi", name="AQHI", device_class=SensorDeviceClass.AQI, - native_unit_of_measurement="AQI", state_class=SensorStateClass.MEASUREMENT, value_fn=_get_aqhi_value, ) From d73b86132b61e686944e12f36537b661ea71356f Mon Sep 17 00:00:00 2001 From: epenet <6771947+epenet@users.noreply.github.com> Date: Thu, 5 Jan 2023 13:00:46 +0100 Subject: [PATCH 08/16] Adjust valid energy units (#85190) --- homeassistant/components/energy/sensor.py | 8 ++++---- homeassistant/components/energy/validate.py | 15 ++++++++++----- 2 files changed, 14 insertions(+), 9 deletions(-) diff --git a/homeassistant/components/energy/sensor.py b/homeassistant/components/energy/sensor.py index 1509eb10afe..5ad4c74a6cf 100644 --- a/homeassistant/components/energy/sensor.py +++ b/homeassistant/components/energy/sensor.py @@ -41,20 +41,20 @@ SUPPORTED_STATE_CLASSES = { SensorStateClass.TOTAL_INCREASING, } VALID_ENERGY_UNITS: set[str] = { - UnitOfEnergy.WATT_HOUR, + UnitOfEnergy.GIGA_JOULE, UnitOfEnergy.KILO_WATT_HOUR, UnitOfEnergy.MEGA_WATT_HOUR, - UnitOfEnergy.GIGA_JOULE, + UnitOfEnergy.WATT_HOUR, } VALID_ENERGY_UNITS_GAS = { - UnitOfVolume.CUBIC_FEET, UnitOfVolume.CENTUM_CUBIC_FEET, + UnitOfVolume.CUBIC_FEET, UnitOfVolume.CUBIC_METERS, *VALID_ENERGY_UNITS, } VALID_VOLUME_UNITS_WATER: set[str] = { - UnitOfVolume.CUBIC_FEET, UnitOfVolume.CENTUM_CUBIC_FEET, + UnitOfVolume.CUBIC_FEET, UnitOfVolume.CUBIC_METERS, UnitOfVolume.GALLONS, UnitOfVolume.LITERS, diff --git a/homeassistant/components/energy/validate.py b/homeassistant/components/energy/validate.py index ea799fcdf06..55d11f5f04d 100644 --- a/homeassistant/components/energy/validate.py +++ b/homeassistant/components/energy/validate.py @@ -22,10 +22,10 @@ from .const import DOMAIN ENERGY_USAGE_DEVICE_CLASSES = (sensor.SensorDeviceClass.ENERGY,) ENERGY_USAGE_UNITS = { sensor.SensorDeviceClass.ENERGY: ( + UnitOfEnergy.GIGA_JOULE, UnitOfEnergy.KILO_WATT_HOUR, UnitOfEnergy.MEGA_WATT_HOUR, UnitOfEnergy.WATT_HOUR, - UnitOfEnergy.GIGA_JOULE, ) } ENERGY_PRICE_UNITS = tuple( @@ -39,12 +39,16 @@ GAS_USAGE_DEVICE_CLASSES = ( ) GAS_USAGE_UNITS = { sensor.SensorDeviceClass.ENERGY: ( - UnitOfEnergy.WATT_HOUR, + UnitOfEnergy.GIGA_JOULE, UnitOfEnergy.KILO_WATT_HOUR, UnitOfEnergy.MEGA_WATT_HOUR, - UnitOfEnergy.GIGA_JOULE, + UnitOfEnergy.WATT_HOUR, + ), + sensor.SensorDeviceClass.GAS: ( + UnitOfVolume.CENTUM_CUBIC_FEET, + UnitOfVolume.CUBIC_FEET, + UnitOfVolume.CUBIC_METERS, ), - sensor.SensorDeviceClass.GAS: (UnitOfVolume.CUBIC_METERS, UnitOfVolume.CUBIC_FEET), } GAS_PRICE_UNITS = tuple( f"/{unit}" for units in GAS_USAGE_UNITS.values() for unit in units @@ -54,8 +58,9 @@ GAS_PRICE_UNIT_ERROR = "entity_unexpected_unit_gas_price" WATER_USAGE_DEVICE_CLASSES = (sensor.SensorDeviceClass.WATER,) WATER_USAGE_UNITS = { sensor.SensorDeviceClass.WATER: ( - UnitOfVolume.CUBIC_METERS, + UnitOfVolume.CENTUM_CUBIC_FEET, UnitOfVolume.CUBIC_FEET, + UnitOfVolume.CUBIC_METERS, UnitOfVolume.GALLONS, UnitOfVolume.LITERS, ), From 26ea02aa8ffe7f382bcd75c77a65a71eaf3439d1 Mon Sep 17 00:00:00 2001 From: epenet <6771947+epenet@users.noreply.github.com> Date: Thu, 5 Jan 2023 21:30:52 +0100 Subject: [PATCH 09/16] Remove invalid device class for RSSI sensors (#85191) * Remove invalid device class for RRSI sensors * Restore state class --- homeassistant/components/zha/sensor.py | 1 - 1 file changed, 1 deletion(-) diff --git a/homeassistant/components/zha/sensor.py b/homeassistant/components/zha/sensor.py index a66f3aa1fe8..ff95b66d101 100644 --- a/homeassistant/components/zha/sensor.py +++ b/homeassistant/components/zha/sensor.py @@ -755,7 +755,6 @@ class RSSISensor(Sensor, id_suffix="rssi"): """RSSI sensor for a device.""" _attr_state_class: SensorStateClass = SensorStateClass.MEASUREMENT - _attr_device_class: SensorDeviceClass = SensorDeviceClass.SIGNAL_STRENGTH _attr_entity_category = EntityCategory.DIAGNOSTIC _attr_entity_registry_enabled_default = False _attr_should_poll = True # BaseZhaEntity defaults to False From 59d6f827c368ffb4298c1b27d128f9b64d88dc10 Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Thu, 5 Jan 2023 20:28:13 +0100 Subject: [PATCH 10/16] Fix device class for DSMR gas sensors providing energy readings (#85202) --- homeassistant/components/dsmr/sensor.py | 16 +++++++ tests/components/dsmr/test_sensor.py | 55 +++++++++++++++++++++++++ 2 files changed, 71 insertions(+) diff --git a/homeassistant/components/dsmr/sensor.py b/homeassistant/components/dsmr/sensor.py index 36f734273f2..a77577302d5 100644 --- a/homeassistant/components/dsmr/sensor.py +++ b/homeassistant/components/dsmr/sensor.py @@ -28,6 +28,7 @@ from homeassistant.const import ( CONF_HOST, CONF_PORT, EVENT_HOMEASSISTANT_STOP, + UnitOfEnergy, UnitOfVolume, ) from homeassistant.core import CoreState, Event, HomeAssistant, callback @@ -591,6 +592,21 @@ class DSMREntity(SensorEntity): """Entity is only available if there is a telegram.""" return self.telegram is not None + @property + def device_class(self) -> SensorDeviceClass | None: + """Return the device class of this entity.""" + device_class = super().device_class + + # Override device class for gas sensors providing energy units, like + # kWh, MWh, GJ, etc. In those cases, the class should be energy, not gas + with suppress(ValueError): + if device_class == SensorDeviceClass.GAS and UnitOfEnergy( + str(self.native_unit_of_measurement) + ): + return SensorDeviceClass.ENERGY + + return device_class + @property def native_value(self) -> StateType: """Return the state of sensor, if available, translate if needed.""" diff --git a/tests/components/dsmr/test_sensor.py b/tests/components/dsmr/test_sensor.py index ee0ffa5db5f..ac4b9587ec7 100644 --- a/tests/components/dsmr/test_sensor.py +++ b/tests/components/dsmr/test_sensor.py @@ -26,6 +26,7 @@ from homeassistant.const import ( STATE_UNAVAILABLE, STATE_UNKNOWN, VOLUME_CUBIC_METERS, + UnitOfEnergy, UnitOfPower, ) from homeassistant.helpers import entity_registry as er @@ -804,3 +805,57 @@ async def test_reconnect(hass, dsmr_connection_fixture): await hass.config_entries.async_unload(mock_entry.entry_id) assert mock_entry.state == config_entries.ConfigEntryState.NOT_LOADED + + +async def test_gas_meter_providing_energy_reading(hass, dsmr_connection_fixture): + """Test that gas providing energy readings use the correct device class.""" + (connection_factory, transport, protocol) = dsmr_connection_fixture + + from dsmr_parser.obis_references import GAS_METER_READING + from dsmr_parser.objects import MBusObject + + entry_data = { + "port": "/dev/ttyUSB0", + "dsmr_version": "2.2", + "precision": 4, + "reconnect_interval": 30, + "serial_id": "1234", + "serial_id_gas": "5678", + } + entry_options = { + "time_between_update": 0, + } + + telegram = { + GAS_METER_READING: MBusObject( + [ + {"value": datetime.datetime.fromtimestamp(1551642213)}, + {"value": Decimal(123.456), "unit": UnitOfEnergy.GIGA_JOULE}, + ] + ), + } + + mock_entry = MockConfigEntry( + domain="dsmr", unique_id="/dev/ttyUSB0", data=entry_data, options=entry_options + ) + + mock_entry.add_to_hass(hass) + + await hass.config_entries.async_setup(mock_entry.entry_id) + await hass.async_block_till_done() + + telegram_callback = connection_factory.call_args_list[0][0][2] + telegram_callback(telegram) + await asyncio.sleep(0) + + gas_consumption = hass.states.get("sensor.gas_meter_gas_consumption") + assert gas_consumption.state == "123.456" + assert gas_consumption.attributes.get(ATTR_DEVICE_CLASS) == SensorDeviceClass.ENERGY + assert ( + gas_consumption.attributes.get(ATTR_STATE_CLASS) + == SensorStateClass.TOTAL_INCREASING + ) + assert ( + gas_consumption.attributes.get(ATTR_UNIT_OF_MEASUREMENT) + == UnitOfEnergy.GIGA_JOULE + ) From fa4c2500015cc1f78e589a15242ed298d03e3404 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Thu, 5 Jan 2023 10:29:13 -1000 Subject: [PATCH 11/16] Improve error reporting when switchbot auth fails (#85244) * Improve error reporting when switchbot auth fails related issue #85243 * bump * coverage --- homeassistant/components/switchbot/config_flow.py | 6 +++++- homeassistant/components/switchbot/manifest.json | 2 +- homeassistant/components/switchbot/strings.json | 2 +- .../components/switchbot/translations/en.json | 15 ++------------- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- tests/components/switchbot/test_config_flow.py | 3 ++- 7 files changed, 13 insertions(+), 19 deletions(-) diff --git a/homeassistant/components/switchbot/config_flow.py b/homeassistant/components/switchbot/config_flow.py index 6ba0e463718..933d8ac3c56 100644 --- a/homeassistant/components/switchbot/config_flow.py +++ b/homeassistant/components/switchbot/config_flow.py @@ -166,6 +166,7 @@ class SwitchbotConfigFlow(ConfigFlow, domain=DOMAIN): """Handle the SwitchBot API auth step.""" errors = {} assert self._discovered_adv is not None + description_placeholders = {} if user_input is not None: try: key_details = await self.hass.async_add_executor_job( @@ -176,8 +177,10 @@ class SwitchbotConfigFlow(ConfigFlow, domain=DOMAIN): ) except SwitchbotAccountConnectionError as ex: raise AbortFlow("cannot_connect") from ex - except SwitchbotAuthenticationError: + except SwitchbotAuthenticationError as ex: + _LOGGER.debug("Authentication failed: %s", ex, exc_info=True) errors = {"base": "auth_failed"} + description_placeholders = {"error_detail": str(ex)} else: return await self.async_step_lock_key(key_details) @@ -195,6 +198,7 @@ class SwitchbotConfigFlow(ConfigFlow, domain=DOMAIN): ), description_placeholders={ "name": name_from_discovery(self._discovered_adv), + **description_placeholders, }, ) diff --git a/homeassistant/components/switchbot/manifest.json b/homeassistant/components/switchbot/manifest.json index c7c50e5cf6e..c38573f82ca 100644 --- a/homeassistant/components/switchbot/manifest.json +++ b/homeassistant/components/switchbot/manifest.json @@ -2,7 +2,7 @@ "domain": "switchbot", "name": "SwitchBot", "documentation": "https://www.home-assistant.io/integrations/switchbot", - "requirements": ["PySwitchbot==0.36.1"], + "requirements": ["PySwitchbot==0.36.2"], "config_flow": true, "dependencies": ["bluetooth"], "codeowners": [ diff --git a/homeassistant/components/switchbot/strings.json b/homeassistant/components/switchbot/strings.json index 08fd960334a..c25769bee41 100644 --- a/homeassistant/components/switchbot/strings.json +++ b/homeassistant/components/switchbot/strings.json @@ -40,7 +40,7 @@ }, "error": { "encryption_key_invalid": "Key ID or Encryption key is invalid", - "auth_failed": "Authentication failed" + "auth_failed": "Authentication failed: {error_detail}" }, "abort": { "already_configured_device": "[%key:common::config_flow::abort::already_configured_device%]", diff --git a/homeassistant/components/switchbot/translations/en.json b/homeassistant/components/switchbot/translations/en.json index b5658e58d6b..d7065138051 100644 --- a/homeassistant/components/switchbot/translations/en.json +++ b/homeassistant/components/switchbot/translations/en.json @@ -8,7 +8,7 @@ "unknown": "Unexpected error" }, "error": { - "auth_failed": "Authentication failed", + "auth_failed": "Authentication failed: {error_detail}", "encryption_key_invalid": "Key ID or Encryption key is invalid" }, "flow_title": "{name} ({address})", @@ -47,18 +47,7 @@ "data": { "address": "Device address" } - }, - "lock_key": { - "description": "The {name} device requires encryption key, details on how to obtain it can be found in the documentation.", - "data": { - "key_id": "Key ID", - "encryption_key": "Encryption key" } - } - }, - "error": { - "key_id_invalid": "Key ID or Encryption key is invalid", - "encryption_key_invalid": "Key ID or Encryption key is invalid" } }, "options": { @@ -70,4 +59,4 @@ } } } -} \ No newline at end of file +} diff --git a/requirements_all.txt b/requirements_all.txt index 682b8781edf..217d9dddc0b 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -40,7 +40,7 @@ PyRMVtransport==0.3.3 PySocks==1.7.1 # homeassistant.components.switchbot -PySwitchbot==0.36.1 +PySwitchbot==0.36.2 # homeassistant.components.transport_nsw PyTransportNSW==0.1.1 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index c5be8d9c6d9..7f856633ed7 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -36,7 +36,7 @@ PyRMVtransport==0.3.3 PySocks==1.7.1 # homeassistant.components.switchbot -PySwitchbot==0.36.1 +PySwitchbot==0.36.2 # homeassistant.components.transport_nsw PyTransportNSW==0.1.1 diff --git a/tests/components/switchbot/test_config_flow.py b/tests/components/switchbot/test_config_flow.py index 1a3db48f192..a8cccbeb31d 100644 --- a/tests/components/switchbot/test_config_flow.py +++ b/tests/components/switchbot/test_config_flow.py @@ -481,7 +481,7 @@ async def test_user_setup_wolock_auth(hass): with patch( "homeassistant.components.switchbot.config_flow.SwitchbotLock.retrieve_encryption_key", - side_effect=SwitchbotAuthenticationError, + side_effect=SwitchbotAuthenticationError("error from api"), ): result = await hass.config_entries.flow.async_configure( result["flow_id"], @@ -494,6 +494,7 @@ async def test_user_setup_wolock_auth(hass): assert result["type"] == FlowResultType.FORM assert result["step_id"] == "lock_auth" assert result["errors"] == {"base": "auth_failed"} + assert "error from api" in result["description_placeholders"]["error_detail"] with patch_async_setup_entry() as mock_setup_entry, patch( "homeassistant.components.switchbot.config_flow.SwitchbotLock.verify_encryption_key", From ee88f34a91b9caad6624a0c9b1ba963cb248eb29 Mon Sep 17 00:00:00 2001 From: starkillerOG Date: Thu, 5 Jan 2023 22:06:40 +0100 Subject: [PATCH 12/16] bump reolink-aio to 0.1.2 (#85247) --- homeassistant/components/reolink/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/reolink/manifest.json b/homeassistant/components/reolink/manifest.json index 2c0deafca45..9ea4422203b 100644 --- a/homeassistant/components/reolink/manifest.json +++ b/homeassistant/components/reolink/manifest.json @@ -3,7 +3,7 @@ "name": "Reolink IP NVR/camera", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/reolink", - "requirements": ["reolink-aio==0.1.1"], + "requirements": ["reolink-aio==0.1.2"], "codeowners": ["@starkillerOG"], "iot_class": "local_polling", "loggers": ["reolink-aio"] diff --git a/requirements_all.txt b/requirements_all.txt index 217d9dddc0b..477825e0822 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2190,7 +2190,7 @@ regenmaschine==2022.11.0 renault-api==0.1.11 # homeassistant.components.reolink -reolink-aio==0.1.1 +reolink-aio==0.1.2 # homeassistant.components.python_script restrictedpython==5.2 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 7f856633ed7..e7ef6e1483f 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1529,7 +1529,7 @@ regenmaschine==2022.11.0 renault-api==0.1.11 # homeassistant.components.reolink -reolink-aio==0.1.1 +reolink-aio==0.1.2 # homeassistant.components.python_script restrictedpython==5.2 From edfd83c3a70cc810e2c5da18d0cfd7b28ed27920 Mon Sep 17 00:00:00 2001 From: rikroe <42204099+rikroe@users.noreply.github.com> Date: Fri, 6 Jan 2023 03:05:37 +0100 Subject: [PATCH 13/16] Bump bimmer_connected to 0.12.0 (#85255) * Bump bimmer_connected to 0.12.0 * Fix mypy * Remove not needed code Co-authored-by: rikroe --- .../bmw_connected_drive/device_tracker.py | 2 ++ .../bmw_connected_drive/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- .../components/bmw_connected_drive/__init__.py | 18 ------------------ 5 files changed, 5 insertions(+), 21 deletions(-) diff --git a/homeassistant/components/bmw_connected_drive/device_tracker.py b/homeassistant/components/bmw_connected_drive/device_tracker.py index c94d9b5b678..12d29736183 100644 --- a/homeassistant/components/bmw_connected_drive/device_tracker.py +++ b/homeassistant/components/bmw_connected_drive/device_tracker.py @@ -69,6 +69,7 @@ class BMWDeviceTracker(BMWBaseEntity, TrackerEntity): return ( self.vehicle.vehicle_location.location[0] if self.vehicle.is_vehicle_tracking_enabled + and self.vehicle.vehicle_location.location else None ) @@ -78,6 +79,7 @@ class BMWDeviceTracker(BMWBaseEntity, TrackerEntity): return ( self.vehicle.vehicle_location.location[1] if self.vehicle.is_vehicle_tracking_enabled + and self.vehicle.vehicle_location.location else None ) diff --git a/homeassistant/components/bmw_connected_drive/manifest.json b/homeassistant/components/bmw_connected_drive/manifest.json index 98b6861fd49..c03bdf6a26f 100644 --- a/homeassistant/components/bmw_connected_drive/manifest.json +++ b/homeassistant/components/bmw_connected_drive/manifest.json @@ -2,7 +2,7 @@ "domain": "bmw_connected_drive", "name": "BMW Connected Drive", "documentation": "https://www.home-assistant.io/integrations/bmw_connected_drive", - "requirements": ["bimmer_connected==0.10.4"], + "requirements": ["bimmer_connected==0.12.0"], "codeowners": ["@gerard33", "@rikroe"], "config_flow": true, "iot_class": "cloud_polling", diff --git a/requirements_all.txt b/requirements_all.txt index 477825e0822..20c80512c2d 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -422,7 +422,7 @@ beautifulsoup4==4.11.1 bellows==0.34.5 # homeassistant.components.bmw_connected_drive -bimmer_connected==0.10.4 +bimmer_connected==0.12.0 # homeassistant.components.bizkaibus bizkaibus==0.1.1 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index e7ef6e1483f..f1bbc843baa 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -349,7 +349,7 @@ beautifulsoup4==4.11.1 bellows==0.34.5 # homeassistant.components.bmw_connected_drive -bimmer_connected==0.10.4 +bimmer_connected==0.12.0 # homeassistant.components.bluetooth bleak-retry-connector==2.13.0 diff --git a/tests/components/bmw_connected_drive/__init__.py b/tests/components/bmw_connected_drive/__init__.py index c2bb65b3fa7..81b3bb9fff3 100644 --- a/tests/components/bmw_connected_drive/__init__.py +++ b/tests/components/bmw_connected_drive/__init__.py @@ -4,7 +4,6 @@ import json from pathlib import Path from bimmer_connected.account import MyBMWAccount -from bimmer_connected.api.utils import log_to_to_file from homeassistant import config_entries from homeassistant.components.bmw_connected_drive.const import ( @@ -64,15 +63,6 @@ async def mock_vehicles_from_fixture(account: MyBMWAccount) -> None: } fetched_at = utcnow() - # simulate storing fingerprints - if account.config.log_response_path: - for brand in ["bmw", "mini"]: - log_to_to_file( - json.dumps(vehicles[brand]), - account.config.log_response_path, - f"vehicles_v2_{brand}", - ) - # Create a vehicle with base + specific state as provided by state/VIN API for vehicle_base in [vehicle for brand in vehicles.values() for vehicle in brand]: vehicle_state_path = ( @@ -93,14 +83,6 @@ async def mock_vehicles_from_fixture(account: MyBMWAccount) -> None: fetched_at, ) - # simulate storing fingerprints - if account.config.log_response_path: - log_to_to_file( - json.dumps(vehicle_state), - account.config.log_response_path, - f"state_{vehicle_base['vin']}", - ) - async def setup_mocked_integration(hass: HomeAssistant) -> MockConfigEntry: """Mock a fully setup config entry and all components based on fixtures.""" From 2840821594fda614c2f076315bbdb3b5d3f7179e Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Thu, 5 Jan 2023 13:44:10 -1000 Subject: [PATCH 14/16] Reject the WiFI AP when considering to update a shelly config entry from zeroconf (#85265) Reject the WiFI AP IP when considering to update a shelly config entry from zeroconf fixes #85180 --- .../components/shelly/config_flow.py | 14 +++++++++- tests/components/shelly/test_config_flow.py | 26 +++++++++++++++++++ 2 files changed, 39 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/shelly/config_flow.py b/homeassistant/components/shelly/config_flow.py index 8679edf5382..70d2c2492e8 100644 --- a/homeassistant/components/shelly/config_flow.py +++ b/homeassistant/components/shelly/config_flow.py @@ -53,6 +53,8 @@ BLE_SCANNER_OPTIONS = [ selector.SelectOptionDict(value=BLEScannerMode.PASSIVE, label="Passive"), ] +INTERNAL_WIFI_AP_IP = "192.168.33.1" + async def validate_input( hass: HomeAssistant, @@ -217,7 +219,17 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): current_entry := await self.async_set_unique_id(mac) ) and current_entry.data[CONF_HOST] == host: await async_reconnect_soon(self.hass, current_entry) - self._abort_if_unique_id_configured({CONF_HOST: host}) + if host == INTERNAL_WIFI_AP_IP: + # If the device is broadcasting the internal wifi ap ip + # we can't connect to it, so we should not update the + # entry with the new host as it will be unreachable + # + # This is a workaround for a bug in the firmware 0.12 (and older?) + # which should be removed once the firmware is fixed + # and the old version is no longer in use + self._abort_if_unique_id_configured() + else: + self._abort_if_unique_id_configured({CONF_HOST: host}) async def async_step_zeroconf( self, discovery_info: zeroconf.ZeroconfServiceInfo diff --git a/tests/components/shelly/test_config_flow.py b/tests/components/shelly/test_config_flow.py index 6795049a207..1c0a32853e1 100644 --- a/tests/components/shelly/test_config_flow.py +++ b/tests/components/shelly/test_config_flow.py @@ -1,6 +1,7 @@ """Test the Shelly config flow.""" from __future__ import annotations +from dataclasses import replace from unittest.mock import AsyncMock, Mock, patch from aioshelly.exceptions import ( @@ -12,6 +13,7 @@ import pytest from homeassistant import config_entries, data_entry_flow from homeassistant.components import zeroconf +from homeassistant.components.shelly import config_flow from homeassistant.components.shelly.const import ( CONF_BLE_SCANNER_MODE, DOMAIN, @@ -704,6 +706,30 @@ async def test_zeroconf_already_configured(hass): assert entry.data["host"] == "1.1.1.1" +async def test_zeroconf_with_wifi_ap_ip(hass): + """Test we ignore the Wi-FI AP IP.""" + + entry = MockConfigEntry( + domain="shelly", unique_id="test-mac", data={"host": "2.2.2.2"} + ) + entry.add_to_hass(hass) + + with patch( + "aioshelly.common.get_info", + return_value={"mac": "test-mac", "type": "SHSW-1", "auth": False}, + ): + result = await hass.config_entries.flow.async_init( + DOMAIN, + data=replace(DISCOVERY_INFO, host=config_flow.INTERNAL_WIFI_AP_IP), + context={"source": config_entries.SOURCE_ZEROCONF}, + ) + assert result["type"] == data_entry_flow.FlowResultType.ABORT + assert result["reason"] == "already_configured" + + # Test config entry was not updated with the wifi ap ip + assert entry.data["host"] == "2.2.2.2" + + async def test_zeroconf_firmware_unsupported(hass): """Test we abort if device firmware is unsupported.""" with patch("aioshelly.common.get_info", side_effect=FirmwareUnsupported): From f0f2c12d91f7d3b71b98f18652d418c3343b1e53 Mon Sep 17 00:00:00 2001 From: Charles Garwood Date: Thu, 5 Jan 2023 22:10:41 -0500 Subject: [PATCH 15/16] Fix Fully Kiosk service call config entry handling (#85275) * Make sure we're getting the fully_kiosk config entry * Make sure we're getting the fully_kiosk config entry --- .../components/fully_kiosk/services.py | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/fully_kiosk/services.py b/homeassistant/components/fully_kiosk/services.py index 3d63bf4f23c..de269af891a 100644 --- a/homeassistant/components/fully_kiosk/services.py +++ b/homeassistant/components/fully_kiosk/services.py @@ -47,10 +47,19 @@ async def async_setup_services(hass: HomeAssistant) -> None: for target in call.data[ATTR_DEVICE_ID]: device = registry.async_get(target) if device: - coordinator = hass.data[DOMAIN][list(device.config_entries)[0]] - # fully_method(coordinator.fully, *args, **kwargs) would make - # test_services.py fail. - await getattr(coordinator.fully, fully_method.__name__)(*args, **kwargs) + for key in device.config_entries: + entry = hass.config_entries.async_get_entry(key) + if not entry: + continue + if entry.domain != DOMAIN: + continue + coordinator = hass.data[DOMAIN][key] + # fully_method(coordinator.fully, *args, **kwargs) would make + # test_services.py fail. + await getattr(coordinator.fully, fully_method.__name__)( + *args, **kwargs + ) + break async def async_load_url(call: ServiceCall) -> None: """Load a URL on the Fully Kiosk Browser.""" From 33bb9c230bef79c8563d36f489c31863677c03af Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Thu, 5 Jan 2023 22:24:17 -0500 Subject: [PATCH 16/16] Bumped version to 2023.1.1 --- homeassistant/const.py | 2 +- pyproject.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/const.py b/homeassistant/const.py index a606656f579..7d65c6b44a3 100644 --- a/homeassistant/const.py +++ b/homeassistant/const.py @@ -8,7 +8,7 @@ from .backports.enum import StrEnum APPLICATION_NAME: Final = "HomeAssistant" MAJOR_VERSION: Final = 2023 MINOR_VERSION: Final = 1 -PATCH_VERSION: Final = "0" +PATCH_VERSION: Final = "1" __short_version__: Final = f"{MAJOR_VERSION}.{MINOR_VERSION}" __version__: Final = f"{__short_version__}.{PATCH_VERSION}" REQUIRED_PYTHON_VER: Final[tuple[int, int, int]] = (3, 9, 0) diff --git a/pyproject.toml b/pyproject.toml index 7d577d96705..4bb8a5f2911 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "homeassistant" -version = "2023.1.0" +version = "2023.1.1" license = {text = "Apache-2.0"} description = "Open-source home automation platform running on Python 3." readme = "README.rst"