diff --git a/homeassistant/components/deconz/manifest.json b/homeassistant/components/deconz/manifest.json index fbd420e4b16..0ae9e8c98b0 100644 --- a/homeassistant/components/deconz/manifest.json +++ b/homeassistant/components/deconz/manifest.json @@ -4,7 +4,7 @@ "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/deconz", "requirements": [ - "pydeconz==81" + "pydeconz==82" ], "ssdp": [ { diff --git a/homeassistant/components/device_tracker/device_condition.py b/homeassistant/components/device_tracker/device_condition.py index 714d6d7f016..afa899444f6 100644 --- a/homeassistant/components/device_tracker/device_condition.py +++ b/homeassistant/components/device_tracker/device_condition.py @@ -11,7 +11,6 @@ from homeassistant.const import ( CONF_ENTITY_ID, CONF_TYPE, STATE_HOME, - STATE_NOT_HOME, ) from homeassistant.core import HomeAssistant, callback from homeassistant.helpers import condition, config_validation as cv, entity_registry @@ -62,14 +61,15 @@ def async_condition_from_config( """Create a function to test a device condition.""" if config_validation: config = CONDITION_SCHEMA(config) - if config[CONF_TYPE] == "is_home": - state = STATE_HOME - else: - state = STATE_NOT_HOME + + reverse = config[CONF_TYPE] == "is_not_home" @callback def test_is_state(hass: HomeAssistant, variables: TemplateVarsType) -> bool: """Test if an entity is a certain state.""" - return condition.state(hass, config[ATTR_ENTITY_ID], state) + result = condition.state(hass, config[ATTR_ENTITY_ID], STATE_HOME) + if reverse: + result = not result + return result return test_is_state diff --git a/homeassistant/components/dlna_dmr/manifest.json b/homeassistant/components/dlna_dmr/manifest.json index 87730aa1316..d11b32a6dd5 100644 --- a/homeassistant/components/dlna_dmr/manifest.json +++ b/homeassistant/components/dlna_dmr/manifest.json @@ -2,7 +2,7 @@ "domain": "dlna_dmr", "name": "DLNA Digital Media Renderer", "documentation": "https://www.home-assistant.io/integrations/dlna_dmr", - "requirements": ["async-upnp-client==0.19.0"], + "requirements": ["async-upnp-client==0.19.1"], "codeowners": [], "iot_class": "local_push" } diff --git a/homeassistant/components/homekit/config_flow.py b/homeassistant/components/homekit/config_flow.py index 506b4f5c7a8..c7c3e5da833 100644 --- a/homeassistant/components/homekit/config_flow.py +++ b/homeassistant/components/homekit/config_flow.py @@ -7,6 +7,7 @@ import voluptuous as vol from homeassistant import config_entries from homeassistant.components.camera import DOMAIN as CAMERA_DOMAIN +from homeassistant.components.lock import DOMAIN as LOCK_DOMAIN from homeassistant.components.media_player import DOMAIN as MEDIA_PLAYER_DOMAIN from homeassistant.components.remote import DOMAIN as REMOTE_DOMAIN from homeassistant.config_entries import SOURCE_IMPORT @@ -54,7 +55,12 @@ MODE_EXCLUDE = "exclude" INCLUDE_EXCLUDE_MODES = [MODE_EXCLUDE, MODE_INCLUDE] -DOMAINS_NEED_ACCESSORY_MODE = [CAMERA_DOMAIN, MEDIA_PLAYER_DOMAIN, REMOTE_DOMAIN] +DOMAINS_NEED_ACCESSORY_MODE = [ + CAMERA_DOMAIN, + LOCK_DOMAIN, + MEDIA_PLAYER_DOMAIN, + REMOTE_DOMAIN, +] NEVER_BRIDGED_DOMAINS = [CAMERA_DOMAIN] CAMERA_ENTITY_PREFIX = f"{CAMERA_DOMAIN}." diff --git a/homeassistant/components/homekit/strings.json b/homeassistant/components/homekit/strings.json index 56bc5438eac..3c9671c93e2 100644 --- a/homeassistant/components/homekit/strings.json +++ b/homeassistant/components/homekit/strings.json @@ -43,7 +43,7 @@ "data": { "include_domains": "Domains to include" }, - "description": "Choose the domains to be included. All supported entities in the domain will be included. A separate HomeKit instance in accessory mode will be created for each tv media player and camera.", + "description": "Choose the domains to be included. All supported entities in the domain will be included. A separate HomeKit instance in accessory mode will be created for each tv media player, activity based remote, lock, and camera.", "title": "Select domains to be included" }, "pairing": { diff --git a/homeassistant/components/homekit/translations/en.json b/homeassistant/components/homekit/translations/en.json index aa78c3e4adc..cee1e64ad56 100644 --- a/homeassistant/components/homekit/translations/en.json +++ b/homeassistant/components/homekit/translations/en.json @@ -12,7 +12,7 @@ "data": { "include_domains": "Domains to include" }, - "description": "Choose the domains to be included. All supported entities in the domain will be included. A separate HomeKit instance in accessory mode will be created for each tv media player and camera.", + "description": "Choose the domains to be included. All supported entities in the domain will be included. A separate HomeKit instance in accessory mode will be created for each tv media player, activity based remote, lock, and camera.", "title": "Select domains to be included" } } diff --git a/homeassistant/components/homekit/util.py b/homeassistant/components/homekit/util.py index 673abc5da67..6585e9e9c4e 100644 --- a/homeassistant/components/homekit/util.py +++ b/homeassistant/components/homekit/util.py @@ -499,12 +499,11 @@ def accessory_friendly_name(hass_name, accessory): def state_needs_accessory_mode(state): """Return if the entity represented by the state must be paired in accessory mode.""" - if state.domain == CAMERA_DOMAIN: + if state.domain in (CAMERA_DOMAIN, LOCK_DOMAIN): return True return ( - state.domain == LOCK_DOMAIN - or state.domain == MEDIA_PLAYER_DOMAIN + state.domain == MEDIA_PLAYER_DOMAIN and state.attributes.get(ATTR_DEVICE_CLASS) == DEVICE_CLASS_TV or state.domain == REMOTE_DOMAIN and state.attributes.get(ATTR_SUPPORTED_FEATURES, 0) & SUPPORT_ACTIVITY diff --git a/homeassistant/components/modbus/modbus.py b/homeassistant/components/modbus/modbus.py index f2b033bec3e..8d2ea46e293 100644 --- a/homeassistant/components/modbus/modbus.py +++ b/homeassistant/components/modbus/modbus.py @@ -249,17 +249,22 @@ class ModbusHub: for entry in self._pb_call.values(): entry[ENTRY_FUNC] = getattr(self._client, entry[ENTRY_NAME]) + await self.async_connect_task() + return True + + async def async_connect_task(self): + """Try to connect, and retry if needed.""" async with self._lock: if not await self.hass.async_add_executor_job(self._pymodbus_connect): - self._log_error("initial connect failed, no retry", error_state=False) - return False + err = f"{self._config_name} connect failed, retry in pymodbus" + self._log_error(err, error_state=False) + return # Start counting down to allow modbus requests. if self._config_delay: self._async_cancel_listener = async_call_later( self.hass, self._config_delay, self.async_end_delay ) - return True @callback def async_end_delay(self, args): @@ -313,8 +318,6 @@ class ModbusHub: return None if not self._client: return None - if not self._client.is_socket_open(): - return None async with self._lock: result = await self.hass.async_add_executor_job( self._pymodbus_call, unit, address, value, use_call diff --git a/homeassistant/components/nexia/manifest.json b/homeassistant/components/nexia/manifest.json index eb471597ec6..a453ec7f1df 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==0.9.9"], + "requirements": ["nexia==0.9.10"], "codeowners": ["@bdraco"], "documentation": "https://www.home-assistant.io/integrations/nexia", "config_flow": true, diff --git a/homeassistant/components/remote_rpi_gpio/__init__.py b/homeassistant/components/remote_rpi_gpio/__init__.py index afa60e44bed..b11625eb7b2 100644 --- a/homeassistant/components/remote_rpi_gpio/__init__.py +++ b/homeassistant/components/remote_rpi_gpio/__init__.py @@ -1,5 +1,5 @@ """Support for controlling GPIO pins of a Raspberry Pi.""" -from gpiozero import LED, Button +from gpiozero import LED, DigitalInputDevice from gpiozero.pins.pigpio import PiGPIOFactory CONF_BOUNCETIME = "bouncetime" @@ -38,7 +38,7 @@ def setup_input(address, port, pull_mode, bouncetime): pull_gpio_up = False try: - return Button( + return DigitalInputDevice( port, pull_up=pull_gpio_up, bounce_time=bouncetime, @@ -56,6 +56,6 @@ def write_output(switch, value): switch.off() -def read_input(button): +def read_input(sensor): """Read a value from a GPIO.""" - return button.is_pressed + return sensor.value diff --git a/homeassistant/components/remote_rpi_gpio/binary_sensor.py b/homeassistant/components/remote_rpi_gpio/binary_sensor.py index 86966ec4d87..aeff838d68d 100644 --- a/homeassistant/components/remote_rpi_gpio/binary_sensor.py +++ b/homeassistant/components/remote_rpi_gpio/binary_sensor.py @@ -42,12 +42,12 @@ def setup_platform(hass, config, add_entities, discovery_info=None): devices = [] for port_num, port_name in ports.items(): try: - button = remote_rpi_gpio.setup_input( + remote_sensor = remote_rpi_gpio.setup_input( address, port_num, pull_mode, bouncetime ) except (ValueError, IndexError, KeyError, OSError): return - new_sensor = RemoteRPiGPIOBinarySensor(port_name, button, invert_logic) + new_sensor = RemoteRPiGPIOBinarySensor(port_name, remote_sensor, invert_logic) devices.append(new_sensor) add_entities(devices, True) @@ -56,23 +56,23 @@ def setup_platform(hass, config, add_entities, discovery_info=None): class RemoteRPiGPIOBinarySensor(BinarySensorEntity): """Represent a binary sensor that uses a Remote Raspberry Pi GPIO.""" - def __init__(self, name, button, invert_logic): + def __init__(self, name, sensor, invert_logic): """Initialize the RPi binary sensor.""" self._name = name self._invert_logic = invert_logic self._state = False - self._button = button + self._sensor = sensor async def async_added_to_hass(self): """Run when entity about to be added to hass.""" def read_gpio(): """Read state from GPIO.""" - self._state = remote_rpi_gpio.read_input(self._button) + self._state = remote_rpi_gpio.read_input(self._sensor) self.schedule_update_ha_state() - self._button.when_released = read_gpio - self._button.when_pressed = read_gpio + self._sensor.when_deactivated = read_gpio + self._sensor.when_activated = read_gpio @property def should_poll(self): @@ -97,6 +97,6 @@ class RemoteRPiGPIOBinarySensor(BinarySensorEntity): def update(self): """Update the GPIO state.""" try: - self._state = remote_rpi_gpio.read_input(self._button) + self._state = remote_rpi_gpio.read_input(self._sensor) except requests.exceptions.ConnectionError: return diff --git a/homeassistant/components/simplisafe/__init__.py b/homeassistant/components/simplisafe/__init__.py index 01e31633a1a..d4e43631b78 100644 --- a/homeassistant/components/simplisafe/__init__.py +++ b/homeassistant/components/simplisafe/__init__.py @@ -3,7 +3,11 @@ import asyncio from uuid import UUID from simplipy import get_api -from simplipy.errors import EndpointUnavailable, InvalidCredentialsError, SimplipyError +from simplipy.errors import ( + EndpointUnavailableError, + InvalidCredentialsError, + SimplipyError, +) import voluptuous as vol from homeassistant.const import ATTR_CODE, CONF_CODE, CONF_PASSWORD, CONF_USERNAME @@ -379,7 +383,7 @@ class SimpliSafe: if isinstance(result, InvalidCredentialsError): raise ConfigEntryAuthFailed("Invalid credentials") from result - if isinstance(result, EndpointUnavailable): + if isinstance(result, EndpointUnavailableError): # In case the user attempts an action not allowed in their current plan, # we merely log that message at INFO level (so the user is aware, # but not spammed with ERROR messages that they cannot change): diff --git a/homeassistant/components/simplisafe/manifest.json b/homeassistant/components/simplisafe/manifest.json index 02713b106bd..3ec1e38ad4d 100644 --- a/homeassistant/components/simplisafe/manifest.json +++ b/homeassistant/components/simplisafe/manifest.json @@ -3,7 +3,7 @@ "name": "SimpliSafe", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/simplisafe", - "requirements": ["simplisafe-python==11.0.1"], + "requirements": ["simplisafe-python==11.0.2"], "codeowners": ["@bachya"], "iot_class": "cloud_polling" } diff --git a/homeassistant/components/sonos/manifest.json b/homeassistant/components/sonos/manifest.json index e873b43839a..a35faee4ad6 100644 --- a/homeassistant/components/sonos/manifest.json +++ b/homeassistant/components/sonos/manifest.json @@ -3,7 +3,7 @@ "name": "Sonos", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/sonos", - "requirements": ["pysonos==0.0.52"], + "requirements": ["pysonos==0.0.53"], "dependencies": ["ssdp"], "after_dependencies": ["plex", "zeroconf"], "zeroconf": ["_sonos._tcp.local."], diff --git a/homeassistant/components/ssdp/manifest.json b/homeassistant/components/ssdp/manifest.json index faadfac5c0c..432686d9027 100644 --- a/homeassistant/components/ssdp/manifest.json +++ b/homeassistant/components/ssdp/manifest.json @@ -4,7 +4,7 @@ "documentation": "https://www.home-assistant.io/integrations/ssdp", "requirements": [ "defusedxml==0.7.1", - "async-upnp-client==0.19.0" + "async-upnp-client==0.19.1" ], "dependencies": ["network"], "after_dependencies": ["zeroconf"], diff --git a/homeassistant/components/surepetcare/manifest.json b/homeassistant/components/surepetcare/manifest.json index 1f0804e0581..ee97e1ac627 100644 --- a/homeassistant/components/surepetcare/manifest.json +++ b/homeassistant/components/surepetcare/manifest.json @@ -3,6 +3,6 @@ "name": "Sure Petcare", "documentation": "https://www.home-assistant.io/integrations/surepetcare", "codeowners": ["@benleb", "@danielhiversen"], - "requirements": ["surepy==0.6.0"], + "requirements": ["surepy==0.7.0"], "iot_class": "cloud_polling" } diff --git a/homeassistant/components/unifi/sensor.py b/homeassistant/components/unifi/sensor.py index 8d34d3cabd7..338f695a2b4 100644 --- a/homeassistant/components/unifi/sensor.py +++ b/homeassistant/components/unifi/sensor.py @@ -133,6 +133,34 @@ class UniFiUpTimeSensor(UniFiClient, SensorEntity): _attr_device_class = DEVICE_CLASS_TIMESTAMP + def __init__(self, client, controller): + """Set up tracked client.""" + super().__init__(client, controller) + + self.last_updated_time = self.client.uptime + + @callback + def async_update_callback(self) -> None: + """Update sensor when time has changed significantly. + + This will help avoid unnecessary updates to the state machine. + """ + update_state = True + + if self.client.uptime < 1000000000: + if self.client.uptime > self.last_updated_time: + update_state = False + else: + if self.client.uptime <= self.last_updated_time: + update_state = False + + self.last_updated_time = self.client.uptime + + if not update_state: + return None + + super().async_update_callback() + @property def name(self) -> str: """Return the name of the client.""" diff --git a/homeassistant/components/upnp/manifest.json b/homeassistant/components/upnp/manifest.json index b252f5082cb..810a53c9e28 100644 --- a/homeassistant/components/upnp/manifest.json +++ b/homeassistant/components/upnp/manifest.json @@ -3,7 +3,7 @@ "name": "UPnP/IGD", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/upnp", - "requirements": ["async-upnp-client==0.19.0"], + "requirements": ["async-upnp-client==0.19.1"], "dependencies": ["ssdp"], "codeowners": ["@StevenLooman"], "ssdp": [ diff --git a/homeassistant/components/wemo/wemo_device.py b/homeassistant/components/wemo/wemo_device.py index 3b0fbdcbe55..6fd1f4d5512 100644 --- a/homeassistant/components/wemo/wemo_device.py +++ b/homeassistant/components/wemo/wemo_device.py @@ -1,7 +1,7 @@ """Home Assistant wrapper for a pyWeMo device.""" import logging -from pywemo import PyWeMoException, WeMoDevice +from pywemo import WeMoDevice from pywemo.subscribe import EVENT_TYPE_LONG_PRESS from homeassistant.config_entries import ConfigEntry @@ -81,8 +81,10 @@ async def async_register_device( if device.supports_long_press: try: await hass.async_add_executor_job(wemo.ensure_long_press_virtual_device) - except PyWeMoException: - _LOGGER.warning( + # Temporarily handling all exceptions for #52996 & pywemo/pywemo/issues/276 + # Replace this with `except: PyWeMoException` after upstream has been fixed. + except Exception: # pylint: disable=broad-except + _LOGGER.exception( "Failed to enable long press support for device: %s", wemo.name ) device.supports_long_press = False diff --git a/homeassistant/const.py b/homeassistant/const.py index 2fb83613a3f..9ad1ceb8a15 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 = "3" +PATCH_VERSION: Final = "4" __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) diff --git a/homeassistant/package_constraints.txt b/homeassistant/package_constraints.txt index 908cd379886..ec9ab9d062e 100644 --- a/homeassistant/package_constraints.txt +++ b/homeassistant/package_constraints.txt @@ -4,7 +4,7 @@ aiodiscover==1.4.2 aiohttp==3.7.4.post0 aiohttp_cors==0.7.0 astral==2.2 -async-upnp-client==0.19.0 +async-upnp-client==0.19.1 async_timeout==3.0.1 attrs==21.2.0 awesomeversion==21.4.0 diff --git a/requirements_all.txt b/requirements_all.txt index f57ac367ca3..7081245a660 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -304,7 +304,7 @@ asterisk_mbox==0.5.0 # homeassistant.components.dlna_dmr # homeassistant.components.ssdp # homeassistant.components.upnp -async-upnp-client==0.19.0 +async-upnp-client==0.19.1 # homeassistant.components.supla asyncpysupla==0.0.5 @@ -1020,7 +1020,7 @@ nettigo-air-monitor==1.0.0 neurio==0.3.1 # homeassistant.components.nexia -nexia==0.9.9 +nexia==0.9.10 # homeassistant.components.nextcloud nextcloudmonitor==1.1.0 @@ -1375,7 +1375,7 @@ pydaikin==2.4.4 pydanfossair==0.1.0 # homeassistant.components.deconz -pydeconz==81 +pydeconz==82 # homeassistant.components.delijn pydelijn==0.6.1 @@ -1773,7 +1773,7 @@ pysnmp==4.4.12 pysoma==0.0.10 # homeassistant.components.sonos -pysonos==0.0.52 +pysonos==0.0.53 # homeassistant.components.spc pyspcwebgw==0.4.0 @@ -2102,7 +2102,7 @@ simplehound==0.3 simplepush==1.1.4 # homeassistant.components.simplisafe -simplisafe-python==11.0.1 +simplisafe-python==11.0.2 # homeassistant.components.sisyphus sisyphus-control==3.0 @@ -2210,7 +2210,7 @@ sucks==0.9.4 sunwatcher==0.2.1 # homeassistant.components.surepetcare -surepy==0.6.0 +surepy==0.7.0 # homeassistant.components.swiss_hydrological_data swisshydrodata==0.1.0 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 1bec3389bbc..41b4b8fa0b1 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -196,7 +196,7 @@ arcam-fmj==0.7.0 # homeassistant.components.dlna_dmr # homeassistant.components.ssdp # homeassistant.components.upnp -async-upnp-client==0.19.0 +async-upnp-client==0.19.1 # homeassistant.components.aurora auroranoaa==0.0.2 @@ -574,7 +574,7 @@ netdisco==2.9.0 nettigo-air-monitor==1.0.0 # homeassistant.components.nexia -nexia==0.9.9 +nexia==0.9.10 # homeassistant.components.notify_events notify-events==1.0.4 @@ -770,7 +770,7 @@ pycoolmasternet-async==0.1.2 pydaikin==2.4.4 # homeassistant.components.deconz -pydeconz==81 +pydeconz==82 # homeassistant.components.dexcom pydexcom==0.2.0 @@ -1006,7 +1006,7 @@ pysmartthings==0.7.6 pysoma==0.0.10 # homeassistant.components.sonos -pysonos==0.0.52 +pysonos==0.0.53 # homeassistant.components.spc pyspcwebgw==0.4.0 @@ -1148,7 +1148,7 @@ sharkiqpy==0.1.8 simplehound==0.3 # homeassistant.components.simplisafe -simplisafe-python==11.0.1 +simplisafe-python==11.0.2 # homeassistant.components.slack slackclient==2.5.0 @@ -1215,7 +1215,7 @@ subarulink==0.3.12 sunwatcher==0.2.1 # homeassistant.components.surepetcare -surepy==0.6.0 +surepy==0.7.0 # homeassistant.components.synology_dsm synologydsm-api==1.0.2 diff --git a/tests/components/deconz/test_light.py b/tests/components/deconz/test_light.py index 42dc04fc7ae..c2b12651fc0 100644 --- a/tests/components/deconz/test_light.py +++ b/tests/components/deconz/test_light.py @@ -742,7 +742,18 @@ async def test_groups(hass, aioclient_mock, input, expected): "name": "Group", "type": "LightGroup", "state": {"all_on": False, "any_on": True}, - "action": {}, + "action": { + "alert": "none", + "bri": 127, + "colormode": "hs", + "ct": 0, + "effect": "none", + "hue": 0, + "on": True, + "sat": 127, + "scene": None, + "xy": [0, 0], + }, "scenes": [], "lights": input["lights"], }, diff --git a/tests/components/device_tracker/test_device_condition.py b/tests/components/device_tracker/test_device_condition.py index 2cd4aceeb07..7e3f79712c4 100644 --- a/tests/components/device_tracker/test_device_condition.py +++ b/tests/components/device_tracker/test_device_condition.py @@ -3,7 +3,7 @@ import pytest import homeassistant.components.automation as automation from homeassistant.components.device_tracker import DOMAIN -from homeassistant.const import STATE_HOME, STATE_NOT_HOME +from homeassistant.const import STATE_HOME from homeassistant.helpers import device_registry from homeassistant.setup import async_setup_component @@ -119,7 +119,7 @@ async def test_if_state(hass, calls): assert len(calls) == 1 assert calls[0].data["some"] == "is_home - event - test_event1" - hass.states.async_set("device_tracker.entity", STATE_NOT_HOME) + hass.states.async_set("device_tracker.entity", "school") hass.bus.async_fire("test_event1") hass.bus.async_fire("test_event2") await hass.async_block_till_done() diff --git a/tests/components/homekit/test_config_flow.py b/tests/components/homekit/test_config_flow.py index c06e8aaa5ad..f3707f9f71e 100644 --- a/tests/components/homekit/test_config_flow.py +++ b/tests/components/homekit/test_config_flow.py @@ -144,6 +144,7 @@ async def test_setup_creates_entries_for_accessory_mode_devices(hass): """Test we can setup a new instance and we create entries for accessory mode devices.""" hass.states.async_set("camera.one", "on") hass.states.async_set("camera.existing", "on") + hass.states.async_set("lock.new", "on") hass.states.async_set("media_player.two", "on", {"device_class": "tv"}) hass.states.async_set("remote.standard", "on") hass.states.async_set("remote.activity", "on", {"supported_features": 4}) @@ -180,7 +181,7 @@ async def test_setup_creates_entries_for_accessory_mode_devices(hass): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], - {"include_domains": ["camera", "media_player", "light", "remote"]}, + {"include_domains": ["camera", "media_player", "light", "lock", "remote"]}, ) assert result2["type"] == data_entry_flow.RESULT_TYPE_FORM assert result2["step_id"] == "pairing" @@ -207,7 +208,7 @@ async def test_setup_creates_entries_for_accessory_mode_devices(hass): "filter": { "exclude_domains": [], "exclude_entities": [], - "include_domains": ["media_player", "light", "remote"], + "include_domains": ["media_player", "light", "lock", "remote"], "include_entities": [], }, "exclude_accessory_mode": True, @@ -225,7 +226,8 @@ async def test_setup_creates_entries_for_accessory_mode_devices(hass): # 4 - camera.one in accessory mode # 5 - media_player.two in accessory mode # 6 - remote.activity in accessory mode - assert len(mock_setup_entry.mock_calls) == 6 + # 7 - lock.new in accessory mode + assert len(mock_setup_entry.mock_calls) == 7 async def test_import(hass): diff --git a/tests/components/modbus/test_init.py b/tests/components/modbus/test_init.py index 435b8446b6b..6349d6bffe3 100644 --- a/tests/components/modbus/test_init.py +++ b/tests/components/modbus/test_init.py @@ -514,35 +514,6 @@ async def test_pymodbus_constructor_fail(hass, caplog): assert mock_pb.called -@pytest.mark.parametrize( - "do_connect,do_exception,do_text", - [ - [False, None, "initial connect failed, no retry"], - [True, ModbusException("no connect"), "Modbus Error: no connect"], - ], -) -async def test_pymodbus_connect_fail( - hass, do_connect, do_exception, do_text, caplog, mock_pymodbus -): - """Run test for failing pymodbus connect.""" - config = { - DOMAIN: [ - { - CONF_TYPE: "tcp", - CONF_HOST: TEST_HOST, - CONF_PORT: 5501, - } - ] - } - caplog.set_level(logging.ERROR) - mock_pymodbus.connect.return_value = do_connect - mock_pymodbus.connect.side_effect = do_exception - assert await async_setup_component(hass, DOMAIN, config) is False - await hass.async_block_till_done() - assert caplog.messages[0].startswith(f"Pymodbus: {do_text}") - assert caplog.records[0].levelname == "ERROR" - - async def test_pymodbus_close_fail(hass, caplog, mock_pymodbus): """Run test for failing pymodbus close.""" config = { @@ -562,44 +533,6 @@ async def test_pymodbus_close_fail(hass, caplog, mock_pymodbus): # Close() is called as part of teardown -async def test_disconnect(hass, mock_pymodbus): - """Run test for startup delay.""" - - # the purpose of this test is to test a device disconnect - # We "hijiack" a binary_sensor to make a proper blackbox test. - entity_id = f"{BINARY_SENSOR_DOMAIN}.{TEST_SENSOR_NAME}" - config = { - DOMAIN: [ - { - CONF_TYPE: "tcp", - CONF_HOST: TEST_HOST, - CONF_PORT: 5501, - CONF_NAME: TEST_MODBUS_NAME, - CONF_BINARY_SENSORS: [ - { - CONF_INPUT_TYPE: CALL_TYPE_COIL, - CONF_NAME: f"{TEST_SENSOR_NAME}", - CONF_ADDRESS: 52, - }, - ], - } - ] - } - mock_pymodbus.read_coils.return_value = ReadResult([0x01]) - mock_pymodbus.is_socket_open.return_value = False - now = dt_util.utcnow() - with mock.patch("homeassistant.helpers.event.dt_util.utcnow", return_value=now): - assert await async_setup_component(hass, DOMAIN, config) is True - await hass.async_block_till_done() - - # pass first scan_interval - now = now + timedelta(seconds=20) - with mock.patch("homeassistant.helpers.event.dt_util.utcnow", return_value=now): - async_fire_time_changed(hass, now) - await hass.async_block_till_done() - assert hass.states.get(entity_id).state == STATE_UNAVAILABLE - - async def test_delay(hass, mock_pymodbus): """Run test for startup delay.""" diff --git a/tests/components/unifi/test_sensor.py b/tests/components/unifi/test_sensor.py index eec4fba7df9..fbf697e295f 100644 --- a/tests/components/unifi/test_sensor.py +++ b/tests/components/unifi/test_sensor.py @@ -4,6 +4,7 @@ from datetime import datetime from unittest.mock import patch from aiounifi.controller import MESSAGE_CLIENT, MESSAGE_CLIENT_REMOVED +import pytest from homeassistant.components.device_tracker import DOMAIN as TRACKER_DOMAIN from homeassistant.components.sensor import DOMAIN as SENSOR_DOMAIN @@ -134,19 +135,29 @@ async def test_bandwidth_sensors(hass, aioclient_mock, mock_unifi_websocket): assert len(hass.states.async_entity_ids(SENSOR_DOMAIN)) == 4 -async def test_uptime_sensors(hass, aioclient_mock, mock_unifi_websocket): +@pytest.mark.parametrize( + "initial_uptime,event_uptime,new_uptime", + [ + # Uptime listed in epoch time should never change + (1609462800, 1609462800, 1612141200), + # Uptime counted in seconds increases with every event + (60, 64, 60), + ], +) +async def test_uptime_sensors( + hass, + aioclient_mock, + mock_unifi_websocket, + initial_uptime, + event_uptime, + new_uptime, +): """Verify that uptime sensors are working as expected.""" - client1 = { + uptime_client = { "mac": "00:00:00:00:00:01", "name": "client1", "oui": "Producer", - "uptime": 1609506061, - } - client2 = { - "hostname": "Client2", - "mac": "00:00:00:00:00:02", - "oui": "Producer", - "uptime": 60, + "uptime": initial_uptime, } options = { CONF_ALLOW_BANDWIDTH_SENSORS: False, @@ -155,32 +166,50 @@ async def test_uptime_sensors(hass, aioclient_mock, mock_unifi_websocket): CONF_TRACK_DEVICES: False, } - now = datetime(2021, 1, 1, 1, tzinfo=dt_util.UTC) + now = datetime(2021, 1, 1, 1, 1, 0, tzinfo=dt_util.UTC) with patch("homeassistant.util.dt.now", return_value=now): config_entry = await setup_unifi_integration( hass, aioclient_mock, options=options, - clients_response=[client1, client2], + clients_response=[uptime_client], ) - assert len(hass.states.async_all()) == 3 - assert len(hass.states.async_entity_ids(SENSOR_DOMAIN)) == 2 - assert hass.states.get("sensor.client1_uptime").state == "2021-01-01T13:01:01+00:00" - assert hass.states.get("sensor.client2_uptime").state == "2021-01-01T00:59:00+00:00" + assert len(hass.states.async_all()) == 2 + assert len(hass.states.async_entity_ids(SENSOR_DOMAIN)) == 1 + assert hass.states.get("sensor.client1_uptime").state == "2021-01-01T01:00:00+00:00" - # Verify state update + # Verify normal new event doesn't change uptime + # 4 seconds has passed - client1["uptime"] = 1609506062 - mock_unifi_websocket( - data={ - "meta": {"message": MESSAGE_CLIENT}, - "data": [client1], - } - ) - await hass.async_block_till_done() + uptime_client["uptime"] = event_uptime + now = datetime(2021, 1, 1, 1, 1, 4, tzinfo=dt_util.UTC) + with patch("homeassistant.util.dt.now", return_value=now): + mock_unifi_websocket( + data={ + "meta": {"message": MESSAGE_CLIENT}, + "data": [uptime_client], + } + ) + await hass.async_block_till_done() - assert hass.states.get("sensor.client1_uptime").state == "2021-01-01T13:01:02+00:00" + assert hass.states.get("sensor.client1_uptime").state == "2021-01-01T01:00:00+00:00" + + # Verify new event change uptime + # 1 month has passed + + uptime_client["uptime"] = new_uptime + now = datetime(2021, 2, 1, 1, 1, 0, tzinfo=dt_util.UTC) + with patch("homeassistant.util.dt.now", return_value=now): + mock_unifi_websocket( + data={ + "meta": {"message": MESSAGE_CLIENT}, + "data": [uptime_client], + } + ) + await hass.async_block_till_done() + + assert hass.states.get("sensor.client1_uptime").state == "2021-02-01T01:00:00+00:00" # Disable option @@ -191,7 +220,6 @@ async def test_uptime_sensors(hass, aioclient_mock, mock_unifi_websocket): assert len(hass.states.async_all()) == 1 assert len(hass.states.async_entity_ids(SENSOR_DOMAIN)) == 0 assert hass.states.get("sensor.client1_uptime") is None - assert hass.states.get("sensor.client2_uptime") is None # Enable option @@ -200,14 +228,13 @@ async def test_uptime_sensors(hass, aioclient_mock, mock_unifi_websocket): hass.config_entries.async_update_entry(config_entry, options=options.copy()) await hass.async_block_till_done() - assert len(hass.states.async_all()) == 3 - assert len(hass.states.async_entity_ids(SENSOR_DOMAIN)) == 2 + assert len(hass.states.async_all()) == 2 + assert len(hass.states.async_entity_ids(SENSOR_DOMAIN)) == 1 assert hass.states.get("sensor.client1_uptime") - assert hass.states.get("sensor.client2_uptime") # Try to add the sensors again, using a signal - clients_connected = {client1["mac"], client2["mac"]} + clients_connected = {uptime_client["mac"]} devices_connected = set() controller = hass.data[UNIFI_DOMAIN][config_entry.entry_id] @@ -220,8 +247,8 @@ async def test_uptime_sensors(hass, aioclient_mock, mock_unifi_websocket): ) await hass.async_block_till_done() - assert len(hass.states.async_all()) == 3 - assert len(hass.states.async_entity_ids(SENSOR_DOMAIN)) == 2 + assert len(hass.states.async_all()) == 2 + assert len(hass.states.async_entity_ids(SENSOR_DOMAIN)) == 1 async def test_remove_sensors(hass, aioclient_mock, mock_unifi_websocket):