From 8bd73709a7a9028b6e23c4add8448b526c90f9b3 Mon Sep 17 00:00:00 2001 From: puddly <32534428+puddly@users.noreply.github.com> Date: Tue, 13 Jul 2021 13:31:17 -0400 Subject: [PATCH 01/20] Update ZHA to support zigpy 0.34.0 device initialization (#52610) * Handle `None` node descriptors * Skip loading uninitialized devices * Fix unit test incorrectly handling unset cluster `ep_attribute` * Revert filtering devices by status during startup --- homeassistant/components/zha/core/device.py | 39 ++++++++++++++------- tests/components/zha/common.py | 17 +++++---- 2 files changed, 34 insertions(+), 22 deletions(-) diff --git a/homeassistant/components/zha/core/device.py b/homeassistant/components/zha/core/device.py index 0c572bfba8a..c6166419e39 100644 --- a/homeassistant/components/zha/core/device.py +++ b/homeassistant/components/zha/core/device.py @@ -183,11 +183,12 @@ class ZHADevice(LogMixin): return self._zigpy_device.model @property - def manufacturer_code(self): + def manufacturer_code(self) -> int | None: """Return the manufacturer code for the device.""" - if self._zigpy_device.node_desc.is_valid: - return self._zigpy_device.node_desc.manufacturer_code - return None + if self._zigpy_device.node_desc is None: + return None + + return self._zigpy_device.node_desc.manufacturer_code @property def nwk(self): @@ -210,17 +211,20 @@ class ZHADevice(LogMixin): return self._zigpy_device.last_seen @property - def is_mains_powered(self): + def is_mains_powered(self) -> bool | None: """Return true if device is mains powered.""" + if self._zigpy_device.node_desc is None: + return None + return self._zigpy_device.node_desc.is_mains_powered @property - def device_type(self): + def device_type(self) -> str: """Return the logical device type for the device.""" - node_descriptor = self._zigpy_device.node_desc - return ( - node_descriptor.logical_type.name if node_descriptor.is_valid else UNKNOWN - ) + if self._zigpy_device.node_desc is None: + return UNKNOWN + + return self._zigpy_device.node_desc.logical_type.name @property def power_source(self): @@ -230,18 +234,27 @@ class ZHADevice(LogMixin): ) @property - def is_router(self): + def is_router(self) -> bool | None: """Return true if this is a routing capable device.""" + if self._zigpy_device.node_desc is None: + return None + return self._zigpy_device.node_desc.is_router @property - def is_coordinator(self): + def is_coordinator(self) -> bool | None: """Return true if this device represents the coordinator.""" + if self._zigpy_device.node_desc is None: + return None + return self._zigpy_device.node_desc.is_coordinator @property - def is_end_device(self): + def is_end_device(self) -> bool | None: """Return true if this device is an end device.""" + if self._zigpy_device.node_desc is None: + return None + return self._zigpy_device.node_desc.is_end_device @property diff --git a/tests/components/zha/common.py b/tests/components/zha/common.py index eb65cc4fd2e..5180e9dbc07 100644 --- a/tests/components/zha/common.py +++ b/tests/components/zha/common.py @@ -3,8 +3,8 @@ import asyncio import time from unittest.mock import AsyncMock, Mock -from zigpy.device import Device as zigpy_dev -from zigpy.endpoint import Endpoint as zigpy_ep +import zigpy.device as zigpy_dev +import zigpy.endpoint as zigpy_ep import zigpy.profiles.zha import zigpy.types import zigpy.zcl @@ -27,7 +27,7 @@ class FakeEndpoint: self.out_clusters = {} self._cluster_attr = {} self.member_of = {} - self.status = 1 + self.status = zigpy_ep.Status.ZDO_INIT self.manufacturer = manufacturer self.model = model self.profile_id = zigpy.profiles.zha.PROFILE_ID @@ -57,7 +57,7 @@ class FakeEndpoint: @property def __class__(self): """Fake being Zigpy endpoint.""" - return zigpy_ep + return zigpy_ep.Endpoint @property def unique_id(self): @@ -65,8 +65,8 @@ class FakeEndpoint: return self.device.ieee, self.endpoint_id -FakeEndpoint.add_to_group = zigpy_ep.add_to_group -FakeEndpoint.remove_from_group = zigpy_ep.remove_from_group +FakeEndpoint.add_to_group = zigpy_ep.Endpoint.add_to_group +FakeEndpoint.remove_from_group = zigpy_ep.Endpoint.remove_from_group def patch_cluster(cluster): @@ -125,12 +125,11 @@ class FakeDevice: self.lqi = 255 self.rssi = 8 self.last_seen = time.time() - self.status = 2 + self.status = zigpy_dev.Status.ENDPOINTS_INIT self.initializing = False self.skip_configuration = False self.manufacturer = manufacturer self.model = model - self.node_desc = zigpy.zdo.types.NodeDescriptor() self.remove_from_group = AsyncMock() if node_desc is None: node_desc = b"\x02@\x807\x10\x7fd\x00\x00*d\x00\x00" @@ -138,7 +137,7 @@ class FakeDevice: self.neighbors = [] -FakeDevice.add_to_group = zigpy_dev.add_to_group +FakeDevice.add_to_group = zigpy_dev.Device.add_to_group def get_zha_gateway(hass): From 29fb5e0cb258dc31f419656bcebe1082e020002e Mon Sep 17 00:00:00 2001 From: jan iversen Date: Wed, 14 Jul 2021 11:00:16 +0200 Subject: [PATCH 02/20] copy() --> deepcopy(). (#52794) --- homeassistant/components/modbus/modbus.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/modbus/modbus.py b/homeassistant/components/modbus/modbus.py index 0826f4d5794..f2b033bec3e 100644 --- a/homeassistant/components/modbus/modbus.py +++ b/homeassistant/components/modbus/modbus.py @@ -1,5 +1,6 @@ """Support for Modbus.""" import asyncio +from copy import deepcopy import logging from pymodbus.client.sync import ModbusSerialClient, ModbusTcpClient, ModbusUdpClient @@ -196,7 +197,7 @@ class ModbusHub: self._config_name = client_config[CONF_NAME] self._config_type = client_config[CONF_TYPE] self._config_delay = client_config[CONF_DELAY] - self._pb_call = PYMODBUS_CALL.copy() + self._pb_call = deepcopy(PYMODBUS_CALL) self._pb_class = { CONF_SERIAL: ModbusSerialClient, CONF_TCP: ModbusTcpClient, From 1d67e6653801a4d2e310c19ade7ed25afbc3c98e Mon Sep 17 00:00:00 2001 From: jan iversen Date: Tue, 13 Jul 2021 21:45:42 +0200 Subject: [PATCH 03/20] only allow one active call in each platform. (#52823) --- .coveragerc | 3 +++ homeassistant/components/modbus/base_platform.py | 6 ++++++ homeassistant/components/modbus/binary_sensor.py | 6 ++++++ homeassistant/components/modbus/climate.py | 7 ++++++- homeassistant/components/modbus/cover.py | 5 +++++ 5 files changed, 26 insertions(+), 1 deletion(-) diff --git a/.coveragerc b/.coveragerc index 06f6bca0eec..dc35999768f 100644 --- a/.coveragerc +++ b/.coveragerc @@ -633,6 +633,9 @@ omit = homeassistant/components/mitemp_bt/sensor.py homeassistant/components/mjpeg/camera.py homeassistant/components/mochad/* + homeassistant/components/modbus/base_platform.py + homeassistant/components/modbus/binary_sensor.py + homeassistant/components/modbus/cover.py homeassistant/components/modbus/climate.py homeassistant/components/modbus/modbus.py homeassistant/components/modem_callerid/sensor.py diff --git a/homeassistant/components/modbus/base_platform.py b/homeassistant/components/modbus/base_platform.py index ed2f6e69863..4ff7adbdd29 100644 --- a/homeassistant/components/modbus/base_platform.py +++ b/homeassistant/components/modbus/base_platform.py @@ -51,6 +51,7 @@ class BasePlatform(Entity): self._value = None self._available = True self._scan_interval = int(entry[CONF_SCAN_INTERVAL]) + self._call_active = False @abstractmethod async def async_update(self, now=None): @@ -160,9 +161,14 @@ class BaseSwitch(BasePlatform, RestoreEntity): self.async_write_ha_state() return + # do not allow multiple active calls to the same platform + if self._call_active: + return + self._call_active = True result = await self._hub.async_pymodbus_call( self._slave, self._verify_address, 1, self._verify_type ) + self._call_active = False if result is None: self._available = False self.async_write_ha_state() diff --git a/homeassistant/components/modbus/binary_sensor.py b/homeassistant/components/modbus/binary_sensor.py index bc586e2f24d..0188210be8a 100644 --- a/homeassistant/components/modbus/binary_sensor.py +++ b/homeassistant/components/modbus/binary_sensor.py @@ -54,9 +54,15 @@ class ModbusBinarySensor(BasePlatform, RestoreEntity, BinarySensorEntity): async def async_update(self, now=None): """Update the state of the sensor.""" + + # do not allow multiple active calls to the same platform + if self._call_active: + return + self._call_active = True result = await self._hub.async_pymodbus_call( self._slave, self._address, 1, self._input_type ) + self._call_active = False if result is None: self._available = False self.async_write_ha_state() diff --git a/homeassistant/components/modbus/climate.py b/homeassistant/components/modbus/climate.py index 5c99ac86d6c..42430f609cf 100644 --- a/homeassistant/components/modbus/climate.py +++ b/homeassistant/components/modbus/climate.py @@ -185,13 +185,18 @@ class ModbusThermostat(BasePlatform, RestoreEntity, ClimateEntity): """Update Target & Current Temperature.""" # remark "now" is a dummy parameter to avoid problems with # async_track_time_interval + + # do not allow multiple active calls to the same platform + if self._call_active: + return + self._call_active = True self._target_temperature = await self._async_read_register( CALL_TYPE_REGISTER_HOLDING, self._target_temperature_register ) self._current_temperature = await self._async_read_register( self._input_type, self._address ) - + self._call_active = False self.async_write_ha_state() async def _async_read_register(self, register_type, register) -> float | None: diff --git a/homeassistant/components/modbus/cover.py b/homeassistant/components/modbus/cover.py index 88c8fd77ae8..bd150434dc1 100644 --- a/homeassistant/components/modbus/cover.py +++ b/homeassistant/components/modbus/cover.py @@ -149,9 +149,14 @@ class ModbusCover(BasePlatform, CoverEntity, RestoreEntity): """Update the state of the cover.""" # remark "now" is a dummy parameter to avoid problems with # async_track_time_interval + # do not allow multiple active calls to the same platform + if self._call_active: + return + self._call_active = True result = await self._hub.async_pymodbus_call( self._slave, self._address, 1, self._input_type ) + self._call_active = False if result is None: self._available = False self.async_write_ha_state() From 47b5866d85d8e6cd82b56c7078b9698c75d04468 Mon Sep 17 00:00:00 2001 From: Doug Hoffman Date: Wed, 14 Jul 2021 04:45:47 -0400 Subject: [PATCH 04/20] Bump pyatv to 0.8.1 (#52849) * Bump pyatv to 0.8.1 * Update apple_tv tests for new create_session location * Update test_user_adds_unusable_device to try device with no services pyatv >=0.8.0 considers AirPlay a valid service and no longer fails under the previous conditions. --- homeassistant/components/apple_tv/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- tests/components/apple_tv/conftest.py | 13 +++++-------- tests/components/apple_tv/test_config_flow.py | 6 +++--- 5 files changed, 11 insertions(+), 14 deletions(-) diff --git a/homeassistant/components/apple_tv/manifest.json b/homeassistant/components/apple_tv/manifest.json index 963cbb9be33..d4eb322f4d7 100644 --- a/homeassistant/components/apple_tv/manifest.json +++ b/homeassistant/components/apple_tv/manifest.json @@ -3,7 +3,7 @@ "name": "Apple TV", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/apple_tv", - "requirements": ["pyatv==0.7.7"], + "requirements": ["pyatv==0.8.1"], "zeroconf": ["_mediaremotetv._tcp.local.", "_touch-able._tcp.local."], "after_dependencies": ["discovery"], "codeowners": ["@postlund"], diff --git a/requirements_all.txt b/requirements_all.txt index 8634b6957ea..cd4092d7c30 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1318,7 +1318,7 @@ pyatmo==5.2.0 pyatome==0.1.1 # homeassistant.components.apple_tv -pyatv==0.7.7 +pyatv==0.8.1 # homeassistant.components.bbox pybbox==0.0.5-alpha diff --git a/requirements_test_all.txt b/requirements_test_all.txt index b9799999a79..2ec48ca38b7 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -743,7 +743,7 @@ pyatag==0.3.5.3 pyatmo==5.2.0 # homeassistant.components.apple_tv -pyatv==0.7.7 +pyatv==0.8.1 # homeassistant.components.blackbird pyblackbird==0.5 diff --git a/tests/components/apple_tv/conftest.py b/tests/components/apple_tv/conftest.py index db543007fb2..f07fa7d70bb 100644 --- a/tests/components/apple_tv/conftest.py +++ b/tests/components/apple_tv/conftest.py @@ -2,7 +2,8 @@ from unittest.mock import patch -from pyatv import conf, net +from pyatv import conf +from pyatv.support.http import create_session import pytest from .common import MockPairingHandler, create_conf @@ -39,7 +40,7 @@ def pairing(): async def _pair(config, protocol, loop, session=None, **kwargs): handler = MockPairingHandler( - await net.create_session(session), config.get_service(protocol) + await create_session(session), config.get_service(protocol) ) handler.always_fail = mock_pair.always_fail return handler @@ -121,11 +122,7 @@ def dmap_device_with_credentials(mock_scan): @pytest.fixture -def airplay_device(mock_scan): +def device_with_no_services(mock_scan): """Mock pyatv.scan.""" - mock_scan.result.append( - create_conf( - "127.0.0.1", "AirPlay Device", conf.AirPlayService("airplayid", port=7777) - ) - ) + mock_scan.result.append(create_conf("127.0.0.1", "Invalid Device")) yield mock_scan diff --git a/tests/components/apple_tv/test_config_flow.py b/tests/components/apple_tv/test_config_flow.py index 615a1f404f5..45edaa36251 100644 --- a/tests/components/apple_tv/test_config_flow.py +++ b/tests/components/apple_tv/test_config_flow.py @@ -236,15 +236,15 @@ async def test_user_adds_existing_device(hass, mrp_device): assert result2["errors"] == {"base": "already_configured"} -async def test_user_adds_unusable_device(hass, airplay_device): - """Test that it is not possible to add pure AirPlay device.""" +async def test_user_adds_unusable_device(hass, device_with_no_services): + """Test that it is not possible to add device with no services.""" result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": config_entries.SOURCE_USER} ) result2 = await hass.config_entries.flow.async_configure( result["flow_id"], - {"device_input": "AirPlay Device"}, + {"device_input": "Invalid Device"}, ) assert result2["type"] == data_entry_flow.RESULT_TYPE_FORM assert result2["errors"] == {"base": "no_usable_service"} From d2e82edb58e8551c2a6051b5dbf25fee13dae613 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 12 Jul 2021 05:25:16 -1000 Subject: [PATCH 05/20] Handle dhcp packets without a hostname (#52882) * Handle dhcp packets without a hostname - Since some integrations only match on OUI we want to make sure they still see devices that do not request a specific hostname * Update tests/components/dhcp/test_init.py * Update homeassistant/components/dhcp/__init__.py Co-authored-by: Franck Nijhof Co-authored-by: Franck Nijhof --- homeassistant/components/dhcp/__init__.py | 8 +-- tests/components/dhcp/test_init.py | 64 +++++++++++++++++++++++ 2 files changed, 68 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/dhcp/__init__.py b/homeassistant/components/dhcp/__init__.py index 5d0b31c8788..7003038593b 100644 --- a/homeassistant/components/dhcp/__init__.py +++ b/homeassistant/components/dhcp/__init__.py @@ -248,10 +248,10 @@ class DeviceTrackerWatcher(WatcherBase): return ip_address = attributes.get(ATTR_IP) - hostname = attributes.get(ATTR_HOST_NAME) + hostname = attributes.get(ATTR_HOST_NAME, "") mac_address = attributes.get(ATTR_MAC) - if ip_address is None or hostname is None or mac_address is None: + if ip_address is None or mac_address is None: return self.process_client(ip_address, hostname, _format_mac(mac_address)) @@ -328,10 +328,10 @@ class DHCPWatcher(WatcherBase): return ip_address = _decode_dhcp_option(options, REQUESTED_ADDR) or packet[IP].src - hostname = _decode_dhcp_option(options, HOSTNAME) + hostname = _decode_dhcp_option(options, HOSTNAME) or "" mac_address = _format_mac(packet[Ether].src) - if ip_address is None or hostname is None or mac_address is None: + if ip_address is None or mac_address is None: return self.process_client(ip_address, hostname, mac_address) diff --git a/tests/components/dhcp/test_init.py b/tests/components/dhcp/test_init.py index 122e81786c2..0da383c758a 100644 --- a/tests/components/dhcp/test_init.py +++ b/tests/components/dhcp/test_init.py @@ -81,6 +81,47 @@ RAW_DHCP_RENEWAL = ( b"\x43\x37\x08\x01\x21\x03\x06\x1c\x33\x3a\x3b\xff" ) +# 60:6b:bd:59:e4:b4 192.168.107.151 +RAW_DHCP_REQUEST_WITHOUT_HOSTNAME = ( + b"\xff\xff\xff\xff\xff\xff\x60\x6b\xbd\x59\xe4\xb4\x08\x00\x45\x00" + b"\x02\x40\x00\x00\x00\x00\x40\x11\x78\xae\x00\x00\x00\x00\xff\xff" + b"\xff\xff\x00\x44\x00\x43\x02\x2c\x02\x04\x01\x01\x06\x00\xff\x92" + b"\x7e\x31\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + b"\x00\x00\x00\x00\x00\x00\x60\x6b\xbd\x59\xe4\xb4\x00\x00\x00\x00" + b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + b"\x00\x00\x00\x00\x00\x00\x63\x82\x53\x63\x35\x01\x03\x3d\x07\x01" + b"\x60\x6b\xbd\x59\xe4\xb4\x3c\x25\x75\x64\x68\x63\x70\x20\x31\x2e" + b"\x31\x34\x2e\x33\x2d\x56\x44\x20\x4c\x69\x6e\x75\x78\x20\x56\x44" + b"\x4c\x69\x6e\x75\x78\x2e\x31\x2e\x32\x2e\x31\x2e\x78\x32\x04\xc0" + b"\xa8\x6b\x97\x36\x04\xc0\xa8\x6b\x01\x37\x07\x01\x03\x06\x0c\x0f" + b"\x1c\x2a\xff\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" +) + async def test_dhcp_match_hostname_and_macaddress(hass): """Test matching based on hostname and macaddress.""" @@ -182,6 +223,29 @@ async def test_dhcp_match_macaddress(hass): } +async def test_dhcp_match_macaddress_without_hostname(hass): + """Test matching based on macaddress only.""" + dhcp_watcher = dhcp.DHCPWatcher( + hass, {}, [{"domain": "mock-domain", "macaddress": "606BBD*"}] + ) + + packet = Ether(RAW_DHCP_REQUEST_WITHOUT_HOSTNAME) + + with patch.object(hass.config_entries.flow, "async_init") as mock_init: + dhcp_watcher.handle_dhcp_packet(packet) + + assert len(mock_init.mock_calls) == 1 + assert mock_init.mock_calls[0][1][0] == "mock-domain" + assert mock_init.mock_calls[0][2]["context"] == { + "source": config_entries.SOURCE_DHCP + } + assert mock_init.mock_calls[0][2]["data"] == { + dhcp.IP_ADDRESS: "192.168.107.151", + dhcp.HOSTNAME: "", + dhcp.MAC_ADDRESS: "606bbd59e4b4", + } + + async def test_dhcp_nomatch(hass): """Test not matching based on macaddress only.""" dhcp_watcher = dhcp.DHCPWatcher( From ddf563c247ce445c1a16b6a962d1cc22f1ddcbb5 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Thu, 15 Jul 2021 02:43:35 -1000 Subject: [PATCH 06/20] Add OUIs for legacy samsungtv (#52928) --- .../components/samsungtv/manifest.json | 6 +- homeassistant/generated/dhcp.py | 16 +++ .../components/samsungtv/test_config_flow.py | 106 +++++++++++++++--- 3 files changed, 110 insertions(+), 18 deletions(-) diff --git a/homeassistant/components/samsungtv/manifest.json b/homeassistant/components/samsungtv/manifest.json index 4ffe940f946..133baccf4fb 100644 --- a/homeassistant/components/samsungtv/manifest.json +++ b/homeassistant/components/samsungtv/manifest.json @@ -18,7 +18,11 @@ "dhcp": [ { "hostname": "tizen*" - } + }, + {"macaddress": "8CC8CD*"}, + {"macaddress": "606BBD*"}, + {"macaddress": "F47B5E*"}, + {"macaddress": "4844F7*"} ], "codeowners": [ "@escoand", diff --git a/homeassistant/generated/dhcp.py b/homeassistant/generated/dhcp.py index 82b09e5f7ef..dbdaaf6da5e 100644 --- a/homeassistant/generated/dhcp.py +++ b/homeassistant/generated/dhcp.py @@ -175,6 +175,22 @@ DHCP = [ "domain": "samsungtv", "hostname": "tizen*" }, + { + "domain": "samsungtv", + "macaddress": "8CC8CD*" + }, + { + "domain": "samsungtv", + "macaddress": "606BBD*" + }, + { + "domain": "samsungtv", + "macaddress": "F47B5E*" + }, + { + "domain": "samsungtv", + "macaddress": "4844F7*" + }, { "domain": "screenlogic", "hostname": "pentair: *", diff --git a/tests/components/samsungtv/test_config_flow.py b/tests/components/samsungtv/test_config_flow.py index a0d2875ca59..1c9fdbcd0c5 100644 --- a/tests/components/samsungtv/test_config_flow.py +++ b/tests/components/samsungtv/test_config_flow.py @@ -3,7 +3,7 @@ import socket from unittest.mock import Mock, PropertyMock, call, patch from samsungctl.exceptions import AccessDenied, UnhandledResponse -from samsungtvws.exceptions import ConnectionFailure +from samsungtvws.exceptions import ConnectionFailure, HttpApiError from websocket import WebSocketException, WebSocketProtocolException from homeassistant import config_entries @@ -86,6 +86,7 @@ MOCK_SSDP_DATA_WRONGMODEL = { ATTR_UPNP_UDN: "uuid:0d1cef00-00dc-1000-9c80-4844f7b172df", } MOCK_DHCP_DATA = {IP_ADDRESS: "fake_host", MAC_ADDRESS: "aa:bb:cc:dd:ee:ff"} +EXISTING_IP = "192.168.40.221" MOCK_ZEROCONF_DATA = { CONF_HOST: "fake_host", CONF_PORT: 1234, @@ -99,7 +100,13 @@ MOCK_ZEROCONF_DATA = { MOCK_OLD_ENTRY = { CONF_HOST: "fake_host", CONF_ID: "0d1cef00-00dc-1000-9c80-4844f7b172de_old", - CONF_IP_ADDRESS: "fake_ip_old", + CONF_IP_ADDRESS: EXISTING_IP, + CONF_METHOD: "legacy", + CONF_PORT: None, +} +MOCK_LEGACY_ENTRY = { + CONF_HOST: EXISTING_IP, + CONF_ID: "0d1cef00-00dc-1000-9c80-4844f7b172de_old", CONF_METHOD: "legacy", CONF_PORT: None, } @@ -306,17 +313,22 @@ async def test_ssdp_noprefix(hass: HomeAssistant, remote: Mock): assert result["type"] == "form" assert result["step_id"] == "confirm" - # entry was added - result = await hass.config_entries.flow.async_configure( - result["flow_id"], user_input="whatever" - ) - assert result["type"] == "create_entry" - assert result["title"] == "fake2_model" - assert result["data"][CONF_HOST] == "fake2_host" - assert result["data"][CONF_NAME] == "fake2_model" - assert result["data"][CONF_MANUFACTURER] == "Samsung fake2_manufacturer" - assert result["data"][CONF_MODEL] == "fake2_model" - assert result["result"].unique_id == "0d1cef00-00dc-1000-9c80-4844f7b172df" + with patch( + "homeassistant.components.samsungtv.bridge.Remote.__enter__", + return_value=True, + ): + + # entry was added + result = await hass.config_entries.flow.async_configure( + result["flow_id"], user_input="whatever" + ) + assert result["type"] == "create_entry" + assert result["title"] == "fake2_model" + assert result["data"][CONF_HOST] == "fake2_host" + assert result["data"][CONF_NAME] == "fake2_model" + assert result["data"][CONF_MANUFACTURER] == "Samsung fake2_manufacturer" + assert result["data"][CONF_MODEL] == "fake2_model" + assert result["result"].unique_id == "0d1cef00-00dc-1000-9c80-4844f7b172df" async def test_ssdp_legacy_missing_auth(hass: HomeAssistant, remote: Mock): @@ -867,7 +879,7 @@ async def test_update_old_entry(hass: HomeAssistant, remote: Mock): assert len(config_entries_domain) == 1 assert entry is config_entries_domain[0] assert entry.data[CONF_ID] == "0d1cef00-00dc-1000-9c80-4844f7b172de_old" - assert entry.data[CONF_IP_ADDRESS] == "fake_ip_old" + assert entry.data[CONF_IP_ADDRESS] == EXISTING_IP assert not entry.unique_id assert await async_setup_component(hass, DOMAIN, {}) is True @@ -998,6 +1010,69 @@ async def test_update_missing_mac_added_unique_id_preserved_from_zeroconf( assert entry.unique_id == "0d1cef00-00dc-1000-9c80-4844f7b172de" +async def test_update_legacy_missing_mac_from_dhcp(hass, remote: Mock): + """Test missing mac added.""" + entry = MockConfigEntry( + domain=DOMAIN, + data=MOCK_LEGACY_ENTRY, + unique_id="0d1cef00-00dc-1000-9c80-4844f7b172de", + ) + entry.add_to_hass(hass) + with patch( + "homeassistant.components.samsungtv.async_setup", + return_value=True, + ) as mock_setup, patch( + "homeassistant.components.samsungtv.async_setup_entry", + return_value=True, + ) as mock_setup_entry: + result = await hass.config_entries.flow.async_init( + DOMAIN, + context={"source": config_entries.SOURCE_DHCP}, + data={IP_ADDRESS: EXISTING_IP, MAC_ADDRESS: "aa:bb:cc:dd:ee:ff"}, + ) + await hass.async_block_till_done() + assert len(mock_setup.mock_calls) == 1 + assert len(mock_setup_entry.mock_calls) == 1 + assert result["type"] == "abort" + assert result["reason"] == "already_configured" + assert entry.data[CONF_MAC] == "aa:bb:cc:dd:ee:ff" + assert entry.unique_id == "0d1cef00-00dc-1000-9c80-4844f7b172de" + + +async def test_update_legacy_missing_mac_from_dhcp_no_unique_id(hass, remote: Mock): + """Test missing mac added when there is no unique id.""" + entry = MockConfigEntry( + domain=DOMAIN, + data=MOCK_LEGACY_ENTRY, + ) + entry.add_to_hass(hass) + with patch( + "homeassistant.components.samsungtv.bridge.SamsungTVWS.rest_device_info", + side_effect=HttpApiError, + ), patch( + "homeassistant.components.samsungtv.bridge.Remote.__enter__", + return_value=True, + ), patch( + "homeassistant.components.samsungtv.async_setup", + return_value=True, + ) as mock_setup, patch( + "homeassistant.components.samsungtv.async_setup_entry", + return_value=True, + ) as mock_setup_entry: + result = await hass.config_entries.flow.async_init( + DOMAIN, + context={"source": config_entries.SOURCE_DHCP}, + data={IP_ADDRESS: EXISTING_IP, MAC_ADDRESS: "aa:bb:cc:dd:ee:ff"}, + ) + await hass.async_block_till_done() + assert len(mock_setup.mock_calls) == 1 + assert len(mock_setup_entry.mock_calls) == 1 + assert result["type"] == "abort" + assert result["reason"] == "not_supported" + assert entry.data[CONF_MAC] == "aa:bb:cc:dd:ee:ff" + assert entry.unique_id is None + + async def test_form_reauth_legacy(hass, remote: Mock): """Test reauthenticate legacy.""" entry = MockConfigEntry(domain=DOMAIN, data=MOCK_OLD_ENTRY) @@ -1068,9 +1143,6 @@ async def test_form_reauth_websocket_cannot_connect(hass, remotews: Mock): ) await hass.async_block_till_done() - import pprint - - pprint.pprint(result2) assert result2["type"] == "form" assert result2["errors"] == {"base": RESULT_AUTH_MISSING} From cea22a9d5e9785e18d96ad3422f5534bd327519c Mon Sep 17 00:00:00 2001 From: Ron Klinkien Date: Tue, 13 Jul 2021 20:21:50 +0200 Subject: [PATCH 07/20] Bump python-fireservicerota to 0.0.43 (#52966) --- homeassistant/components/fireservicerota/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/fireservicerota/manifest.json b/homeassistant/components/fireservicerota/manifest.json index f35be9e839f..1eea9fbfbf1 100644 --- a/homeassistant/components/fireservicerota/manifest.json +++ b/homeassistant/components/fireservicerota/manifest.json @@ -3,7 +3,7 @@ "name": "FireServiceRota", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/fireservicerota", - "requirements": ["pyfireservicerota==0.0.42"], + "requirements": ["pyfireservicerota==0.0.43"], "codeowners": ["@cyberjunky"], "iot_class": "cloud_polling" } diff --git a/requirements_all.txt b/requirements_all.txt index cd4092d7c30..120b4706da5 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1423,7 +1423,7 @@ pyezviz==0.1.8.9 pyfido==2.1.1 # homeassistant.components.fireservicerota -pyfireservicerota==0.0.42 +pyfireservicerota==0.0.43 # homeassistant.components.flexit pyflexit==0.3 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 2ec48ca38b7..eabfc4f055f 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -791,7 +791,7 @@ pyezviz==0.1.8.9 pyfido==2.1.1 # homeassistant.components.fireservicerota -pyfireservicerota==0.0.42 +pyfireservicerota==0.0.43 # homeassistant.components.flume pyflume==0.5.5 From f17ed626bb8d64998f5dd01bd1a193da69276ea4 Mon Sep 17 00:00:00 2001 From: jjlawren Date: Tue, 13 Jul 2021 13:22:31 -0500 Subject: [PATCH 08/20] More graceful exception handling in Plex library sensors (#52969) --- homeassistant/components/plex/sensor.py | 8 +++++++ tests/components/plex/test_sensor.py | 31 +++++++++++++++++++++++++ 2 files changed, 39 insertions(+) diff --git a/homeassistant/components/plex/sensor.py b/homeassistant/components/plex/sensor.py index 95ba0a65ef0..7b01d48c862 100644 --- a/homeassistant/components/plex/sensor.py +++ b/homeassistant/components/plex/sensor.py @@ -2,6 +2,7 @@ import logging from plexapi.exceptions import NotFound +import requests.exceptions from homeassistant.components.sensor import SensorEntity from homeassistant.helpers.debounce import Debouncer @@ -171,6 +172,13 @@ class PlexLibrarySectionSensor(SensorEntity): self._available = True except NotFound: self._available = False + except requests.exceptions.RequestException as err: + _LOGGER.error( + "Could not update library sensor for '%s': %s", + self.library_section.title, + err, + ) + self._available = False self.async_write_ha_state() def _update_state_and_attrs(self): diff --git a/tests/components/plex/test_sensor.py b/tests/components/plex/test_sensor.py index 5fa50892f32..39a2901e72d 100644 --- a/tests/components/plex/test_sensor.py +++ b/tests/components/plex/test_sensor.py @@ -1,6 +1,8 @@ """Tests for Plex sensors.""" from datetime import timedelta +import requests.exceptions + from homeassistant.config_entries import RELOAD_AFTER_UPDATE_DELAY from homeassistant.const import STATE_UNAVAILABLE from homeassistant.helpers import entity_registry as er @@ -15,6 +17,7 @@ LIBRARY_UPDATE_PAYLOAD = {"StatusNotification": [{"title": "Library scan complet async def test_library_sensor_values( hass, + caplog, setup_plex_server, mock_websocket, requests_mock, @@ -63,6 +66,34 @@ async def test_library_sensor_values( assert library_tv_sensor.attributes["seasons"] == 1 assert library_tv_sensor.attributes["shows"] == 1 + # Handle `requests` exception + requests_mock.get( + "/library/sections/2/all?includeCollections=0&type=2", + exc=requests.exceptions.ReadTimeout, + ) + trigger_plex_update( + mock_websocket, msgtype="status", payload=LIBRARY_UPDATE_PAYLOAD + ) + await hass.async_block_till_done() + + library_tv_sensor = hass.states.get("sensor.plex_server_1_library_tv_shows") + assert library_tv_sensor.state == STATE_UNAVAILABLE + + assert "Could not update library sensor" in caplog.text + + # Ensure sensor updates properly when it recovers + requests_mock.get( + "/library/sections/2/all?includeCollections=0&type=2", + text=library_tvshows_size, + ) + trigger_plex_update( + mock_websocket, msgtype="status", payload=LIBRARY_UPDATE_PAYLOAD + ) + await hass.async_block_till_done() + + library_tv_sensor = hass.states.get("sensor.plex_server_1_library_tv_shows") + assert library_tv_sensor.state == "10" + # Handle library deletion requests_mock.get( "/library/sections/2/all?includeCollections=0&type=2", status_code=404 From 4ddfaf41eeb02fa7b78bdd97a5338cd797214f32 Mon Sep 17 00:00:00 2001 From: Tom Harris Date: Tue, 13 Jul 2021 14:27:04 -0400 Subject: [PATCH 09/20] Fix issue connecting to Insteon Hub v2 (#52970) --- homeassistant/components/insteon/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/insteon/manifest.json b/homeassistant/components/insteon/manifest.json index 353cd55c747..4643a8c662a 100644 --- a/homeassistant/components/insteon/manifest.json +++ b/homeassistant/components/insteon/manifest.json @@ -3,7 +3,7 @@ "name": "Insteon", "documentation": "https://www.home-assistant.io/integrations/insteon", "requirements": [ - "pyinsteon==1.0.11" + "pyinsteon==1.0.12" ], "codeowners": [ "@teharris1" diff --git a/requirements_all.txt b/requirements_all.txt index 120b4706da5..1f790a84243 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1490,7 +1490,7 @@ pyialarm==1.9.0 pyicloud==0.10.2 # homeassistant.components.insteon -pyinsteon==1.0.11 +pyinsteon==1.0.12 # homeassistant.components.intesishome pyintesishome==1.7.6 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index eabfc4f055f..bf0664a4e58 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -837,7 +837,7 @@ pyialarm==1.9.0 pyicloud==0.10.2 # homeassistant.components.insteon -pyinsteon==1.0.11 +pyinsteon==1.0.12 # homeassistant.components.ipma pyipma==2.0.5 From 4e2042b63d229148acd933e058105dc0178c9bb0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Klomp?= Date: Tue, 13 Jul 2021 19:33:53 +0200 Subject: [PATCH 10/20] Bump pysma to 0.6.4 (#52973) --- homeassistant/components/sma/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/sma/manifest.json b/homeassistant/components/sma/manifest.json index a48b9ba74ce..985a0506574 100644 --- a/homeassistant/components/sma/manifest.json +++ b/homeassistant/components/sma/manifest.json @@ -3,7 +3,7 @@ "name": "SMA Solar", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/sma", - "requirements": ["pysma==0.6.2"], + "requirements": ["pysma==0.6.4"], "codeowners": ["@kellerza", "@rklomp"], "iot_class": "local_polling" } diff --git a/requirements_all.txt b/requirements_all.txt index 1f790a84243..6b237f03f11 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1749,7 +1749,7 @@ pysignalclirestapi==0.3.4 pyskyqhub==0.1.3 # homeassistant.components.sma -pysma==0.6.2 +pysma==0.6.4 # homeassistant.components.smappee pysmappee==0.2.25 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index bf0664a4e58..ba98cbe4aa5 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -991,7 +991,7 @@ pysiaalarm==3.0.0 pysignalclirestapi==0.3.4 # homeassistant.components.sma -pysma==0.6.2 +pysma==0.6.4 # homeassistant.components.smappee pysmappee==0.2.25 From 3050d9350a849b6bb3fbe2794f7f4370bdcfcd91 Mon Sep 17 00:00:00 2001 From: Peter Nijssen Date: Wed, 14 Jul 2021 00:10:23 +0200 Subject: [PATCH 11/20] Update pyrainbird to 0.4.3 (#52990) --- homeassistant/components/rainbird/manifest.json | 2 +- requirements_all.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/rainbird/manifest.json b/homeassistant/components/rainbird/manifest.json index 120e38e8058..d7d3c064ad7 100644 --- a/homeassistant/components/rainbird/manifest.json +++ b/homeassistant/components/rainbird/manifest.json @@ -2,7 +2,7 @@ "domain": "rainbird", "name": "Rain Bird", "documentation": "https://www.home-assistant.io/integrations/rainbird", - "requirements": ["pyrainbird==0.4.2"], + "requirements": ["pyrainbird==0.4.3"], "codeowners": ["@konikvranik"], "iot_class": "local_polling" } diff --git a/requirements_all.txt b/requirements_all.txt index 6b237f03f11..cd39fd4d224 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1696,7 +1696,7 @@ pyqwikswitch==0.93 pyrail==0.0.3 # homeassistant.components.rainbird -pyrainbird==0.4.2 +pyrainbird==0.4.3 # homeassistant.components.recswitch pyrecswitch==1.0.2 From 762f5a5d18d4aa4e038474d11c78eba4da9c7abb Mon Sep 17 00:00:00 2001 From: Andre Lengwenus Date: Wed, 14 Jul 2021 19:59:11 +0200 Subject: [PATCH 12/20] Bump pypck to 0.7.10 (#53013) --- homeassistant/components/lcn/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/lcn/manifest.json b/homeassistant/components/lcn/manifest.json index 092e07eb5d2..1adc407d692 100644 --- a/homeassistant/components/lcn/manifest.json +++ b/homeassistant/components/lcn/manifest.json @@ -3,7 +3,7 @@ "name": "LCN", "config_flow": false, "documentation": "https://www.home-assistant.io/integrations/lcn", - "requirements": ["pypck==0.7.9"], + "requirements": ["pypck==0.7.10"], "codeowners": ["@alengwenus"], "iot_class": "local_push" } diff --git a/requirements_all.txt b/requirements_all.txt index cd39fd4d224..f57ac367ca3 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1669,7 +1669,7 @@ pyownet==0.10.0.post1 pypca==0.0.7 # homeassistant.components.lcn -pypck==0.7.9 +pypck==0.7.10 # homeassistant.components.pjlink pypjlink2==1.2.1 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index ba98cbe4aa5..1bec3389bbc 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -950,7 +950,7 @@ pyowm==3.2.0 pyownet==0.10.0.post1 # homeassistant.components.lcn -pypck==0.7.9 +pypck==0.7.10 # homeassistant.components.plaato pyplaato==0.0.15 From c90fa90faf85dd3b976ed23ceb9849c23b492339 Mon Sep 17 00:00:00 2001 From: Eduard van Valkenburg Date: Wed, 14 Jul 2021 20:01:16 +0200 Subject: [PATCH 13/20] fix for timestamp not present in SIA (#53015) --- homeassistant/components/sia/utils.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/sia/utils.py b/homeassistant/components/sia/utils.py index 66fdd7d95be..6b87c9cb1fc 100644 --- a/homeassistant/components/sia/utils.py +++ b/homeassistant/components/sia/utils.py @@ -6,6 +6,8 @@ from typing import Any from pysiaalarm import SIAEvent +from homeassistant.util.dt import utcnow + from .const import ATTR_CODE, ATTR_ID, ATTR_MESSAGE, ATTR_TIMESTAMP, ATTR_ZONE PING_INTERVAL_MARGIN = 30 @@ -42,7 +44,9 @@ def get_event_data_from_sia_event(event: SIAEvent) -> dict[str, Any]: "code": event.code, "message": event.message, "x_data": event.x_data, - "timestamp": event.timestamp.isoformat(), + "timestamp": event.timestamp.isoformat() + if event.timestamp + else utcnow().isoformat(), "event_qualifier": event.event_qualifier, "event_type": event.event_type, "partition": event.partition, From 480215714684287eed9870a1d4ffde6ba2332b78 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Hjelseth=20H=C3=B8yer?= Date: Wed, 14 Jul 2021 19:58:02 +0200 Subject: [PATCH 14/20] Co2signal, set SCAN_INTERVAL (#53023) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * limit co2signal, wip Signed-off-by: Daniel Hjelseth Høyer * limit co2signal Signed-off-by: Daniel Hjelseth Høyer * limit co2signal Signed-off-by: Daniel Hjelseth Høyer --- homeassistant/components/co2signal/sensor.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/homeassistant/components/co2signal/sensor.py b/homeassistant/components/co2signal/sensor.py index 980ffa8549b..e9cfdb87983 100644 --- a/homeassistant/components/co2signal/sensor.py +++ b/homeassistant/components/co2signal/sensor.py @@ -1,4 +1,5 @@ """Support for the CO2signal platform.""" +from datetime import timedelta import logging import CO2Signal @@ -17,6 +18,7 @@ import homeassistant.helpers.config_validation as cv CONF_COUNTRY_CODE = "country_code" _LOGGER = logging.getLogger(__name__) +SCAN_INTERVAL = timedelta(minutes=3) ATTRIBUTION = "Data provided by CO2signal" From acf705a95862c8e233bfedbbaf3679b98887bdaa Mon Sep 17 00:00:00 2001 From: Eduard van Valkenburg Date: Thu, 15 Jul 2021 09:31:17 +0200 Subject: [PATCH 15/20] Another SIA fix for timestamp not present. (#53045) --- homeassistant/components/sia/utils.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/sia/utils.py b/homeassistant/components/sia/utils.py index 6b87c9cb1fc..9150099656c 100644 --- a/homeassistant/components/sia/utils.py +++ b/homeassistant/components/sia/utils.py @@ -25,7 +25,9 @@ def get_attr_from_sia_event(event: SIAEvent) -> dict[str, Any]: ATTR_CODE: event.code, ATTR_MESSAGE: event.message, ATTR_ID: event.id, - ATTR_TIMESTAMP: event.timestamp.isoformat(), + ATTR_TIMESTAMP: event.timestamp.isoformat() + if event.timestamp + else utcnow().isoformat(), } From 2da660b76ec1f561d408037d404e3ded9f366fa8 Mon Sep 17 00:00:00 2001 From: da-anda Date: Thu, 15 Jul 2021 14:41:04 +0200 Subject: [PATCH 16/20] Fix knx expose feature not correctly falling back to default value (#53046) --- homeassistant/components/knx/expose.py | 23 ++++++++++------------- 1 file changed, 10 insertions(+), 13 deletions(-) diff --git a/homeassistant/components/knx/expose.py b/homeassistant/components/knx/expose.py index 5b57e2b0b4c..5b92f9f1f6a 100644 --- a/homeassistant/components/knx/expose.py +++ b/homeassistant/components/knx/expose.py @@ -100,10 +100,8 @@ class KNXExposeSensor: def _init_expose_state(self) -> None: """Initialize state of the exposure.""" init_state = self.hass.states.get(self.entity_id) - init_value = self._get_expose_value(init_state) - self.device.sensor_value.value = ( - init_value if init_value is not None else self.expose_default - ) + state_value = self._get_expose_value(init_state) + self.device.sensor_value.value = state_value @callback def shutdown(self) -> None: @@ -116,12 +114,13 @@ class KNXExposeSensor: def _get_expose_value(self, state: State | None) -> StateType: """Extract value from state.""" if state is None or state.state in (STATE_UNKNOWN, STATE_UNAVAILABLE): - return None - value = ( - state.state - if self.expose_attribute is None - else state.attributes.get(self.expose_attribute) - ) + value = self.expose_default + else: + value = ( + state.state + if self.expose_attribute is None + else state.attributes.get(self.expose_attribute, self.expose_default) + ) if self.type == "binary": if value in (1, STATE_ON, "True"): return True @@ -150,9 +149,7 @@ class KNXExposeSensor: async def _async_set_knx_value(self, value: StateType) -> None: """Set new value on xknx ExposeSensor.""" if value is None: - if self.expose_default is None: - return - value = self.expose_default + return await self.device.set(value) From c5070da20e8f9ae8e2240eeddc800bd3214fba95 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Thu, 15 Jul 2021 10:27:18 -0700 Subject: [PATCH 17/20] Expose Spotify as a service (#53063) --- homeassistant/components/spotify/media_player.py | 1 + 1 file changed, 1 insertion(+) diff --git a/homeassistant/components/spotify/media_player.py b/homeassistant/components/spotify/media_player.py index 1c92e2ce51a..c88aa453d2c 100644 --- a/homeassistant/components/spotify/media_player.py +++ b/homeassistant/components/spotify/media_player.py @@ -266,6 +266,7 @@ class SpotifyMediaPlayer(MediaPlayerEntity): "manufacturer": "Spotify AB", "model": model, "name": self._name, + "entry_type": "service", } @property From a6ad08f5b65e3518c7c5328b7d4c163a5c3fa7b8 Mon Sep 17 00:00:00 2001 From: Vincent Le Bourlot Date: Thu, 15 Jul 2021 23:24:54 +0200 Subject: [PATCH 18/20] Increase polling interval to prevent reaching daily limit (#53066) * increase polling interval to prevent reaching daily limit * update test accordingly --- homeassistant/components/home_plus_control/__init__.py | 2 +- tests/components/home_plus_control/test_switch.py | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/homeassistant/components/home_plus_control/__init__.py b/homeassistant/components/home_plus_control/__init__.py index 954203e9b10..718900533aa 100644 --- a/homeassistant/components/home_plus_control/__init__.py +++ b/homeassistant/components/home_plus_control/__init__.py @@ -133,7 +133,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: name="home_plus_control_module", update_method=async_update_data, # Polling interval. Will only be polled if there are subscribers. - update_interval=timedelta(seconds=60), + update_interval=timedelta(seconds=300), ) hass_entry_data[DATA_COORDINATOR] = coordinator diff --git a/tests/components/home_plus_control/test_switch.py b/tests/components/home_plus_control/test_switch.py index aec23f0d32a..75d416ba2b1 100644 --- a/tests/components/home_plus_control/test_switch.py +++ b/tests/components/home_plus_control/test_switch.py @@ -146,7 +146,7 @@ async def test_plant_topology_reduction_change( return_value=mock_modules, ) as mock_check: async_fire_time_changed( - hass, dt.datetime.now(dt.timezone.utc) + dt.timedelta(seconds=100) + hass, dt.datetime.now(dt.timezone.utc) + dt.timedelta(seconds=400) ) await hass.async_block_till_done() assert len(mock_check.mock_calls) == 1 @@ -208,7 +208,7 @@ async def test_plant_topology_increase_change( return_value=mock_modules, ) as mock_check: async_fire_time_changed( - hass, dt.datetime.now(dt.timezone.utc) + dt.timedelta(seconds=100) + hass, dt.datetime.now(dt.timezone.utc) + dt.timedelta(seconds=400) ) await hass.async_block_till_done() assert len(mock_check.mock_calls) == 1 @@ -268,7 +268,7 @@ async def test_module_status_unavailable(hass, mock_config_entry, mock_modules): return_value=mock_modules, ) as mock_check: async_fire_time_changed( - hass, dt.datetime.now(dt.timezone.utc) + dt.timedelta(seconds=100) + hass, dt.datetime.now(dt.timezone.utc) + dt.timedelta(seconds=400) ) await hass.async_block_till_done() assert len(mock_check.mock_calls) == 1 @@ -339,7 +339,7 @@ async def test_module_status_available( return_value=mock_modules, ) as mock_check: async_fire_time_changed( - hass, dt.datetime.now(dt.timezone.utc) + dt.timedelta(seconds=100) + hass, dt.datetime.now(dt.timezone.utc) + dt.timedelta(seconds=400) ) await hass.async_block_till_done() assert len(mock_check.mock_calls) == 1 @@ -443,7 +443,7 @@ async def test_update_with_api_error( side_effect=HomePlusControlApiError, ) as mock_check: async_fire_time_changed( - hass, dt.datetime.now(dt.timezone.utc) + dt.timedelta(seconds=100) + hass, dt.datetime.now(dt.timezone.utc) + dt.timedelta(seconds=400) ) await hass.async_block_till_done() assert len(mock_check.mock_calls) == 1 From 1295daa10ec97289b24670fb2af9b60f8046aad8 Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Fri, 16 Jul 2021 09:03:28 +0200 Subject: [PATCH 19/20] Add light white parameter to light/services.yaml (#53075) --- homeassistant/components/light/services.yaml | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/homeassistant/components/light/services.yaml b/homeassistant/components/light/services.yaml index e2a8a94a74a..778203a1c93 100644 --- a/homeassistant/components/light/services.yaml +++ b/homeassistant/components/light/services.yaml @@ -265,6 +265,17 @@ turn_on: min: -100 max: 100 unit_of_measurement: "%" + white: + name: White + description: + Set the light to white mode and change its brightness, where 0 turns + the light off, 1 is the minimum brightness and 255 is the maximum + brightness supported by the light. + advanced: true + selector: + number: + min: 0 + max: 255 profile: name: Profile description: Name of a light profile to use. From ae40ba6a74974fa94106338b79f5443f1f9861a4 Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Fri, 16 Jul 2021 09:46:32 +0200 Subject: [PATCH 20/20] Bumped version to 2021.7.3 --- homeassistant/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/const.py b/homeassistant/const.py index 2c90713249c..2fb83613a3f 100644 --- a/homeassistant/const.py +++ b/homeassistant/const.py @@ -5,7 +5,7 @@ from typing import Final MAJOR_VERSION: Final = 2021 MINOR_VERSION: Final = 7 -PATCH_VERSION: Final = "2" +PATCH_VERSION: Final = "3" __short_version__: Final = f"{MAJOR_VERSION}.{MINOR_VERSION}" __version__: Final = f"{__short_version__}.{PATCH_VERSION}" REQUIRED_PYTHON_VER: Final[tuple[int, int, int]] = (3, 8, 0)