From 574ec5a507fd073553c02a09ec5a96eba27505ef Mon Sep 17 00:00:00 2001 From: Gordon Allott Date: Mon, 20 Jun 2022 19:27:39 +0100 Subject: [PATCH 01/20] Ensure metoffice daily are returned once daily (#72440) * ensure metoffice daily are returned once daily * Fixes metoffice tests for MODE_DAILY --- homeassistant/components/metoffice/helpers.py | 4 ++ tests/components/metoffice/test_weather.py | 45 ++++++++++--------- 2 files changed, 28 insertions(+), 21 deletions(-) diff --git a/homeassistant/components/metoffice/helpers.py b/homeassistant/components/metoffice/helpers.py index 31ac4c141a9..00d5e73501d 100644 --- a/homeassistant/components/metoffice/helpers.py +++ b/homeassistant/components/metoffice/helpers.py @@ -7,6 +7,7 @@ import datapoint from homeassistant.helpers.update_coordinator import UpdateFailed from homeassistant.util.dt import utcnow +from .const import MODE_3HOURLY from .data import MetOfficeData _LOGGER = logging.getLogger(__name__) @@ -39,6 +40,9 @@ def fetch_data(connection: datapoint.Manager, site, mode) -> MetOfficeData: for day in forecast.days for timestep in day.timesteps if timestep.date > time_now + and ( + mode == MODE_3HOURLY or timestep.date.hour > 6 + ) # ensures only one result per day in MODE_DAILY ], site, ) diff --git a/tests/components/metoffice/test_weather.py b/tests/components/metoffice/test_weather.py index fb00203661b..bf279ff3cf7 100644 --- a/tests/components/metoffice/test_weather.py +++ b/tests/components/metoffice/test_weather.py @@ -163,16 +163,17 @@ async def test_one_weather_site_running(hass, requests_mock): assert weather.attributes.get("humidity") == 50 # Also has Forecasts added - again, just pick out 1 entry to check - assert len(weather.attributes.get("forecast")) == 8 + # ensures that daily filters out multiple results per day + assert len(weather.attributes.get("forecast")) == 4 assert ( - weather.attributes.get("forecast")[7]["datetime"] == "2020-04-29T12:00:00+00:00" + weather.attributes.get("forecast")[3]["datetime"] == "2020-04-29T12:00:00+00:00" ) - assert weather.attributes.get("forecast")[7]["condition"] == "rainy" - assert weather.attributes.get("forecast")[7]["precipitation_probability"] == 59 - assert weather.attributes.get("forecast")[7]["temperature"] == 13 - assert weather.attributes.get("forecast")[7]["wind_speed"] == 13 - assert weather.attributes.get("forecast")[7]["wind_bearing"] == "SE" + assert weather.attributes.get("forecast")[3]["condition"] == "rainy" + assert weather.attributes.get("forecast")[3]["precipitation_probability"] == 59 + assert weather.attributes.get("forecast")[3]["temperature"] == 13 + assert weather.attributes.get("forecast")[3]["wind_speed"] == 13 + assert weather.attributes.get("forecast")[3]["wind_bearing"] == "SE" @freeze_time(datetime.datetime(2020, 4, 25, 12, tzinfo=datetime.timezone.utc)) @@ -258,16 +259,17 @@ async def test_two_weather_sites_running(hass, requests_mock): assert weather.attributes.get("humidity") == 50 # Also has Forecasts added - again, just pick out 1 entry to check - assert len(weather.attributes.get("forecast")) == 8 + # ensures that daily filters out multiple results per day + assert len(weather.attributes.get("forecast")) == 4 assert ( - weather.attributes.get("forecast")[7]["datetime"] == "2020-04-29T12:00:00+00:00" + weather.attributes.get("forecast")[3]["datetime"] == "2020-04-29T12:00:00+00:00" ) - assert weather.attributes.get("forecast")[7]["condition"] == "rainy" - assert weather.attributes.get("forecast")[7]["precipitation_probability"] == 59 - assert weather.attributes.get("forecast")[7]["temperature"] == 13 - assert weather.attributes.get("forecast")[7]["wind_speed"] == 13 - assert weather.attributes.get("forecast")[7]["wind_bearing"] == "SE" + assert weather.attributes.get("forecast")[3]["condition"] == "rainy" + assert weather.attributes.get("forecast")[3]["precipitation_probability"] == 59 + assert weather.attributes.get("forecast")[3]["temperature"] == 13 + assert weather.attributes.get("forecast")[3]["wind_speed"] == 13 + assert weather.attributes.get("forecast")[3]["wind_bearing"] == "SE" # King's Lynn 3-hourly weather platform expected results weather = hass.states.get("weather.met_office_king_s_lynn_3_hourly") @@ -305,13 +307,14 @@ async def test_two_weather_sites_running(hass, requests_mock): assert weather.attributes.get("humidity") == 75 # All should have Forecast added - again, just picking out 1 entry to check - assert len(weather.attributes.get("forecast")) == 8 + # ensures daily filters out multiple results per day + assert len(weather.attributes.get("forecast")) == 4 assert ( - weather.attributes.get("forecast")[5]["datetime"] == "2020-04-28T12:00:00+00:00" + weather.attributes.get("forecast")[2]["datetime"] == "2020-04-28T12:00:00+00:00" ) - assert weather.attributes.get("forecast")[5]["condition"] == "cloudy" - assert weather.attributes.get("forecast")[5]["precipitation_probability"] == 14 - assert weather.attributes.get("forecast")[5]["temperature"] == 11 - assert weather.attributes.get("forecast")[5]["wind_speed"] == 7 - assert weather.attributes.get("forecast")[5]["wind_bearing"] == "ESE" + assert weather.attributes.get("forecast")[2]["condition"] == "cloudy" + assert weather.attributes.get("forecast")[2]["precipitation_probability"] == 14 + assert weather.attributes.get("forecast")[2]["temperature"] == 11 + assert weather.attributes.get("forecast")[2]["wind_speed"] == 7 + assert weather.attributes.get("forecast")[2]["wind_bearing"] == "ESE" From 8dbc6b10855b1317a736c08ece0cb8c3f58ef69b Mon Sep 17 00:00:00 2001 From: Jonny Bergdahl Date: Wed, 22 Jun 2022 10:22:09 +0200 Subject: [PATCH 02/20] Fix thumbnail issues in Twitch integration (#72564) Co-authored-by: Franck Nijhof Co-authored-by: Franck Nijhof --- homeassistant/components/twitch/sensor.py | 9 ++++++--- tests/components/twitch/test_twitch.py | 1 + 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/twitch/sensor.py b/homeassistant/components/twitch/sensor.py index 95006a4cab7..dcc2dc9bbf6 100644 --- a/homeassistant/components/twitch/sensor.py +++ b/homeassistant/components/twitch/sensor.py @@ -108,10 +108,8 @@ class TwitchSensor(SensorEntity): def update(self) -> None: """Update device state.""" - followers = self._client.get_users_follows(to_id=self.unique_id)["total"] channel = self._client.get_users(user_ids=[self.unique_id])["data"][0] - self._attr_extra_state_attributes = { ATTR_FOLLOWING: followers, ATTR_VIEWS: channel["view_count"], @@ -151,8 +149,13 @@ class TwitchSensor(SensorEntity): self._attr_extra_state_attributes[ATTR_GAME] = stream["game_name"] self._attr_extra_state_attributes[ATTR_TITLE] = stream["title"] self._attr_entity_picture = stream["thumbnail_url"] + if self._attr_entity_picture is not None: + self._attr_entity_picture = self._attr_entity_picture.format( + height=24, + width=24, + ) else: self._attr_native_value = STATE_OFFLINE self._attr_extra_state_attributes[ATTR_GAME] = None self._attr_extra_state_attributes[ATTR_TITLE] = None - self._attr_entity_picture = channel["offline_image_url"] + self._attr_entity_picture = channel["profile_image_url"] diff --git a/tests/components/twitch/test_twitch.py b/tests/components/twitch/test_twitch.py index bfffeb4ae7f..0e086fe6f7a 100644 --- a/tests/components/twitch/test_twitch.py +++ b/tests/components/twitch/test_twitch.py @@ -28,6 +28,7 @@ USER_OBJECT = { "id": 123, "display_name": "channel123", "offline_image_url": "logo.png", + "profile_image_url": "logo.png", "view_count": 42, } STREAM_OBJECT_ONLINE = { From 7b5258bc575767e578218a24f50d7a4f3c5e4ba7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-Fran=C3=A7ois=20Roy?= Date: Tue, 14 Jun 2022 20:11:37 -0700 Subject: [PATCH 03/20] Bump aiobafi6 to 0.6.0 to fix logging performance (#73517) --- homeassistant/components/baf/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/baf/manifest.json b/homeassistant/components/baf/manifest.json index 8143c35410e..821ad1a21cb 100644 --- a/homeassistant/components/baf/manifest.json +++ b/homeassistant/components/baf/manifest.json @@ -3,7 +3,7 @@ "name": "Big Ass Fans", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/baf", - "requirements": ["aiobafi6==0.5.0"], + "requirements": ["aiobafi6==0.6.0"], "codeowners": ["@bdraco", "@jfroy"], "iot_class": "local_push", "zeroconf": [ diff --git a/requirements_all.txt b/requirements_all.txt index c90229fdb76..608d0d5bbdf 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -122,7 +122,7 @@ aioasuswrt==1.4.0 aioazuredevops==1.3.5 # homeassistant.components.baf -aiobafi6==0.5.0 +aiobafi6==0.6.0 # homeassistant.components.aws aiobotocore==2.1.0 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 881aa1cef55..19a9bdfc684 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -109,7 +109,7 @@ aioasuswrt==1.4.0 aioazuredevops==1.3.5 # homeassistant.components.baf -aiobafi6==0.5.0 +aiobafi6==0.6.0 # homeassistant.components.aws aiobotocore==2.1.0 From a310b28170f608a3632b03979e47bb51d42f9531 Mon Sep 17 00:00:00 2001 From: Maciej Bieniek Date: Thu, 16 Jun 2022 11:43:36 +0200 Subject: [PATCH 04/20] Use IP address instead of hostname in Brother integration (#73556) --- .../components/brother/config_flow.py | 5 +-- tests/components/brother/test_config_flow.py | 38 +++++++++++-------- 2 files changed, 24 insertions(+), 19 deletions(-) diff --git a/homeassistant/components/brother/config_flow.py b/homeassistant/components/brother/config_flow.py index 39a196aa6cb..a136c98bf91 100644 --- a/homeassistant/components/brother/config_flow.py +++ b/homeassistant/components/brother/config_flow.py @@ -83,8 +83,7 @@ class BrotherConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): self, discovery_info: zeroconf.ZeroconfServiceInfo ) -> FlowResult: """Handle zeroconf discovery.""" - # Hostname is format: brother.local. - self.host = discovery_info.hostname.rstrip(".") + self.host = discovery_info.host # Do not probe the device if the host is already configured self._async_abort_entries_match({CONF_HOST: self.host}) @@ -102,7 +101,7 @@ class BrotherConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): # Check if already configured await self.async_set_unique_id(self.brother.serial.lower()) - self._abort_if_unique_id_configured() + self._abort_if_unique_id_configured({CONF_HOST: self.host}) self.context.update( { diff --git a/tests/components/brother/test_config_flow.py b/tests/components/brother/test_config_flow.py index 6dbaebdfa7b..00493011500 100644 --- a/tests/components/brother/test_config_flow.py +++ b/tests/components/brother/test_config_flow.py @@ -12,7 +12,7 @@ from homeassistant.const import CONF_HOST, CONF_TYPE from tests.common import MockConfigEntry, load_fixture -CONFIG = {CONF_HOST: "localhost", CONF_TYPE: "laser"} +CONFIG = {CONF_HOST: "127.0.0.1", CONF_TYPE: "laser"} async def test_show_form(hass): @@ -32,13 +32,15 @@ async def test_create_entry_with_hostname(hass): return_value=json.loads(load_fixture("printer_data.json", "brother")), ): result = await hass.config_entries.flow.async_init( - DOMAIN, context={"source": SOURCE_USER}, data=CONFIG + DOMAIN, + context={"source": SOURCE_USER}, + data={CONF_HOST: "example.local", CONF_TYPE: "laser"}, ) assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY assert result["title"] == "HL-L2340DW 0123456789" - assert result["data"][CONF_HOST] == CONFIG[CONF_HOST] - assert result["data"][CONF_TYPE] == CONFIG[CONF_TYPE] + assert result["data"][CONF_HOST] == "example.local" + assert result["data"][CONF_TYPE] == "laser" async def test_create_entry_with_ipv4_address(hass): @@ -48,9 +50,7 @@ async def test_create_entry_with_ipv4_address(hass): return_value=json.loads(load_fixture("printer_data.json", "brother")), ): result = await hass.config_entries.flow.async_init( - DOMAIN, - context={"source": SOURCE_USER}, - data={CONF_HOST: "127.0.0.1", CONF_TYPE: "laser"}, + DOMAIN, context={"source": SOURCE_USER}, data=CONFIG ) assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY @@ -145,7 +145,7 @@ async def test_zeroconf_snmp_error(hass): DOMAIN, context={"source": SOURCE_ZEROCONF}, data=zeroconf.ZeroconfServiceInfo( - host="mock_host", + host="127.0.0.1", addresses=["mock_host"], hostname="example.local.", name="Brother Printer", @@ -166,7 +166,7 @@ async def test_zeroconf_unsupported_model(hass): DOMAIN, context={"source": SOURCE_ZEROCONF}, data=zeroconf.ZeroconfServiceInfo( - host="mock_host", + host="127.0.0.1", addresses=["mock_host"], hostname="example.local.", name="Brother Printer", @@ -187,15 +187,18 @@ async def test_zeroconf_device_exists_abort(hass): "brother.Brother._get_data", return_value=json.loads(load_fixture("printer_data.json", "brother")), ): - MockConfigEntry(domain=DOMAIN, unique_id="0123456789", data=CONFIG).add_to_hass( - hass + entry = MockConfigEntry( + domain=DOMAIN, + unique_id="0123456789", + data={CONF_HOST: "example.local", CONF_TYPE: "laser"}, ) + entry.add_to_hass(hass) result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": SOURCE_ZEROCONF}, data=zeroconf.ZeroconfServiceInfo( - host="mock_host", + host="127.0.0.1", addresses=["mock_host"], hostname="example.local.", name="Brother Printer", @@ -208,6 +211,9 @@ async def test_zeroconf_device_exists_abort(hass): assert result["type"] == data_entry_flow.RESULT_TYPE_ABORT assert result["reason"] == "already_configured" + # Test config entry got updated with latest IP + assert entry.data["host"] == "127.0.0.1" + async def test_zeroconf_no_probe_existing_device(hass): """Test we do not probe the device is the host is already configured.""" @@ -218,9 +224,9 @@ async def test_zeroconf_no_probe_existing_device(hass): DOMAIN, context={"source": SOURCE_ZEROCONF}, data=zeroconf.ZeroconfServiceInfo( - host="mock_host", + host="127.0.0.1", addresses=["mock_host"], - hostname="localhost", + hostname="example.local.", name="Brother Printer", port=None, properties={}, @@ -245,7 +251,7 @@ async def test_zeroconf_confirm_create_entry(hass): DOMAIN, context={"source": SOURCE_ZEROCONF}, data=zeroconf.ZeroconfServiceInfo( - host="mock_host", + host="127.0.0.1", addresses=["mock_host"], hostname="example.local.", name="Brother Printer", @@ -266,5 +272,5 @@ async def test_zeroconf_confirm_create_entry(hass): assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY assert result["title"] == "HL-L2340DW 0123456789" - assert result["data"][CONF_HOST] == "example.local" + assert result["data"][CONF_HOST] == "127.0.0.1" assert result["data"][CONF_TYPE] == "laser" From 66bcebbab75155b129cf996b434fe09d003a0f5b Mon Sep 17 00:00:00 2001 From: muppet3000 Date: Thu, 16 Jun 2022 09:15:19 +0100 Subject: [PATCH 05/20] Bump growattServer to 1.2.2 (#73561) Fix #71577 - Updating growattServer dependency --- homeassistant/components/growatt_server/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/growatt_server/manifest.json b/homeassistant/components/growatt_server/manifest.json index c8a71d426e7..4127b48ae64 100644 --- a/homeassistant/components/growatt_server/manifest.json +++ b/homeassistant/components/growatt_server/manifest.json @@ -3,7 +3,7 @@ "name": "Growatt", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/growatt_server/", - "requirements": ["growattServer==1.1.0"], + "requirements": ["growattServer==1.2.2"], "codeowners": ["@indykoning", "@muppet3000", "@JasperPlant"], "iot_class": "cloud_polling", "loggers": ["growattServer"] diff --git a/requirements_all.txt b/requirements_all.txt index 608d0d5bbdf..20dd9ae64ff 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -768,7 +768,7 @@ greenwavereality==0.5.1 gridnet==4.0.0 # homeassistant.components.growatt_server -growattServer==1.1.0 +growattServer==1.2.2 # homeassistant.components.gstreamer gstreamer-player==1.1.2 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 19a9bdfc684..fe7002d422a 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -550,7 +550,7 @@ greeneye_monitor==3.0.3 gridnet==4.0.0 # homeassistant.components.growatt_server -growattServer==1.1.0 +growattServer==1.2.2 # homeassistant.components.profiler guppy3==3.1.2 From cfbc2ed4375d071dd2f9a828633e3b49866936bf Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Wed, 15 Jun 2022 19:51:55 -1000 Subject: [PATCH 06/20] Handle offline generators in oncue (#73568) Fixes #73565 --- homeassistant/components/oncue/entity.py | 10 +- tests/components/oncue/__init__.py | 277 +++++++++++++++++++++++ tests/components/oncue/test_sensor.py | 22 +- 3 files changed, 304 insertions(+), 5 deletions(-) diff --git a/homeassistant/components/oncue/entity.py b/homeassistant/components/oncue/entity.py index 40ca21edf96..d1942c532e7 100644 --- a/homeassistant/components/oncue/entity.py +++ b/homeassistant/components/oncue/entity.py @@ -3,6 +3,7 @@ from __future__ import annotations from aiooncue import OncueDevice, OncueSensor +from homeassistant.const import ATTR_CONNECTIONS from homeassistant.helpers import device_registry as dr from homeassistant.helpers.entity import DeviceInfo, Entity, EntityDescription from homeassistant.helpers.update_coordinator import ( @@ -30,16 +31,21 @@ class OncueEntity(CoordinatorEntity, Entity): self._device_id = device_id self._attr_unique_id = f"{device_id}_{description.key}" self._attr_name = f"{device.name} {sensor.display_name}" - mac_address_hex = hex(int(device.sensors["MacAddress"].value))[2:] self._attr_device_info = DeviceInfo( identifiers={(DOMAIN, device_id)}, - connections={(dr.CONNECTION_NETWORK_MAC, mac_address_hex)}, name=device.name, hw_version=device.hardware_version, sw_version=device.sensors["FirmwareVersion"].display_value, model=device.sensors["GensetModelNumberSelect"].display_value, manufacturer="Kohler", ) + try: + mac_address_hex = hex(int(device.sensors["MacAddress"].value))[2:] + except ValueError: # MacAddress may be invalid if the gateway is offline + return + self._attr_device_info[ATTR_CONNECTIONS] = { + (dr.CONNECTION_NETWORK_MAC, mac_address_hex) + } @property def _oncue_value(self) -> str: diff --git a/tests/components/oncue/__init__.py b/tests/components/oncue/__init__.py index 48492a19933..32845aa8d26 100644 --- a/tests/components/oncue/__init__.py +++ b/tests/components/oncue/__init__.py @@ -269,6 +269,271 @@ MOCK_ASYNC_FETCH_ALL = { } +MOCK_ASYNC_FETCH_ALL_OFFLINE_DEVICE = { + "456789": OncueDevice( + name="My Generator", + state="Off", + product_name="RDC 2.4", + hardware_version="319", + serial_number="SERIAL", + sensors={ + "Product": OncueSensor( + name="Product", + display_name="Controller Type", + value="RDC 2.4", + display_value="RDC 2.4", + unit=None, + ), + "FirmwareVersion": OncueSensor( + name="FirmwareVersion", + display_name="Current Firmware", + value="2.0.6", + display_value="2.0.6", + unit=None, + ), + "LatestFirmware": OncueSensor( + name="LatestFirmware", + display_name="Latest Firmware", + value="2.0.6", + display_value="2.0.6", + unit=None, + ), + "EngineSpeed": OncueSensor( + name="EngineSpeed", + display_name="Engine Speed", + value="0", + display_value="0 R/min", + unit="R/min", + ), + "EngineTargetSpeed": OncueSensor( + name="EngineTargetSpeed", + display_name="Engine Target Speed", + value="0", + display_value="0 R/min", + unit="R/min", + ), + "EngineOilPressure": OncueSensor( + name="EngineOilPressure", + display_name="Engine Oil Pressure", + value=0, + display_value="0 Psi", + unit="Psi", + ), + "EngineCoolantTemperature": OncueSensor( + name="EngineCoolantTemperature", + display_name="Engine Coolant Temperature", + value=32, + display_value="32 F", + unit="F", + ), + "BatteryVoltage": OncueSensor( + name="BatteryVoltage", + display_name="Battery Voltage", + value="13.4", + display_value="13.4 V", + unit="V", + ), + "LubeOilTemperature": OncueSensor( + name="LubeOilTemperature", + display_name="Lube Oil Temperature", + value=32, + display_value="32 F", + unit="F", + ), + "GensetControllerTemperature": OncueSensor( + name="GensetControllerTemperature", + display_name="Generator Controller Temperature", + value=84.2, + display_value="84.2 F", + unit="F", + ), + "EngineCompartmentTemperature": OncueSensor( + name="EngineCompartmentTemperature", + display_name="Engine Compartment Temperature", + value=62.6, + display_value="62.6 F", + unit="F", + ), + "GeneratorTrueTotalPower": OncueSensor( + name="GeneratorTrueTotalPower", + display_name="Generator True Total Power", + value="0.0", + display_value="0.0 W", + unit="W", + ), + "GeneratorTruePercentOfRatedPower": OncueSensor( + name="GeneratorTruePercentOfRatedPower", + display_name="Generator True Percent Of Rated Power", + value="0", + display_value="0 %", + unit="%", + ), + "GeneratorVoltageAB": OncueSensor( + name="GeneratorVoltageAB", + display_name="Generator Voltage AB", + value="0.0", + display_value="0.0 V", + unit="V", + ), + "GeneratorVoltageAverageLineToLine": OncueSensor( + name="GeneratorVoltageAverageLineToLine", + display_name="Generator Voltage Average Line To Line", + value="0.0", + display_value="0.0 V", + unit="V", + ), + "GeneratorCurrentAverage": OncueSensor( + name="GeneratorCurrentAverage", + display_name="Generator Current Average", + value="0.0", + display_value="0.0 A", + unit="A", + ), + "GeneratorFrequency": OncueSensor( + name="GeneratorFrequency", + display_name="Generator Frequency", + value="0.0", + display_value="0.0 Hz", + unit="Hz", + ), + "GensetSerialNumber": OncueSensor( + name="GensetSerialNumber", + display_name="Generator Serial Number", + value="33FDGMFR0026", + display_value="33FDGMFR0026", + unit=None, + ), + "GensetState": OncueSensor( + name="GensetState", + display_name="Generator State", + value="Off", + display_value="Off", + unit=None, + ), + "GensetControllerSerialNumber": OncueSensor( + name="GensetControllerSerialNumber", + display_name="Generator Controller Serial Number", + value="-1", + display_value="-1", + unit=None, + ), + "GensetModelNumberSelect": OncueSensor( + name="GensetModelNumberSelect", + display_name="Genset Model Number Select", + value="38 RCLB", + display_value="38 RCLB", + unit=None, + ), + "GensetControllerClockTime": OncueSensor( + name="GensetControllerClockTime", + display_name="Generator Controller Clock Time", + value="2022-01-13 18:08:13", + display_value="2022-01-13 18:08:13", + unit=None, + ), + "GensetControllerTotalOperationTime": OncueSensor( + name="GensetControllerTotalOperationTime", + display_name="Generator Controller Total Operation Time", + value="16770.8", + display_value="16770.8 h", + unit="h", + ), + "EngineTotalRunTime": OncueSensor( + name="EngineTotalRunTime", + display_name="Engine Total Run Time", + value="28.1", + display_value="28.1 h", + unit="h", + ), + "EngineTotalRunTimeLoaded": OncueSensor( + name="EngineTotalRunTimeLoaded", + display_name="Engine Total Run Time Loaded", + value="5.5", + display_value="5.5 h", + unit="h", + ), + "EngineTotalNumberOfStarts": OncueSensor( + name="EngineTotalNumberOfStarts", + display_name="Engine Total Number Of Starts", + value="101", + display_value="101", + unit=None, + ), + "GensetTotalEnergy": OncueSensor( + name="GensetTotalEnergy", + display_name="Genset Total Energy", + value="1.2022309E7", + display_value="1.2022309E7 kWh", + unit="kWh", + ), + "AtsContactorPosition": OncueSensor( + name="AtsContactorPosition", + display_name="Ats Contactor Position", + value="Source1", + display_value="Source1", + unit=None, + ), + "AtsSourcesAvailable": OncueSensor( + name="AtsSourcesAvailable", + display_name="Ats Sources Available", + value="Source1", + display_value="Source1", + unit=None, + ), + "Source1VoltageAverageLineToLine": OncueSensor( + name="Source1VoltageAverageLineToLine", + display_name="Source1 Voltage Average Line To Line", + value="253.5", + display_value="253.5 V", + unit="V", + ), + "Source2VoltageAverageLineToLine": OncueSensor( + name="Source2VoltageAverageLineToLine", + display_name="Source2 Voltage Average Line To Line", + value="0.0", + display_value="0.0 V", + unit="V", + ), + "IPAddress": OncueSensor( + name="IPAddress", + display_name="IP Address", + value="1.2.3.4:1026", + display_value="1.2.3.4:1026", + unit=None, + ), + "MacAddress": OncueSensor( + name="MacAddress", + display_name="Mac Address", + value="--", + display_value="--", + unit=None, + ), + "ConnectedServerIPAddress": OncueSensor( + name="ConnectedServerIPAddress", + display_name="Connected Server IP Address", + value="40.117.195.28", + display_value="40.117.195.28", + unit=None, + ), + "NetworkConnectionEstablished": OncueSensor( + name="NetworkConnectionEstablished", + display_name="Network Connection Established", + value="true", + display_value="True", + unit=None, + ), + "SerialNumber": OncueSensor( + name="SerialNumber", + display_name="Serial Number", + value="1073879692", + display_value="1073879692", + unit=None, + ), + }, + ) +} + + def _patch_login_and_data(): @contextmanager def _patcher(): @@ -279,3 +544,15 @@ def _patch_login_and_data(): yield return _patcher() + + +def _patch_login_and_data_offline_device(): + @contextmanager + def _patcher(): + with patch("homeassistant.components.oncue.Oncue.async_login",), patch( + "homeassistant.components.oncue.Oncue.async_fetch_all", + return_value=MOCK_ASYNC_FETCH_ALL_OFFLINE_DEVICE, + ): + yield + + return _patcher() diff --git a/tests/components/oncue/test_sensor.py b/tests/components/oncue/test_sensor.py index 5fe8b807c1b..60c9f68f81b 100644 --- a/tests/components/oncue/test_sensor.py +++ b/tests/components/oncue/test_sensor.py @@ -1,19 +1,29 @@ """Tests for the oncue sensor.""" from __future__ import annotations +import pytest + from homeassistant.components import oncue from homeassistant.components.oncue.const import DOMAIN from homeassistant.config_entries import ConfigEntryState from homeassistant.const import CONF_PASSWORD, CONF_USERNAME from homeassistant.core import HomeAssistant +from homeassistant.helpers import device_registry as dr, entity_registry as er from homeassistant.setup import async_setup_component -from . import _patch_login_and_data +from . import _patch_login_and_data, _patch_login_and_data_offline_device from tests.common import MockConfigEntry -async def test_sensors(hass: HomeAssistant) -> None: +@pytest.mark.parametrize( + "patcher, connections", + [ + [_patch_login_and_data, {("mac", "c9:24:22:6f:14:00")}], + [_patch_login_and_data_offline_device, set()], + ], +) +async def test_sensors(hass: HomeAssistant, patcher, connections) -> None: """Test that the sensors are setup with the expected values.""" config_entry = MockConfigEntry( domain=DOMAIN, @@ -21,11 +31,17 @@ async def test_sensors(hass: HomeAssistant) -> None: unique_id="any", ) config_entry.add_to_hass(hass) - with _patch_login_and_data(): + with patcher(): await async_setup_component(hass, oncue.DOMAIN, {oncue.DOMAIN: {}}) await hass.async_block_till_done() assert config_entry.state == ConfigEntryState.LOADED + entity_registry = er.async_get(hass) + ent = entity_registry.async_get("sensor.my_generator_latest_firmware") + device_registry = dr.async_get(hass) + dev = device_registry.async_get(ent.device_id) + assert dev.connections == connections + assert len(hass.states.async_all("sensor")) == 25 assert hass.states.get("sensor.my_generator_latest_firmware").state == "2.0.6" From 08382204a348453cec7630fe144e947ecf8f1b26 Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Mon, 20 Jun 2022 10:26:50 +0200 Subject: [PATCH 07/20] Don't attempt to reload MQTT device tracker (#73577) --- homeassistant/components/mqtt/__init__.py | 3 ++- homeassistant/components/mqtt/const.py | 20 ++++++++++++++++++++ 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/mqtt/__init__.py b/homeassistant/components/mqtt/__init__.py index f9a6ebd025f..95cf621f456 100644 --- a/homeassistant/components/mqtt/__init__.py +++ b/homeassistant/components/mqtt/__init__.py @@ -76,6 +76,7 @@ from .const import ( # noqa: F401 MQTT_DISCONNECTED, MQTT_RELOADED, PLATFORMS, + RELOADABLE_PLATFORMS, ) from .models import ( # noqa: F401 MqttCommandTemplate, @@ -378,7 +379,7 @@ async def async_setup_entry( # noqa: C901 # Setup reload service. Once support for legacy config is removed in 2022.9, we # should no longer call async_setup_reload_service but instead implement a custom # service - await async_setup_reload_service(hass, DOMAIN, PLATFORMS) + await async_setup_reload_service(hass, DOMAIN, RELOADABLE_PLATFORMS) async def _async_reload_platforms(_: Event | None) -> None: """Discover entities for a platform.""" diff --git a/homeassistant/components/mqtt/const.py b/homeassistant/components/mqtt/const.py index b05fd867eeb..67a9208faba 100644 --- a/homeassistant/components/mqtt/const.py +++ b/homeassistant/components/mqtt/const.py @@ -92,3 +92,23 @@ PLATFORMS = [ Platform.SWITCH, Platform.VACUUM, ] + +RELOADABLE_PLATFORMS = [ + Platform.ALARM_CONTROL_PANEL, + Platform.BINARY_SENSOR, + Platform.BUTTON, + Platform.CAMERA, + Platform.CLIMATE, + Platform.COVER, + Platform.FAN, + Platform.HUMIDIFIER, + Platform.LIGHT, + Platform.LOCK, + Platform.NUMBER, + Platform.SELECT, + Platform.SCENE, + Platform.SENSOR, + Platform.SIREN, + Platform.SWITCH, + Platform.VACUUM, +] From 8d41f8bbc24ce2eaf35339262795775e9a989d40 Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Mon, 20 Jun 2022 08:52:37 +0200 Subject: [PATCH 08/20] Fix handling of illegal dates in onvif sensor (#73600) * Fix handling of illegal dates in onvif sensor * Address review comment * Address review comment --- homeassistant/components/onvif/parsers.py | 38 ++++++++++++++--------- 1 file changed, 24 insertions(+), 14 deletions(-) diff --git a/homeassistant/components/onvif/parsers.py b/homeassistant/components/onvif/parsers.py index 5141f25cbef..87b901d2c52 100644 --- a/homeassistant/components/onvif/parsers.py +++ b/homeassistant/components/onvif/parsers.py @@ -1,4 +1,6 @@ """ONVIF event parsers.""" +from __future__ import annotations + from collections.abc import Callable, Coroutine import datetime from typing import Any @@ -12,16 +14,16 @@ from .models import Event PARSERS: Registry[str, Callable[[str, Any], Coroutine[Any, Any, Event]]] = Registry() -def datetime_or_zero(value: str) -> datetime: - """Convert strings to datetimes, if invalid, return datetime.min.""" +def local_datetime_or_none(value: str) -> datetime.datetime | None: + """Convert strings to datetimes, if invalid, return None.""" # To handle cameras that return times like '0000-00-00T00:00:00Z' (e.g. hikvision) try: ret = dt_util.parse_datetime(value) except ValueError: - return datetime.datetime.min - if ret is None: - return datetime.datetime.min - return ret + return None + if ret is not None: + return dt_util.as_local(ret) + return None @PARSERS.register("tns1:VideoSource/MotionAlarm") @@ -394,14 +396,16 @@ async def async_parse_last_reboot(uid: str, msg) -> Event: Topic: tns1:Monitoring/OperatingTime/LastReboot """ try: - date_time = datetime_or_zero(msg.Message._value_1.Data.SimpleItem[0].Value) + date_time = local_datetime_or_none( + msg.Message._value_1.Data.SimpleItem[0].Value + ) return Event( f"{uid}_{msg.Topic._value_1}", "Last Reboot", "sensor", "timestamp", None, - dt_util.as_local(date_time), + date_time, EntityCategory.DIAGNOSTIC, ) except (AttributeError, KeyError): @@ -416,14 +420,16 @@ async def async_parse_last_reset(uid: str, msg) -> Event: Topic: tns1:Monitoring/OperatingTime/LastReset """ try: - date_time = datetime_or_zero(msg.Message._value_1.Data.SimpleItem[0].Value) + date_time = local_datetime_or_none( + msg.Message._value_1.Data.SimpleItem[0].Value + ) return Event( f"{uid}_{msg.Topic._value_1}", "Last Reset", "sensor", "timestamp", None, - dt_util.as_local(date_time), + date_time, EntityCategory.DIAGNOSTIC, entity_enabled=False, ) @@ -440,14 +446,16 @@ async def async_parse_backup_last(uid: str, msg) -> Event: """ try: - date_time = datetime_or_zero(msg.Message._value_1.Data.SimpleItem[0].Value) + date_time = local_datetime_or_none( + msg.Message._value_1.Data.SimpleItem[0].Value + ) return Event( f"{uid}_{msg.Topic._value_1}", "Last Backup", "sensor", "timestamp", None, - dt_util.as_local(date_time), + date_time, EntityCategory.DIAGNOSTIC, entity_enabled=False, ) @@ -463,14 +471,16 @@ async def async_parse_last_clock_sync(uid: str, msg) -> Event: Topic: tns1:Monitoring/OperatingTime/LastClockSynchronization """ try: - date_time = datetime_or_zero(msg.Message._value_1.Data.SimpleItem[0].Value) + date_time = local_datetime_or_none( + msg.Message._value_1.Data.SimpleItem[0].Value + ) return Event( f"{uid}_{msg.Topic._value_1}", "Last Clock Synchronization", "sensor", "timestamp", None, - dt_util.as_local(date_time), + date_time, EntityCategory.DIAGNOSTIC, entity_enabled=False, ) From 01053418f6d267af7918eb71674198a29a6ea8ec Mon Sep 17 00:00:00 2001 From: Michael <35783820+mib1185@users.noreply.github.com> Date: Fri, 17 Jun 2022 07:40:02 +0200 Subject: [PATCH 09/20] Fix voltage and current values for Fritz!DECT smart plugs (#73608) fix voltage and current values --- homeassistant/components/fritzbox/sensor.py | 4 +- tests/components/fritzbox/test_switch.py | 65 +++++++++++++++------ 2 files changed, 48 insertions(+), 21 deletions(-) diff --git a/homeassistant/components/fritzbox/sensor.py b/homeassistant/components/fritzbox/sensor.py index 2ae7f9dccc8..161dfc196d2 100644 --- a/homeassistant/components/fritzbox/sensor.py +++ b/homeassistant/components/fritzbox/sensor.py @@ -96,7 +96,7 @@ SENSOR_TYPES: Final[tuple[FritzSensorEntityDescription, ...]] = ( device_class=SensorDeviceClass.VOLTAGE, state_class=SensorStateClass.MEASUREMENT, suitable=lambda device: device.has_powermeter, # type: ignore[no-any-return] - native_value=lambda device: device.voltage / 1000 + native_value=lambda device: device.voltage if getattr(device, "voltage", None) else 0.0, ), @@ -107,7 +107,7 @@ SENSOR_TYPES: Final[tuple[FritzSensorEntityDescription, ...]] = ( device_class=SensorDeviceClass.CURRENT, state_class=SensorStateClass.MEASUREMENT, suitable=lambda device: device.has_powermeter, # type: ignore[no-any-return] - native_value=lambda device: device.power / device.voltage + native_value=lambda device: device.power / device.voltage / 1000 if device.power and getattr(device, "voltage", None) else 0.0, ), diff --git a/tests/components/fritzbox/test_switch.py b/tests/components/fritzbox/test_switch.py index a3135dd61f3..75799a08d48 100644 --- a/tests/components/fritzbox/test_switch.py +++ b/tests/components/fritzbox/test_switch.py @@ -16,6 +16,8 @@ from homeassistant.const import ( ATTR_FRIENDLY_NAME, ATTR_UNIT_OF_MEASUREMENT, CONF_DEVICES, + ELECTRIC_CURRENT_AMPERE, + ELECTRIC_POTENTIAL_VOLT, ENERGY_KILO_WATT_HOUR, POWER_WATT, SERVICE_TURN_OFF, @@ -48,29 +50,54 @@ async def test_setup(hass: HomeAssistant, fritz: Mock): assert state.attributes[ATTR_FRIENDLY_NAME] == CONF_FAKE_NAME assert ATTR_STATE_CLASS not in state.attributes - state = hass.states.get(f"{SENSOR_DOMAIN}.{CONF_FAKE_NAME}_temperature") - assert state - assert state.state == "1.23" - assert state.attributes[ATTR_FRIENDLY_NAME] == f"{CONF_FAKE_NAME} Temperature" - assert state.attributes[ATTR_UNIT_OF_MEASUREMENT] == TEMP_CELSIUS - assert state.attributes[ATTR_STATE_CLASS] == SensorStateClass.MEASUREMENT - state = hass.states.get(f"{ENTITY_ID}_humidity") assert state is None - state = hass.states.get(f"{SENSOR_DOMAIN}.{CONF_FAKE_NAME}_power_consumption") - assert state - assert state.state == "5.678" - assert state.attributes[ATTR_FRIENDLY_NAME] == f"{CONF_FAKE_NAME} Power Consumption" - assert state.attributes[ATTR_UNIT_OF_MEASUREMENT] == POWER_WATT - assert state.attributes[ATTR_STATE_CLASS] == SensorStateClass.MEASUREMENT + sensors = ( + [ + f"{SENSOR_DOMAIN}.{CONF_FAKE_NAME}_temperature", + "1.23", + f"{CONF_FAKE_NAME} Temperature", + TEMP_CELSIUS, + SensorStateClass.MEASUREMENT, + ], + [ + f"{SENSOR_DOMAIN}.{CONF_FAKE_NAME}_power_consumption", + "5.678", + f"{CONF_FAKE_NAME} Power Consumption", + POWER_WATT, + SensorStateClass.MEASUREMENT, + ], + [ + f"{SENSOR_DOMAIN}.{CONF_FAKE_NAME}_total_energy", + "1.234", + f"{CONF_FAKE_NAME} Total Energy", + ENERGY_KILO_WATT_HOUR, + SensorStateClass.TOTAL_INCREASING, + ], + [ + f"{SENSOR_DOMAIN}.{CONF_FAKE_NAME}_voltage", + "230", + f"{CONF_FAKE_NAME} Voltage", + ELECTRIC_POTENTIAL_VOLT, + SensorStateClass.MEASUREMENT, + ], + [ + f"{SENSOR_DOMAIN}.{CONF_FAKE_NAME}_electric_current", + "0.0246869565217391", + f"{CONF_FAKE_NAME} Electric Current", + ELECTRIC_CURRENT_AMPERE, + SensorStateClass.MEASUREMENT, + ], + ) - state = hass.states.get(f"{SENSOR_DOMAIN}.{CONF_FAKE_NAME}_total_energy") - assert state - assert state.state == "1.234" - assert state.attributes[ATTR_FRIENDLY_NAME] == f"{CONF_FAKE_NAME} Total Energy" - assert state.attributes[ATTR_UNIT_OF_MEASUREMENT] == ENERGY_KILO_WATT_HOUR - assert state.attributes[ATTR_STATE_CLASS] == SensorStateClass.TOTAL_INCREASING + for sensor in sensors: + state = hass.states.get(sensor[0]) + assert state + assert state.state == sensor[1] + assert state.attributes[ATTR_FRIENDLY_NAME] == sensor[2] + assert state.attributes[ATTR_UNIT_OF_MEASUREMENT] == sensor[3] + assert state.attributes[ATTR_STATE_CLASS] == sensor[4] async def test_turn_on(hass: HomeAssistant, fritz: Mock): From 155117758191ff75eef80d09a4a5e6d67dba16ab Mon Sep 17 00:00:00 2001 From: Jan Bouwhuis Date: Mon, 20 Jun 2022 08:51:12 +0200 Subject: [PATCH 10/20] Fix MQTT config schema to ensure correct validation (#73619) * Ensure config schema validation * Use correct schema for device_tracker * Remove schema validation from the platform setup * Remove loop to build schema --- homeassistant/components/mqtt/__init__.py | 6 +- .../components/mqtt/alarm_control_panel.py | 2 +- .../components/mqtt/binary_sensor.py | 4 +- homeassistant/components/mqtt/button.py | 4 +- homeassistant/components/mqtt/camera.py | 4 +- homeassistant/components/mqtt/climate.py | 4 +- homeassistant/components/mqtt/config.py | 107 +--------- .../components/mqtt/config_integration.py | 193 ++++++++++++++++++ homeassistant/components/mqtt/cover.py | 2 +- .../mqtt/device_tracker/__init__.py | 1 + .../mqtt/device_tracker/schema_discovery.py | 2 +- homeassistant/components/mqtt/fan.py | 4 +- homeassistant/components/mqtt/humidifier.py | 4 +- .../components/mqtt/light/__init__.py | 2 +- homeassistant/components/mqtt/lock.py | 2 +- homeassistant/components/mqtt/mixins.py | 22 +- homeassistant/components/mqtt/number.py | 4 +- homeassistant/components/mqtt/scene.py | 2 +- homeassistant/components/mqtt/select.py | 4 +- homeassistant/components/mqtt/sensor.py | 4 +- homeassistant/components/mqtt/siren.py | 2 +- homeassistant/components/mqtt/switch.py | 4 +- .../components/mqtt/vacuum/__init__.py | 4 +- tests/components/mqtt/test_humidifier.py | 13 ++ tests/components/mqtt/test_init.py | 58 +++--- 25 files changed, 262 insertions(+), 196 deletions(-) create mode 100644 homeassistant/components/mqtt/config_integration.py diff --git a/homeassistant/components/mqtt/__init__.py b/homeassistant/components/mqtt/__init__.py index 95cf621f456..fc4e15f3237 100644 --- a/homeassistant/components/mqtt/__init__.py +++ b/homeassistant/components/mqtt/__init__.py @@ -47,7 +47,11 @@ from .client import ( # noqa: F401 publish, subscribe, ) -from .config import CONFIG_SCHEMA_BASE, DEFAULT_VALUES, DEPRECATED_CONFIG_KEYS +from .config_integration import ( + CONFIG_SCHEMA_BASE, + DEFAULT_VALUES, + DEPRECATED_CONFIG_KEYS, +) from .const import ( # noqa: F401 ATTR_PAYLOAD, ATTR_QOS, diff --git a/homeassistant/components/mqtt/alarm_control_panel.py b/homeassistant/components/mqtt/alarm_control_panel.py index c0c6f9732d7..6bb7d9cd0d1 100644 --- a/homeassistant/components/mqtt/alarm_control_panel.py +++ b/homeassistant/components/mqtt/alarm_control_panel.py @@ -148,7 +148,7 @@ async def async_setup_entry( """Set up MQTT alarm control panel through configuration.yaml and dynamically through MQTT discovery.""" # load and initialize platform config from configuration.yaml config_entry.async_on_unload( - await async_setup_platform_discovery(hass, alarm.DOMAIN, PLATFORM_SCHEMA_MODERN) + await async_setup_platform_discovery(hass, alarm.DOMAIN) ) # setup for discovery setup = functools.partial( diff --git a/homeassistant/components/mqtt/binary_sensor.py b/homeassistant/components/mqtt/binary_sensor.py index cec065e20f2..39fd87c8b02 100644 --- a/homeassistant/components/mqtt/binary_sensor.py +++ b/homeassistant/components/mqtt/binary_sensor.py @@ -103,9 +103,7 @@ async def async_setup_entry( """Set up MQTT binary sensor through configuration.yaml and dynamically through MQTT discovery.""" # load and initialize platform config from configuration.yaml config_entry.async_on_unload( - await async_setup_platform_discovery( - hass, binary_sensor.DOMAIN, PLATFORM_SCHEMA_MODERN - ) + await async_setup_platform_discovery(hass, binary_sensor.DOMAIN) ) # setup for discovery setup = functools.partial( diff --git a/homeassistant/components/mqtt/button.py b/homeassistant/components/mqtt/button.py index afa9900db35..370243c3579 100644 --- a/homeassistant/components/mqtt/button.py +++ b/homeassistant/components/mqtt/button.py @@ -83,9 +83,7 @@ async def async_setup_entry( """Set up MQTT button through configuration.yaml and dynamically through MQTT discovery.""" # load and initialize platform config from configuration.yaml config_entry.async_on_unload( - await async_setup_platform_discovery( - hass, button.DOMAIN, PLATFORM_SCHEMA_MODERN - ) + await async_setup_platform_discovery(hass, button.DOMAIN) ) # setup for discovery setup = functools.partial( diff --git a/homeassistant/components/mqtt/camera.py b/homeassistant/components/mqtt/camera.py index 86db828b111..5c8d3bc48b2 100644 --- a/homeassistant/components/mqtt/camera.py +++ b/homeassistant/components/mqtt/camera.py @@ -81,9 +81,7 @@ async def async_setup_entry( """Set up MQTT camera through configuration.yaml and dynamically through MQTT discovery.""" # load and initialize platform config from configuration.yaml config_entry.async_on_unload( - await async_setup_platform_discovery( - hass, camera.DOMAIN, PLATFORM_SCHEMA_MODERN - ) + await async_setup_platform_discovery(hass, camera.DOMAIN) ) # setup for discovery setup = functools.partial( diff --git a/homeassistant/components/mqtt/climate.py b/homeassistant/components/mqtt/climate.py index bdcc82f2c39..a26e9cba8df 100644 --- a/homeassistant/components/mqtt/climate.py +++ b/homeassistant/components/mqtt/climate.py @@ -392,9 +392,7 @@ async def async_setup_entry( """Set up MQTT climate device through configuration.yaml and dynamically through MQTT discovery.""" # load and initialize platform config from configuration.yaml config_entry.async_on_unload( - await async_setup_platform_discovery( - hass, climate.DOMAIN, PLATFORM_SCHEMA_MODERN - ) + await async_setup_platform_discovery(hass, climate.DOMAIN) ) # setup for discovery setup = functools.partial( diff --git a/homeassistant/components/mqtt/config.py b/homeassistant/components/mqtt/config.py index 4f84d911418..8cfc3490f0c 100644 --- a/homeassistant/components/mqtt/config.py +++ b/homeassistant/components/mqtt/config.py @@ -3,126 +3,21 @@ from __future__ import annotations import voluptuous as vol -from homeassistant.const import ( - CONF_CLIENT_ID, - CONF_DISCOVERY, - CONF_PASSWORD, - CONF_PORT, - CONF_PROTOCOL, - CONF_USERNAME, - CONF_VALUE_TEMPLATE, -) +from homeassistant.const import CONF_VALUE_TEMPLATE from homeassistant.helpers import config_validation as cv from .const import ( - ATTR_PAYLOAD, - ATTR_QOS, - ATTR_RETAIN, - ATTR_TOPIC, - CONF_BIRTH_MESSAGE, - CONF_BROKER, - CONF_CERTIFICATE, - CONF_CLIENT_CERT, - CONF_CLIENT_KEY, CONF_COMMAND_TOPIC, - CONF_DISCOVERY_PREFIX, CONF_ENCODING, - CONF_KEEPALIVE, CONF_QOS, CONF_RETAIN, CONF_STATE_TOPIC, - CONF_TLS_INSECURE, - CONF_TLS_VERSION, - CONF_WILL_MESSAGE, - DEFAULT_BIRTH, - DEFAULT_DISCOVERY, DEFAULT_ENCODING, - DEFAULT_PREFIX, DEFAULT_QOS, DEFAULT_RETAIN, - DEFAULT_WILL, - PLATFORMS, - PROTOCOL_31, - PROTOCOL_311, ) from .util import _VALID_QOS_SCHEMA, valid_publish_topic, valid_subscribe_topic -DEFAULT_PORT = 1883 -DEFAULT_KEEPALIVE = 60 -DEFAULT_PROTOCOL = PROTOCOL_311 -DEFAULT_TLS_PROTOCOL = "auto" - -DEFAULT_VALUES = { - CONF_BIRTH_MESSAGE: DEFAULT_BIRTH, - CONF_DISCOVERY: DEFAULT_DISCOVERY, - CONF_PORT: DEFAULT_PORT, - CONF_TLS_VERSION: DEFAULT_TLS_PROTOCOL, - CONF_WILL_MESSAGE: DEFAULT_WILL, -} - -CLIENT_KEY_AUTH_MSG = ( - "client_key and client_cert must both be present in " - "the MQTT broker configuration" -) - -MQTT_WILL_BIRTH_SCHEMA = vol.Schema( - { - vol.Inclusive(ATTR_TOPIC, "topic_payload"): valid_publish_topic, - vol.Inclusive(ATTR_PAYLOAD, "topic_payload"): cv.string, - vol.Optional(ATTR_QOS, default=DEFAULT_QOS): _VALID_QOS_SCHEMA, - vol.Optional(ATTR_RETAIN, default=DEFAULT_RETAIN): cv.boolean, - }, - required=True, -) - -PLATFORM_CONFIG_SCHEMA_BASE = vol.Schema( - {vol.Optional(platform.value): cv.ensure_list for platform in PLATFORMS} -) - -CONFIG_SCHEMA_BASE = PLATFORM_CONFIG_SCHEMA_BASE.extend( - { - vol.Optional(CONF_CLIENT_ID): cv.string, - vol.Optional(CONF_KEEPALIVE, default=DEFAULT_KEEPALIVE): vol.All( - vol.Coerce(int), vol.Range(min=15) - ), - vol.Optional(CONF_BROKER): cv.string, - vol.Optional(CONF_PORT): cv.port, - vol.Optional(CONF_USERNAME): cv.string, - vol.Optional(CONF_PASSWORD): cv.string, - vol.Optional(CONF_CERTIFICATE): vol.Any("auto", cv.isfile), - vol.Inclusive( - CONF_CLIENT_KEY, "client_key_auth", msg=CLIENT_KEY_AUTH_MSG - ): cv.isfile, - vol.Inclusive( - CONF_CLIENT_CERT, "client_key_auth", msg=CLIENT_KEY_AUTH_MSG - ): cv.isfile, - vol.Optional(CONF_TLS_INSECURE): cv.boolean, - vol.Optional(CONF_TLS_VERSION): vol.Any("auto", "1.0", "1.1", "1.2"), - vol.Optional(CONF_PROTOCOL, default=DEFAULT_PROTOCOL): vol.All( - cv.string, vol.In([PROTOCOL_31, PROTOCOL_311]) - ), - vol.Optional(CONF_WILL_MESSAGE): MQTT_WILL_BIRTH_SCHEMA, - vol.Optional(CONF_BIRTH_MESSAGE): MQTT_WILL_BIRTH_SCHEMA, - vol.Optional(CONF_DISCOVERY): cv.boolean, - # discovery_prefix must be a valid publish topic because if no - # state topic is specified, it will be created with the given prefix. - vol.Optional( - CONF_DISCOVERY_PREFIX, default=DEFAULT_PREFIX - ): valid_publish_topic, - } -) - -DEPRECATED_CONFIG_KEYS = [ - CONF_BIRTH_MESSAGE, - CONF_BROKER, - CONF_DISCOVERY, - CONF_PASSWORD, - CONF_PORT, - CONF_TLS_VERSION, - CONF_USERNAME, - CONF_WILL_MESSAGE, -] - SCHEMA_BASE = { vol.Optional(CONF_QOS, default=DEFAULT_QOS): _VALID_QOS_SCHEMA, vol.Optional(CONF_ENCODING, default=DEFAULT_ENCODING): cv.string, diff --git a/homeassistant/components/mqtt/config_integration.py b/homeassistant/components/mqtt/config_integration.py new file mode 100644 index 00000000000..ab685a63802 --- /dev/null +++ b/homeassistant/components/mqtt/config_integration.py @@ -0,0 +1,193 @@ +"""Support for MQTT platform config setup.""" +from __future__ import annotations + +import voluptuous as vol + +from homeassistant.const import ( + CONF_CLIENT_ID, + CONF_DISCOVERY, + CONF_PASSWORD, + CONF_PORT, + CONF_PROTOCOL, + CONF_USERNAME, + Platform, +) +from homeassistant.helpers import config_validation as cv + +from . import ( + alarm_control_panel as alarm_control_panel_platform, + binary_sensor as binary_sensor_platform, + button as button_platform, + camera as camera_platform, + climate as climate_platform, + cover as cover_platform, + device_tracker as device_tracker_platform, + fan as fan_platform, + humidifier as humidifier_platform, + light as light_platform, + lock as lock_platform, + number as number_platform, + scene as scene_platform, + select as select_platform, + sensor as sensor_platform, + siren as siren_platform, + switch as switch_platform, + vacuum as vacuum_platform, +) +from .const import ( + ATTR_PAYLOAD, + ATTR_QOS, + ATTR_RETAIN, + ATTR_TOPIC, + CONF_BIRTH_MESSAGE, + CONF_BROKER, + CONF_CERTIFICATE, + CONF_CLIENT_CERT, + CONF_CLIENT_KEY, + CONF_DISCOVERY_PREFIX, + CONF_KEEPALIVE, + CONF_TLS_INSECURE, + CONF_TLS_VERSION, + CONF_WILL_MESSAGE, + DEFAULT_BIRTH, + DEFAULT_DISCOVERY, + DEFAULT_PREFIX, + DEFAULT_QOS, + DEFAULT_RETAIN, + DEFAULT_WILL, + PROTOCOL_31, + PROTOCOL_311, +) +from .util import _VALID_QOS_SCHEMA, valid_publish_topic + +DEFAULT_PORT = 1883 +DEFAULT_KEEPALIVE = 60 +DEFAULT_PROTOCOL = PROTOCOL_311 +DEFAULT_TLS_PROTOCOL = "auto" + +DEFAULT_VALUES = { + CONF_BIRTH_MESSAGE: DEFAULT_BIRTH, + CONF_DISCOVERY: DEFAULT_DISCOVERY, + CONF_PORT: DEFAULT_PORT, + CONF_TLS_VERSION: DEFAULT_TLS_PROTOCOL, + CONF_WILL_MESSAGE: DEFAULT_WILL, +} + +PLATFORM_CONFIG_SCHEMA_BASE = vol.Schema( + { + Platform.ALARM_CONTROL_PANEL.value: vol.All( + cv.ensure_list, [alarm_control_panel_platform.PLATFORM_SCHEMA_MODERN] # type: ignore[has-type] + ), + Platform.BINARY_SENSOR.value: vol.All( + cv.ensure_list, [binary_sensor_platform.PLATFORM_SCHEMA_MODERN] # type: ignore[has-type] + ), + Platform.BUTTON.value: vol.All( + cv.ensure_list, [button_platform.PLATFORM_SCHEMA_MODERN] # type: ignore[has-type] + ), + Platform.CAMERA.value: vol.All( + cv.ensure_list, [camera_platform.PLATFORM_SCHEMA_MODERN] # type: ignore[has-type] + ), + Platform.CLIMATE.value: vol.All( + cv.ensure_list, [climate_platform.PLATFORM_SCHEMA_MODERN] # type: ignore[has-type] + ), + Platform.COVER.value: vol.All( + cv.ensure_list, [cover_platform.PLATFORM_SCHEMA_MODERN] # type: ignore[has-type] + ), + Platform.DEVICE_TRACKER.value: vol.All( + cv.ensure_list, [device_tracker_platform.PLATFORM_SCHEMA_MODERN] # type: ignore[has-type] + ), + Platform.FAN.value: vol.All( + cv.ensure_list, [fan_platform.PLATFORM_SCHEMA_MODERN] # type: ignore[has-type] + ), + Platform.HUMIDIFIER.value: vol.All( + cv.ensure_list, [humidifier_platform.PLATFORM_SCHEMA_MODERN] # type: ignore[has-type] + ), + Platform.LOCK.value: vol.All( + cv.ensure_list, [lock_platform.PLATFORM_SCHEMA_MODERN] # type: ignore[has-type] + ), + Platform.LIGHT.value: vol.All( + cv.ensure_list, [light_platform.PLATFORM_SCHEMA_MODERN] # type: ignore[has-type] + ), + Platform.NUMBER.value: vol.All( + cv.ensure_list, [number_platform.PLATFORM_SCHEMA_MODERN] # type: ignore[has-type] + ), + Platform.SCENE.value: vol.All( + cv.ensure_list, [scene_platform.PLATFORM_SCHEMA_MODERN] # type: ignore[has-type] + ), + Platform.SELECT.value: vol.All( + cv.ensure_list, [select_platform.PLATFORM_SCHEMA_MODERN] # type: ignore[has-type] + ), + Platform.SENSOR.value: vol.All( + cv.ensure_list, [sensor_platform.PLATFORM_SCHEMA_MODERN] # type: ignore[has-type] + ), + Platform.SIREN.value: vol.All( + cv.ensure_list, [siren_platform.PLATFORM_SCHEMA_MODERN] # type: ignore[has-type] + ), + Platform.SWITCH.value: vol.All( + cv.ensure_list, [switch_platform.PLATFORM_SCHEMA_MODERN] # type: ignore[has-type] + ), + Platform.VACUUM.value: vol.All( + cv.ensure_list, [vacuum_platform.PLATFORM_SCHEMA_MODERN] # type: ignore[has-type] + ), + } +) + + +CLIENT_KEY_AUTH_MSG = ( + "client_key and client_cert must both be present in " + "the MQTT broker configuration" +) + +MQTT_WILL_BIRTH_SCHEMA = vol.Schema( + { + vol.Inclusive(ATTR_TOPIC, "topic_payload"): valid_publish_topic, + vol.Inclusive(ATTR_PAYLOAD, "topic_payload"): cv.string, + vol.Optional(ATTR_QOS, default=DEFAULT_QOS): _VALID_QOS_SCHEMA, + vol.Optional(ATTR_RETAIN, default=DEFAULT_RETAIN): cv.boolean, + }, + required=True, +) + +CONFIG_SCHEMA_BASE = PLATFORM_CONFIG_SCHEMA_BASE.extend( + { + vol.Optional(CONF_CLIENT_ID): cv.string, + vol.Optional(CONF_KEEPALIVE, default=DEFAULT_KEEPALIVE): vol.All( + vol.Coerce(int), vol.Range(min=15) + ), + vol.Optional(CONF_BROKER): cv.string, + vol.Optional(CONF_PORT): cv.port, + vol.Optional(CONF_USERNAME): cv.string, + vol.Optional(CONF_PASSWORD): cv.string, + vol.Optional(CONF_CERTIFICATE): vol.Any("auto", cv.isfile), + vol.Inclusive( + CONF_CLIENT_KEY, "client_key_auth", msg=CLIENT_KEY_AUTH_MSG + ): cv.isfile, + vol.Inclusive( + CONF_CLIENT_CERT, "client_key_auth", msg=CLIENT_KEY_AUTH_MSG + ): cv.isfile, + vol.Optional(CONF_TLS_INSECURE): cv.boolean, + vol.Optional(CONF_TLS_VERSION): vol.Any("auto", "1.0", "1.1", "1.2"), + vol.Optional(CONF_PROTOCOL, default=DEFAULT_PROTOCOL): vol.All( + cv.string, vol.In([PROTOCOL_31, PROTOCOL_311]) + ), + vol.Optional(CONF_WILL_MESSAGE): MQTT_WILL_BIRTH_SCHEMA, + vol.Optional(CONF_BIRTH_MESSAGE): MQTT_WILL_BIRTH_SCHEMA, + vol.Optional(CONF_DISCOVERY): cv.boolean, + # discovery_prefix must be a valid publish topic because if no + # state topic is specified, it will be created with the given prefix. + vol.Optional( + CONF_DISCOVERY_PREFIX, default=DEFAULT_PREFIX + ): valid_publish_topic, + } +) + +DEPRECATED_CONFIG_KEYS = [ + CONF_BIRTH_MESSAGE, + CONF_BROKER, + CONF_DISCOVERY, + CONF_PASSWORD, + CONF_PORT, + CONF_TLS_VERSION, + CONF_USERNAME, + CONF_WILL_MESSAGE, +] diff --git a/homeassistant/components/mqtt/cover.py b/homeassistant/components/mqtt/cover.py index 8d4df0c301d..0901a4f63a6 100644 --- a/homeassistant/components/mqtt/cover.py +++ b/homeassistant/components/mqtt/cover.py @@ -241,7 +241,7 @@ async def async_setup_entry( """Set up MQTT cover through configuration.yaml and dynamically through MQTT discovery.""" # load and initialize platform config from configuration.yaml config_entry.async_on_unload( - await async_setup_platform_discovery(hass, cover.DOMAIN, PLATFORM_SCHEMA_MODERN) + await async_setup_platform_discovery(hass, cover.DOMAIN) ) # setup for discovery setup = functools.partial( diff --git a/homeassistant/components/mqtt/device_tracker/__init__.py b/homeassistant/components/mqtt/device_tracker/__init__.py index bcd5bbd4ee1..1b6c2b25ff3 100644 --- a/homeassistant/components/mqtt/device_tracker/__init__.py +++ b/homeassistant/components/mqtt/device_tracker/__init__.py @@ -4,6 +4,7 @@ import voluptuous as vol from homeassistant.components import device_tracker from ..mixins import warn_for_legacy_schema +from .schema_discovery import PLATFORM_SCHEMA_MODERN # noqa: F401 from .schema_discovery import async_setup_entry_from_discovery from .schema_yaml import PLATFORM_SCHEMA_YAML, async_setup_scanner_from_yaml diff --git a/homeassistant/components/mqtt/device_tracker/schema_discovery.py b/homeassistant/components/mqtt/device_tracker/schema_discovery.py index 1b48e15b80e..1ba540c8243 100644 --- a/homeassistant/components/mqtt/device_tracker/schema_discovery.py +++ b/homeassistant/components/mqtt/device_tracker/schema_discovery.py @@ -54,7 +54,7 @@ async def async_setup_entry_from_discovery(hass, config_entry, async_add_entitie *( _async_setup_entity(hass, async_add_entities, config, config_entry) for config in await async_get_platform_config_from_yaml( - hass, device_tracker.DOMAIN, PLATFORM_SCHEMA_MODERN + hass, device_tracker.DOMAIN ) ) ) diff --git a/homeassistant/components/mqtt/fan.py b/homeassistant/components/mqtt/fan.py index d0b4ff10692..4e1d1465ac2 100644 --- a/homeassistant/components/mqtt/fan.py +++ b/homeassistant/components/mqtt/fan.py @@ -231,9 +231,7 @@ async def async_setup_entry( ) -> None: """Set up MQTT fan through configuration.yaml and dynamically through MQTT discovery.""" # load and initialize platform config from configuration.yaml - config_entry.async_on_unload( - await async_setup_platform_discovery(hass, fan.DOMAIN, PLATFORM_SCHEMA_MODERN) - ) + config_entry.async_on_unload(await async_setup_platform_discovery(hass, fan.DOMAIN)) # setup for discovery setup = functools.partial( _async_setup_entity, hass, async_add_entities, config_entry=config_entry diff --git a/homeassistant/components/mqtt/humidifier.py b/homeassistant/components/mqtt/humidifier.py index 1c9ec5dc201..d2856767cf0 100644 --- a/homeassistant/components/mqtt/humidifier.py +++ b/homeassistant/components/mqtt/humidifier.py @@ -188,9 +188,7 @@ async def async_setup_entry( """Set up MQTT humidifier through configuration.yaml and dynamically through MQTT discovery.""" # load and initialize platform config from configuration.yaml config_entry.async_on_unload( - await async_setup_platform_discovery( - hass, humidifier.DOMAIN, PLATFORM_SCHEMA_MODERN - ) + await async_setup_platform_discovery(hass, humidifier.DOMAIN) ) # setup for discovery setup = functools.partial( diff --git a/homeassistant/components/mqtt/light/__init__.py b/homeassistant/components/mqtt/light/__init__.py index 158ea6ffa0d..d4914cb9506 100644 --- a/homeassistant/components/mqtt/light/__init__.py +++ b/homeassistant/components/mqtt/light/__init__.py @@ -112,7 +112,7 @@ async def async_setup_entry( """Set up MQTT lights configured under the light platform key (deprecated).""" # load and initialize platform config from configuration.yaml config_entry.async_on_unload( - await async_setup_platform_discovery(hass, light.DOMAIN, PLATFORM_SCHEMA_MODERN) + await async_setup_platform_discovery(hass, light.DOMAIN) ) # setup for discovery setup = functools.partial( diff --git a/homeassistant/components/mqtt/lock.py b/homeassistant/components/mqtt/lock.py index 862e76635f7..ab9eca9aafa 100644 --- a/homeassistant/components/mqtt/lock.py +++ b/homeassistant/components/mqtt/lock.py @@ -103,7 +103,7 @@ async def async_setup_entry( """Set up MQTT lock through configuration.yaml and dynamically through MQTT discovery.""" # load and initialize platform config from configuration.yaml config_entry.async_on_unload( - await async_setup_platform_discovery(hass, lock.DOMAIN, PLATFORM_SCHEMA_MODERN) + await async_setup_platform_discovery(hass, lock.DOMAIN) ) # setup for discovery setup = functools.partial( diff --git a/homeassistant/components/mqtt/mixins.py b/homeassistant/components/mqtt/mixins.py index e768c2ff409..dcf387eb360 100644 --- a/homeassistant/components/mqtt/mixins.py +++ b/homeassistant/components/mqtt/mixins.py @@ -10,7 +10,6 @@ from typing import Any, Protocol, cast, final import voluptuous as vol -from homeassistant.config import async_log_exception from homeassistant.config_entries import ConfigEntry from homeassistant.const import ( ATTR_CONFIGURATION_URL, @@ -263,7 +262,7 @@ class SetupEntity(Protocol): async def async_setup_platform_discovery( - hass: HomeAssistant, platform_domain: str, schema: vol.Schema + hass: HomeAssistant, platform_domain: str ) -> CALLBACK_TYPE: """Set up platform discovery for manual config.""" @@ -282,7 +281,7 @@ async def async_setup_platform_discovery( *( discovery.async_load_platform(hass, platform_domain, DOMAIN, config, {}) for config in await async_get_platform_config_from_yaml( - hass, platform_domain, schema, config_yaml + hass, platform_domain, config_yaml ) ) ) @@ -295,32 +294,17 @@ async def async_setup_platform_discovery( async def async_get_platform_config_from_yaml( hass: HomeAssistant, platform_domain: str, - schema: vol.Schema, config_yaml: ConfigType = None, ) -> list[ConfigType]: """Return a list of validated configurations for the domain.""" - def async_validate_config( - hass: HomeAssistant, - config: list[ConfigType], - ) -> list[ConfigType]: - """Validate config.""" - validated_config = [] - for config_item in config: - try: - validated_config.append(schema(config_item)) - except vol.MultipleInvalid as err: - async_log_exception(err, platform_domain, config_item, hass) - - return validated_config - if config_yaml is None: config_yaml = hass.data.get(DATA_MQTT_CONFIG) if not config_yaml: return [] if not (platform_configs := config_yaml.get(platform_domain)): return [] - return async_validate_config(hass, platform_configs) + return platform_configs async def async_setup_entry_helper(hass, domain, async_setup, schema): diff --git a/homeassistant/components/mqtt/number.py b/homeassistant/components/mqtt/number.py index 1404dc86a3c..7ec07394aa5 100644 --- a/homeassistant/components/mqtt/number.py +++ b/homeassistant/components/mqtt/number.py @@ -134,9 +134,7 @@ async def async_setup_entry( """Set up MQTT number through configuration.yaml and dynamically through MQTT discovery.""" # load and initialize platform config from configuration.yaml config_entry.async_on_unload( - await async_setup_platform_discovery( - hass, number.DOMAIN, PLATFORM_SCHEMA_MODERN - ) + await async_setup_platform_discovery(hass, number.DOMAIN) ) # setup for discovery setup = functools.partial( diff --git a/homeassistant/components/mqtt/scene.py b/homeassistant/components/mqtt/scene.py index 9c4a212bd8e..cc911cc3431 100644 --- a/homeassistant/components/mqtt/scene.py +++ b/homeassistant/components/mqtt/scene.py @@ -80,7 +80,7 @@ async def async_setup_entry( """Set up MQTT scene through configuration.yaml and dynamically through MQTT discovery.""" # load and initialize platform config from configuration.yaml config_entry.async_on_unload( - await async_setup_platform_discovery(hass, scene.DOMAIN, PLATFORM_SCHEMA_MODERN) + await async_setup_platform_discovery(hass, scene.DOMAIN) ) # setup for discovery setup = functools.partial( diff --git a/homeassistant/components/mqtt/select.py b/homeassistant/components/mqtt/select.py index 994c11653b7..0d9f1411fd1 100644 --- a/homeassistant/components/mqtt/select.py +++ b/homeassistant/components/mqtt/select.py @@ -95,9 +95,7 @@ async def async_setup_entry( """Set up MQTT select through configuration.yaml and dynamically through MQTT discovery.""" # load and initialize platform config from configuration.yaml config_entry.async_on_unload( - await async_setup_platform_discovery( - hass, select.DOMAIN, PLATFORM_SCHEMA_MODERN - ) + await async_setup_platform_discovery(hass, select.DOMAIN) ) # setup for discovery setup = functools.partial( diff --git a/homeassistant/components/mqtt/sensor.py b/homeassistant/components/mqtt/sensor.py index f9e0b5151bb..672e22f632f 100644 --- a/homeassistant/components/mqtt/sensor.py +++ b/homeassistant/components/mqtt/sensor.py @@ -148,9 +148,7 @@ async def async_setup_entry( """Set up MQTT sensor through configuration.yaml and dynamically through MQTT discovery.""" # load and initialize platform config from configuration.yaml config_entry.async_on_unload( - await async_setup_platform_discovery( - hass, sensor.DOMAIN, PLATFORM_SCHEMA_MODERN - ) + await async_setup_platform_discovery(hass, sensor.DOMAIN) ) # setup for discovery setup = functools.partial( diff --git a/homeassistant/components/mqtt/siren.py b/homeassistant/components/mqtt/siren.py index fef2a4fb3dd..e7b91274f4f 100644 --- a/homeassistant/components/mqtt/siren.py +++ b/homeassistant/components/mqtt/siren.py @@ -144,7 +144,7 @@ async def async_setup_entry( """Set up MQTT siren through configuration.yaml and dynamically through MQTT discovery.""" # load and initialize platform config from configuration.yaml config_entry.async_on_unload( - await async_setup_platform_discovery(hass, siren.DOMAIN, PLATFORM_SCHEMA_MODERN) + await async_setup_platform_discovery(hass, siren.DOMAIN) ) # setup for discovery setup = functools.partial( diff --git a/homeassistant/components/mqtt/switch.py b/homeassistant/components/mqtt/switch.py index be7fc655e1e..dadd5f86f20 100644 --- a/homeassistant/components/mqtt/switch.py +++ b/homeassistant/components/mqtt/switch.py @@ -98,9 +98,7 @@ async def async_setup_entry( """Set up MQTT switch through configuration.yaml and dynamically through MQTT discovery.""" # load and initialize platform config from configuration.yaml config_entry.async_on_unload( - await async_setup_platform_discovery( - hass, switch.DOMAIN, PLATFORM_SCHEMA_MODERN - ) + await async_setup_platform_discovery(hass, switch.DOMAIN) ) # setup for discovery setup = functools.partial( diff --git a/homeassistant/components/mqtt/vacuum/__init__.py b/homeassistant/components/mqtt/vacuum/__init__.py index 206a15a024a..694e9530939 100644 --- a/homeassistant/components/mqtt/vacuum/__init__.py +++ b/homeassistant/components/mqtt/vacuum/__init__.py @@ -92,9 +92,7 @@ async def async_setup_entry( """Set up MQTT vacuum through configuration.yaml and dynamically through MQTT discovery.""" # load and initialize platform config from configuration.yaml config_entry.async_on_unload( - await async_setup_platform_discovery( - hass, vacuum.DOMAIN, PLATFORM_SCHEMA_MODERN - ) + await async_setup_platform_discovery(hass, vacuum.DOMAIN) ) # setup for discovery setup = functools.partial( diff --git a/tests/components/mqtt/test_humidifier.py b/tests/components/mqtt/test_humidifier.py index ea9a6edf0e3..cea5bfa1787 100644 --- a/tests/components/mqtt/test_humidifier.py +++ b/tests/components/mqtt/test_humidifier.py @@ -13,6 +13,7 @@ from homeassistant.components.humidifier import ( SERVICE_SET_HUMIDITY, SERVICE_SET_MODE, ) +from homeassistant.components.mqtt import CONFIG_SCHEMA from homeassistant.components.mqtt.humidifier import ( CONF_MODE_COMMAND_TOPIC, CONF_MODE_STATE_TOPIC, @@ -1277,3 +1278,15 @@ async def test_setup_manual_entity_from_yaml(hass, caplog, tmp_path): hass, caplog, tmp_path, platform, config ) assert hass.states.get(f"{platform}.test") is not None + + +async def test_config_schema_validation(hass): + """Test invalid platform options in the config schema do pass the config validation.""" + platform = humidifier.DOMAIN + config = copy.deepcopy(DEFAULT_CONFIG[platform]) + config["name"] = "test" + del config["platform"] + CONFIG_SCHEMA({DOMAIN: {platform: config}}) + CONFIG_SCHEMA({DOMAIN: {platform: [config]}}) + with pytest.raises(MultipleInvalid): + CONFIG_SCHEMA({"mqtt": {"humidifier": [{"bla": "bla"}]}}) diff --git a/tests/components/mqtt/test_init.py b/tests/components/mqtt/test_init.py index 861b50d2f71..e9625e0fdb2 100644 --- a/tests/components/mqtt/test_init.py +++ b/tests/components/mqtt/test_init.py @@ -14,7 +14,7 @@ import yaml from homeassistant import config as hass_config from homeassistant.components import mqtt -from homeassistant.components.mqtt import debug_info +from homeassistant.components.mqtt import CONFIG_SCHEMA, debug_info from homeassistant.components.mqtt.mixins import MQTT_ENTITY_DEVICE_INFO_SCHEMA from homeassistant.components.mqtt.models import ReceiveMessage from homeassistant.const import ( @@ -1362,56 +1362,47 @@ async def test_setup_override_configuration(hass, caplog, tmp_path): assert calls_username_password_set[0][1] == "somepassword" -async def test_setup_manual_mqtt_with_platform_key(hass, caplog, tmp_path): +@patch("homeassistant.components.mqtt.PLATFORMS", []) +async def test_setup_manual_mqtt_with_platform_key(hass, caplog): """Test set up a manual MQTT item with a platform key.""" config = {"platform": "mqtt", "name": "test", "command_topic": "test-topic"} - await help_test_setup_manual_entity_from_yaml( - hass, - caplog, - tmp_path, - "light", - config, - ) + with pytest.raises(AssertionError): + await help_test_setup_manual_entity_from_yaml(hass, "light", config) assert ( - "Invalid config for [light]: [platform] is an invalid option for [light]. " - "Check: light->platform. (See ?, line ?)" in caplog.text + "Invalid config for [mqtt]: [platform] is an invalid option for [mqtt]" + in caplog.text ) -async def test_setup_manual_mqtt_with_invalid_config(hass, caplog, tmp_path): +@patch("homeassistant.components.mqtt.PLATFORMS", []) +async def test_setup_manual_mqtt_with_invalid_config(hass, caplog): """Test set up a manual MQTT item with an invalid config.""" config = {"name": "test"} - await help_test_setup_manual_entity_from_yaml( - hass, - caplog, - tmp_path, - "light", - config, - ) + with pytest.raises(AssertionError): + await help_test_setup_manual_entity_from_yaml(hass, "light", config) assert ( - "Invalid config for [light]: required key not provided @ data['command_topic']." + "Invalid config for [mqtt]: required key not provided @ data['mqtt']['light'][0]['command_topic']." " Got None. (See ?, line ?)" in caplog.text ) -async def test_setup_manual_mqtt_empty_platform(hass, caplog, tmp_path): +@patch("homeassistant.components.mqtt.PLATFORMS", []) +async def test_setup_manual_mqtt_empty_platform(hass, caplog): """Test set up a manual MQTT platform without items.""" - config = None - await help_test_setup_manual_entity_from_yaml( - hass, - caplog, - tmp_path, - "light", - config, - ) + config = [] + await help_test_setup_manual_entity_from_yaml(hass, "light", config) assert "voluptuous.error.MultipleInvalid" not in caplog.text +@patch("homeassistant.components.mqtt.PLATFORMS", []) async def test_setup_mqtt_client_protocol(hass): """Test MQTT client protocol setup.""" entry = MockConfigEntry( domain=mqtt.DOMAIN, - data={mqtt.CONF_BROKER: "test-broker", mqtt.config.CONF_PROTOCOL: "3.1"}, + data={ + mqtt.CONF_BROKER: "test-broker", + mqtt.config_integration.CONF_PROTOCOL: "3.1", + }, ) with patch("paho.mqtt.client.Client") as mock_client: mock_client.on_connect(return_value=0) @@ -2617,3 +2608,10 @@ async def test_one_deprecation_warning_per_platform( ): count += 1 assert count == 1 + + +async def test_config_schema_validation(hass): + """Test invalid platform options in the config schema do not pass the config validation.""" + config = {"mqtt": {"sensor": [{"some_illegal_topic": "mystate/topic/path"}]}} + with pytest.raises(vol.MultipleInvalid): + CONFIG_SCHEMA(config) From 65c1d4812a48e876a8f63fcb3d76e679c419bb27 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Fri, 17 Jun 2022 21:57:44 -0500 Subject: [PATCH 11/20] Fix calling permanent off with nexia (#73623) * Fix calling permanent off with nexia Changelog: https://github.com/bdraco/nexia/compare/1.0.1...1.0.2 Fixes #73610 * one more --- homeassistant/components/nexia/climate.py | 4 +++- homeassistant/components/nexia/manifest.json | 2 +- homeassistant/components/nexia/switch.py | 6 +++++- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 5 files changed, 11 insertions(+), 5 deletions(-) diff --git a/homeassistant/components/nexia/climate.py b/homeassistant/components/nexia/climate.py index 20fcf5c6b85..33ad91e1561 100644 --- a/homeassistant/components/nexia/climate.py +++ b/homeassistant/components/nexia/climate.py @@ -391,7 +391,9 @@ class NexiaZone(NexiaThermostatZoneEntity, ClimateEntity): async def async_set_hvac_mode(self, hvac_mode: HVACMode) -> None: """Set the system mode (Auto, Heat_Cool, Cool, Heat, etc).""" - if hvac_mode == HVACMode.AUTO: + if hvac_mode == HVACMode.OFF: + await self._zone.call_permanent_off() + elif hvac_mode == HVACMode.AUTO: await self._zone.call_return_to_schedule() await self._zone.set_mode(mode=OPERATION_MODE_AUTO) else: diff --git a/homeassistant/components/nexia/manifest.json b/homeassistant/components/nexia/manifest.json index f9ca21d9e0b..4bae2d9a15d 100644 --- a/homeassistant/components/nexia/manifest.json +++ b/homeassistant/components/nexia/manifest.json @@ -1,7 +1,7 @@ { "domain": "nexia", "name": "Nexia/American Standard/Trane", - "requirements": ["nexia==1.0.1"], + "requirements": ["nexia==1.0.2"], "codeowners": ["@bdraco"], "documentation": "https://www.home-assistant.io/integrations/nexia", "config_flow": true, diff --git a/homeassistant/components/nexia/switch.py b/homeassistant/components/nexia/switch.py index 380fea8c4a0..e242032c947 100644 --- a/homeassistant/components/nexia/switch.py +++ b/homeassistant/components/nexia/switch.py @@ -3,6 +3,7 @@ from __future__ import annotations from typing import Any +from nexia.const import OPERATION_MODE_OFF from nexia.home import NexiaHome from nexia.thermostat import NexiaThermostat from nexia.zone import NexiaThermostatZone @@ -58,7 +59,10 @@ class NexiaHoldSwitch(NexiaThermostatZoneEntity, SwitchEntity): async def async_turn_on(self, **kwargs: Any) -> None: """Enable permanent hold.""" - await self._zone.call_permanent_hold() + if self._zone.get_current_mode() == OPERATION_MODE_OFF: + await self._zone.call_permanent_off() + else: + await self._zone.call_permanent_hold() self._signal_zone_update() async def async_turn_off(self, **kwargs: Any) -> None: diff --git a/requirements_all.txt b/requirements_all.txt index 20dd9ae64ff..dbffe63f9fa 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1080,7 +1080,7 @@ nettigo-air-monitor==1.2.4 neurio==0.3.1 # homeassistant.components.nexia -nexia==1.0.1 +nexia==1.0.2 # homeassistant.components.nextcloud nextcloudmonitor==1.1.0 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index fe7002d422a..73d4d24ca01 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -745,7 +745,7 @@ netmap==0.7.0.2 nettigo-air-monitor==1.2.4 # homeassistant.components.nexia -nexia==1.0.1 +nexia==1.0.2 # homeassistant.components.discord nextcord==2.0.0a8 From 9e4ee0d36d11a8b4ad58710cca9d6f3c48eaf1e5 Mon Sep 17 00:00:00 2001 From: Steven Looman Date: Fri, 17 Jun 2022 18:26:25 +0200 Subject: [PATCH 12/20] Don't verify ssl certificates for ssdp/upnp devices (#73647) --- homeassistant/components/ssdp/__init__.py | 2 +- homeassistant/components/upnp/device.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/ssdp/__init__.py b/homeassistant/components/ssdp/__init__.py index e854472f21f..d221cb162f4 100644 --- a/homeassistant/components/ssdp/__init__.py +++ b/homeassistant/components/ssdp/__init__.py @@ -351,7 +351,7 @@ class Scanner: async def async_start(self) -> None: """Start the scanners.""" - session = async_get_clientsession(self.hass) + session = async_get_clientsession(self.hass, verify_ssl=False) requester = AiohttpSessionRequester(session, True, 10) self._description_cache = DescriptionCache(requester) diff --git a/homeassistant/components/upnp/device.py b/homeassistant/components/upnp/device.py index 334d870939f..3a688b8571d 100644 --- a/homeassistant/components/upnp/device.py +++ b/homeassistant/components/upnp/device.py @@ -48,7 +48,7 @@ async def async_get_mac_address_from_host(hass: HomeAssistant, host: str) -> str async def async_create_device(hass: HomeAssistant, ssdp_location: str) -> Device: """Create UPnP/IGD device.""" - session = async_get_clientsession(hass) + session = async_get_clientsession(hass, verify_ssl=False) requester = AiohttpSessionRequester(session, with_sleep=True, timeout=20) factory = UpnpFactory(requester, disable_state_variable_validation=True) From eda2c8cb8f9f39817b3dceaaca8549b6d5e299d6 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Fri, 17 Jun 2022 14:03:42 -0500 Subject: [PATCH 13/20] Retry on SenseAPIException during sense config entry setup (#73651) --- homeassistant/components/sense/__init__.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/homeassistant/components/sense/__init__.py b/homeassistant/components/sense/__init__.py index 2e0ed4622e8..e938f7132e0 100644 --- a/homeassistant/components/sense/__init__.py +++ b/homeassistant/components/sense/__init__.py @@ -5,6 +5,7 @@ import logging from sense_energy import ( ASyncSenseable, + SenseAPIException, SenseAuthenticationException, SenseMFARequiredException, ) @@ -84,6 +85,8 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: raise ConfigEntryNotReady( str(err) or "Timed out during authentication" ) from err + except SenseAPIException as err: + raise ConfigEntryNotReady(str(err)) from err sense_devices_data = SenseDevicesData() try: From 14c11cd13a96d310a0fe799dafb22920ae7362b0 Mon Sep 17 00:00:00 2001 From: Max Gashkov Date: Mon, 20 Jun 2022 16:05:28 +0900 Subject: [PATCH 14/20] Fix AmbiClimate services definition (#73668) --- homeassistant/components/ambiclimate/services.yaml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/homeassistant/components/ambiclimate/services.yaml b/homeassistant/components/ambiclimate/services.yaml index f75857e4d2e..e5532ae82f9 100644 --- a/homeassistant/components/ambiclimate/services.yaml +++ b/homeassistant/components/ambiclimate/services.yaml @@ -5,7 +5,7 @@ set_comfort_mode: description: > Enable comfort mode on your AC. fields: - Name: + name: description: > String with device name. required: true @@ -18,14 +18,14 @@ send_comfort_feedback: description: > Send feedback for comfort mode. fields: - Name: + name: description: > String with device name. required: true example: Bedroom selector: text: - Value: + value: description: > Send any of the following comfort values: too_hot, too_warm, bit_warm, comfortable, bit_cold, too_cold, freezing required: true @@ -38,14 +38,14 @@ set_temperature_mode: description: > Enable temperature mode on your AC. fields: - Name: + name: description: > String with device name. required: true example: Bedroom selector: text: - Value: + value: description: > Target value in celsius required: true From d211399056ea21c29b9170cfed71fe66e3110dcc Mon Sep 17 00:00:00 2001 From: micha91 Date: Mon, 20 Jun 2022 10:36:04 +0200 Subject: [PATCH 15/20] Update aiomusiccast (#73694) --- homeassistant/components/yamaha_musiccast/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/yamaha_musiccast/manifest.json b/homeassistant/components/yamaha_musiccast/manifest.json index 86115e77988..8c0b55def69 100644 --- a/homeassistant/components/yamaha_musiccast/manifest.json +++ b/homeassistant/components/yamaha_musiccast/manifest.json @@ -3,7 +3,7 @@ "name": "MusicCast", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/yamaha_musiccast", - "requirements": ["aiomusiccast==0.14.3"], + "requirements": ["aiomusiccast==0.14.4"], "ssdp": [ { "manufacturer": "Yamaha Corporation" diff --git a/requirements_all.txt b/requirements_all.txt index dbffe63f9fa..47be523361f 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -196,7 +196,7 @@ aiolyric==1.0.8 aiomodernforms==0.1.8 # homeassistant.components.yamaha_musiccast -aiomusiccast==0.14.3 +aiomusiccast==0.14.4 # homeassistant.components.nanoleaf aionanoleaf==0.2.0 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 73d4d24ca01..aa7eaf87853 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -168,7 +168,7 @@ aiolyric==1.0.8 aiomodernforms==0.1.8 # homeassistant.components.yamaha_musiccast -aiomusiccast==0.14.3 +aiomusiccast==0.14.4 # homeassistant.components.nanoleaf aionanoleaf==0.2.0 From 5c71de8055f0516ac0fc2656f3b72989e230bc3f Mon Sep 17 00:00:00 2001 From: Robert Svensson Date: Mon, 20 Jun 2022 10:31:19 +0200 Subject: [PATCH 16/20] Fix CSRF token for UniFi (#73716) Bump aiounifi to v32 --- homeassistant/components/unifi/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/unifi/manifest.json b/homeassistant/components/unifi/manifest.json index e779dca22f0..d481f0d0fc4 100644 --- a/homeassistant/components/unifi/manifest.json +++ b/homeassistant/components/unifi/manifest.json @@ -3,7 +3,7 @@ "name": "UniFi Network", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/unifi", - "requirements": ["aiounifi==31"], + "requirements": ["aiounifi==32"], "codeowners": ["@Kane610"], "quality_scale": "platinum", "ssdp": [ diff --git a/requirements_all.txt b/requirements_all.txt index 47be523361f..c42bbe6c076 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -256,7 +256,7 @@ aiosyncthing==0.5.1 aiotractive==0.5.4 # homeassistant.components.unifi -aiounifi==31 +aiounifi==32 # homeassistant.components.vlc_telnet aiovlc==0.1.0 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index aa7eaf87853..a1efb0f1941 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -225,7 +225,7 @@ aiosyncthing==0.5.1 aiotractive==0.5.4 # homeassistant.components.unifi -aiounifi==31 +aiounifi==32 # homeassistant.components.vlc_telnet aiovlc==0.1.0 From 29f8493a0654f2279bdd21fe547bebc9a4be569f Mon Sep 17 00:00:00 2001 From: Tom Harris Date: Wed, 22 Jun 2022 04:04:11 -0400 Subject: [PATCH 17/20] Insteon bug fixes (#73791) --- homeassistant/components/insteon/api/properties.py | 9 ++++----- homeassistant/components/insteon/manifest.json | 4 ++-- homeassistant/components/insteon/utils.py | 5 ++--- requirements_all.txt | 4 ++-- requirements_test_all.txt | 4 ++-- tests/components/insteon/test_api_properties.py | 4 ++-- 6 files changed, 14 insertions(+), 16 deletions(-) diff --git a/homeassistant/components/insteon/api/properties.py b/homeassistant/components/insteon/api/properties.py index 47def71c1ab..8e697a88459 100644 --- a/homeassistant/components/insteon/api/properties.py +++ b/homeassistant/components/insteon/api/properties.py @@ -99,8 +99,6 @@ def get_properties(device: Device, show_advanced=False): continue prop_schema = get_schema(prop, name, device.groups) - if name == "momentary_delay": - print(prop_schema) if prop_schema is None: continue schema[name] = prop_schema @@ -216,7 +214,7 @@ async def websocket_write_properties( result = await device.async_write_config() await devices.async_save(workdir=hass.config.config_dir) - if result != ResponseStatus.SUCCESS: + if result not in [ResponseStatus.SUCCESS, ResponseStatus.RUN_ON_WAKE]: connection.send_message( websocket_api.error_message( msg[ID], "write_failed", "properties not written to device" @@ -244,9 +242,10 @@ async def websocket_load_properties( notify_device_not_found(connection, msg, INSTEON_DEVICE_NOT_FOUND) return - result, _ = await device.async_read_config(read_aldb=False) + result = await device.async_read_config(read_aldb=False) await devices.async_save(workdir=hass.config.config_dir) - if result != ResponseStatus.SUCCESS: + + if result not in [ResponseStatus.SUCCESS, ResponseStatus.RUN_ON_WAKE]: connection.send_message( websocket_api.error_message( msg[ID], "load_failed", "properties not loaded from device" diff --git a/homeassistant/components/insteon/manifest.json b/homeassistant/components/insteon/manifest.json index c69f4f2cdf5..1be077a6b38 100644 --- a/homeassistant/components/insteon/manifest.json +++ b/homeassistant/components/insteon/manifest.json @@ -4,8 +4,8 @@ "documentation": "https://www.home-assistant.io/integrations/insteon", "dependencies": ["http", "websocket_api"], "requirements": [ - "pyinsteon==1.1.0", - "insteon-frontend-home-assistant==0.1.0" + "pyinsteon==1.1.1", + "insteon-frontend-home-assistant==0.1.1" ], "codeowners": ["@teharris1"], "dhcp": [ diff --git a/homeassistant/components/insteon/utils.py b/homeassistant/components/insteon/utils.py index e8e34ee8e62..09375e7827a 100644 --- a/homeassistant/components/insteon/utils.py +++ b/homeassistant/components/insteon/utils.py @@ -196,9 +196,8 @@ def async_register_services(hass): for address in devices: device = devices[address] if device != devices.modem and device.cat != 0x03: - await device.aldb.async_load( - refresh=reload, callback=async_srv_save_devices - ) + await device.aldb.async_load(refresh=reload) + await async_srv_save_devices() async def async_srv_save_devices(): """Write the Insteon device configuration to file.""" diff --git a/requirements_all.txt b/requirements_all.txt index c42bbe6c076..b52f43924d9 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -885,7 +885,7 @@ influxdb-client==1.24.0 influxdb==5.3.1 # homeassistant.components.insteon -insteon-frontend-home-assistant==0.1.0 +insteon-frontend-home-assistant==0.1.1 # homeassistant.components.intellifire intellifire4py==1.0.2 @@ -1556,7 +1556,7 @@ pyialarmxr-homeassistant==1.0.18 pyicloud==1.0.0 # homeassistant.components.insteon -pyinsteon==1.1.0 +pyinsteon==1.1.1 # homeassistant.components.intesishome pyintesishome==1.7.6 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index a1efb0f1941..1f70484f554 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -628,7 +628,7 @@ influxdb-client==1.24.0 influxdb==5.3.1 # homeassistant.components.insteon -insteon-frontend-home-assistant==0.1.0 +insteon-frontend-home-assistant==0.1.1 # homeassistant.components.intellifire intellifire4py==1.0.2 @@ -1044,7 +1044,7 @@ pyialarmxr-homeassistant==1.0.18 pyicloud==1.0.0 # homeassistant.components.insteon -pyinsteon==1.1.0 +pyinsteon==1.1.1 # homeassistant.components.ipma pyipma==2.0.5 diff --git a/tests/components/insteon/test_api_properties.py b/tests/components/insteon/test_api_properties.py index 7211402e343..9088b23f42a 100644 --- a/tests/components/insteon/test_api_properties.py +++ b/tests/components/insteon/test_api_properties.py @@ -401,7 +401,7 @@ async def test_load_properties(hass, hass_ws_client, kpl_properties_data): ) device = devices["33.33.33"] - device.async_read_config = AsyncMock(return_value=(1, 1)) + device.async_read_config = AsyncMock(return_value=1) with patch.object(insteon.api.properties, "devices", devices): await ws_client.send_json( {ID: 2, TYPE: "insteon/properties/load", DEVICE_ADDRESS: "33.33.33"} @@ -418,7 +418,7 @@ async def test_load_properties_failure(hass, hass_ws_client, kpl_properties_data ) device = devices["33.33.33"] - device.async_read_config = AsyncMock(return_value=(0, 0)) + device.async_read_config = AsyncMock(return_value=0) with patch.object(insteon.api.properties, "devices", devices): await ws_client.send_json( {ID: 2, TYPE: "insteon/properties/load", DEVICE_ADDRESS: "33.33.33"} From 30383e0102fb10d744830f767bf8b74faba6bb1b Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Wed, 22 Jun 2022 09:54:35 +0200 Subject: [PATCH 18/20] Fix Plugwise migration error (#73812) --- homeassistant/components/plugwise/gateway.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/plugwise/gateway.py b/homeassistant/components/plugwise/gateway.py index 7eb2b1371d5..afa7451021e 100644 --- a/homeassistant/components/plugwise/gateway.py +++ b/homeassistant/components/plugwise/gateway.py @@ -113,7 +113,7 @@ def migrate_sensor_entities( # Migrating opentherm_outdoor_temperature to opentherm_outdoor_air_temperature sensor for device_id, device in coordinator.data.devices.items(): - if device["dev_class"] != "heater_central": + if device.get("dev_class") != "heater_central": continue old_unique_id = f"{device_id}-outdoor_temperature" From 505c4b0f770f60825ca7b2fce424bda0e37e55db Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Wed, 22 Jun 2022 11:50:34 +0200 Subject: [PATCH 19/20] Fix MQTT tests for RC --- tests/components/mqtt/test_init.py | 35 +++++++++++++++++++++--------- 1 file changed, 25 insertions(+), 10 deletions(-) diff --git a/tests/components/mqtt/test_init.py b/tests/components/mqtt/test_init.py index e9625e0fdb2..eb392f8c4f8 100644 --- a/tests/components/mqtt/test_init.py +++ b/tests/components/mqtt/test_init.py @@ -1362,35 +1362,50 @@ async def test_setup_override_configuration(hass, caplog, tmp_path): assert calls_username_password_set[0][1] == "somepassword" -@patch("homeassistant.components.mqtt.PLATFORMS", []) -async def test_setup_manual_mqtt_with_platform_key(hass, caplog): +async def test_setup_manual_mqtt_with_platform_key(hass, caplog, tmp_path): """Test set up a manual MQTT item with a platform key.""" config = {"platform": "mqtt", "name": "test", "command_topic": "test-topic"} with pytest.raises(AssertionError): - await help_test_setup_manual_entity_from_yaml(hass, "light", config) + await help_test_setup_manual_entity_from_yaml( + hass, + caplog, + tmp_path, + "light", + config, + ) assert ( "Invalid config for [mqtt]: [platform] is an invalid option for [mqtt]" in caplog.text ) -@patch("homeassistant.components.mqtt.PLATFORMS", []) -async def test_setup_manual_mqtt_with_invalid_config(hass, caplog): +async def test_setup_manual_mqtt_with_invalid_config(hass, caplog, tmp_path): """Test set up a manual MQTT item with an invalid config.""" config = {"name": "test"} with pytest.raises(AssertionError): - await help_test_setup_manual_entity_from_yaml(hass, "light", config) + await help_test_setup_manual_entity_from_yaml( + hass, + caplog, + tmp_path, + "light", + config, + ) assert ( "Invalid config for [mqtt]: required key not provided @ data['mqtt']['light'][0]['command_topic']." " Got None. (See ?, line ?)" in caplog.text ) -@patch("homeassistant.components.mqtt.PLATFORMS", []) -async def test_setup_manual_mqtt_empty_platform(hass, caplog): +async def test_setup_manual_mqtt_empty_platform(hass, caplog, tmp_path): """Test set up a manual MQTT platform without items.""" - config = [] - await help_test_setup_manual_entity_from_yaml(hass, "light", config) + config = None + await help_test_setup_manual_entity_from_yaml( + hass, + caplog, + tmp_path, + "light", + config, + ) assert "voluptuous.error.MultipleInvalid" not in caplog.text From 08ff99a1e87e444a1dd70bebadb06d2a66d6caec Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Wed, 22 Jun 2022 11:50:42 +0200 Subject: [PATCH 20/20] Bumped version to 2022.6.7 --- homeassistant/const.py | 2 +- setup.cfg | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/const.py b/homeassistant/const.py index 8f8a8008457..3bcf2a603b4 100644 --- a/homeassistant/const.py +++ b/homeassistant/const.py @@ -7,7 +7,7 @@ from .backports.enum import StrEnum MAJOR_VERSION: Final = 2022 MINOR_VERSION: Final = 6 -PATCH_VERSION: Final = "6" +PATCH_VERSION: Final = "7" __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/setup.cfg b/setup.cfg index 17e4901b2ff..ab5ab807491 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,5 +1,5 @@ [metadata] -version = 2022.6.6 +version = 2022.6.7 url = https://www.home-assistant.io/ [options]